Commit b17b841b by chrisndodge

Merge pull request #15 from edx/afzaledx/tests_for_get_student_view

improved coverage.
parents 4898316d fe892365
...@@ -2,24 +2,24 @@ ...@@ -2,24 +2,24 @@
<div class="sequence proctored-exam entrance" data-exam-id="{{exam_id}}"> <div class="sequence proctored-exam entrance" data-exam-id="{{exam_id}}">
<h3> <h3>
{% blocktrans %} {% blocktrans %}
Would you Like to take {{ display_name }} as Proctored Exam? Would you like to take {{ display_name }} as a Proctored Exam?
{% endblocktrans %} {% endblocktrans %}
</h3> </h3>
<p> <p>
{% blocktrans %} {% blocktrans %}
Since you're enrolled in this course as a verified student, you have the option to take this exam Since you're enrolled in this course as a verified student, you have the option to take this exam with
with online proctoring. Online proctoring is one requirement towards being eligible for credit. online proctoring. Online proctoring is one requirement towards being eligible for credit.
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<div class="gated-sequence"> <div class="gated-sequence">
<span><i class="fa fa-lock"></i></span> <span><i class="fa fa-lock"></i></span>
<a class="start-timed-exam" data-ajax-url="{{enter_exam_endpoint}}" data-exam-id="{{exam_id}}" data-attempt-proctored=true> <a class="start-timed-exam" data-ajax-url="{{enter_exam_endpoint}}" data-exam-id="{{exam_id}}" data-attempt-proctored=true>
{% trans "Yes, take this exam as a proctored exam (and be eligible for credit)" %} {% trans "Yes, take this as a proctored exam (and be eligible for credit)" %}
</a> </a>
<p> <p>
{% blocktrans %} {% blocktrans %}
You will need to <strong>download and install edX-approved software </strong>for the online proctoring You will need to <strong>download and install edX-approved software </strong>for the online proctoring of
of your exam. After successful installation, you will be <strong>guided through setting up your your exam. After successful installation, you will be <strong>guided through setting up your
proctored session and begin the exam immediately </strong>afterwards.</p> proctored session and begin the exam immediately </strong>afterwards.</p>
{% endblocktrans %} {% endblocktrans %}
<i class="fa fa-arrow-circle-right start-timed-exam" data-ajax-url="{{enter_exam_endpoint}}" data-exam-id="{{exam_id}}" data-attempt-proctored=true></i> <i class="fa fa-arrow-circle-right start-timed-exam" data-ajax-url="{{enter_exam_endpoint}}" data-exam-id="{{exam_id}}" data-attempt-proctored=true></i>
...@@ -27,12 +27,12 @@ ...@@ -27,12 +27,12 @@
<div class="gated-sequence"> <div class="gated-sequence">
<span><i class="fa fa-unlock"></i></span> <span><i class="fa fa-unlock"></i></span>
<a class="start-timed-exam" data-ajax-url="{{enter_exam_endpoint}}" data-exam-id="{{exam_id}}" data-attempt-proctored=false> <a class="start-timed-exam" data-ajax-url="{{enter_exam_endpoint}}" data-exam-id="{{exam_id}}" data-attempt-proctored=false>
{% trans "No, take this exam as an open exam (and not be eligible for credit)" %} {% trans "No, take this as an open exam (and not be eligible for credit)" %}
</a> </a>
<p> <p>
{% blocktrans %} {% blocktrans %}
You may proceed and begin the exam at your leisure, but <strong>you will not be able to apply for college You may proceed and begin the exam at your leisure, but <strong>you will not be able to apply for
credit </strong>upon completing the exam or this course in general. college credit </strong>upon completing the exam or this course in general.
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<i class="fa fa-arrow-circle-right start-timed-exam" data-ajax-url="{{enter_exam_endpoint}}" data-exam-id="{{exam_id}}" data-attempt-proctored=false></i> <i class="fa fa-arrow-circle-right start-timed-exam" data-ajax-url="{{enter_exam_endpoint}}" data-exam-id="{{exam_id}}" data-attempt-proctored=false></i>
......
...@@ -7,8 +7,8 @@ ...@@ -7,8 +7,8 @@
</h3> </h3>
<p> <p>
{% blocktrans %} {% blocktrans %}
you have chosen to take {{display_name}} as a proctored exam. You should be redirected to a new window You've chosen to take {{display_name}} as a proctored exam. You should be directed to a new window
with installation and setup instructions. You can also <a href="#">open the installation</a> with installation and setup instructions. You can also <a href="#">open the installation window directly.</a>
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<div class="proctored-exam-message"> <div class="proctored-exam-message">
...@@ -20,16 +20,16 @@ ...@@ -20,16 +20,16 @@
<h1> {{exam_code}}</h1> <h1> {{exam_code}}</h1>
<p> <p>
{% blocktrans %} {% blocktrans %}
Please do not share this code. It can only be used once and it tied to your edX Account. Please do not share this code. It can only be used once and it tied to your edX account.
{% endblocktrans %} {% endblocktrans %}
</p> </p>
</div> </div>
</div> </div>
<div class="footer-sequence border-b-0 padding-b-0"> <div class="footer-sequence border-b-0 padding-b-0">
<span> {% trans "Note: Once you complete your installation and set up, your timed exam will begin." %} </span> <span> {% trans "Note: Once you complete installation and set up, your timed exam will begin." %} </span>
<p> <p>
{% blocktrans %} {% blocktrans %}
Please be prepared to start the exam and follow all the guidelines of an edX proctored exam. Please be prepared to start the exam and follow all of the guidelines of an edX proctored exam.
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<p class="proctored-exam-option"> <p class="proctored-exam-option">
......
...@@ -14,23 +14,24 @@ from edx_proctoring.api import ( ...@@ -14,23 +14,24 @@ from edx_proctoring.api import (
stop_exam_attempt, stop_exam_attempt,
get_active_exams_for_user, get_active_exams_for_user,
get_exam_attempt, get_exam_attempt,
create_exam_attempt create_exam_attempt,
get_student_view,
) )
from edx_proctoring.exceptions import ( from edx_proctoring.exceptions import (
ProctoredExamAlreadyExists, ProctoredExamAlreadyExists,
ProctoredExamNotFoundException, ProctoredExamNotFoundException,
StudentExamAttemptAlreadyExistsException, StudentExamAttemptAlreadyExistsException,
StudentExamAttemptDoesNotExistsException, StudentExamAttemptDoesNotExistsException,
StudentExamAttemptedAlreadyStarted StudentExamAttemptedAlreadyStarted,
) )
from edx_proctoring.models import ( from edx_proctoring.models import (
ProctoredExam, ProctoredExam,
ProctoredExamStudentAllowance, ProctoredExamStudentAllowance,
ProctoredExamStudentAttempt ProctoredExamStudentAttempt,
) )
from .utils import ( from .utils import (
LoggedInTestCase LoggedInTestCase,
) )
...@@ -54,6 +55,12 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -54,6 +55,12 @@ class ProctoredExamApiTests(LoggedInTestCase):
self.external_id = 'test_external_id' self.external_id = 'test_external_id'
self.proctored_exam_id = self._create_proctored_exam() self.proctored_exam_id = self._create_proctored_exam()
# Messages for get_student_view
self.start_an_exam_msg = 'Would you like to take %s as a Proctored Exam?'
self.timed_exam_msg = '%s is a Timed Exam'
self.exam_time_expired_msg = 'you did not submit your exam before the time allotted expired'
self.chose_proctored_exam_msg = 'You\'ve chosen to take %s as a proctored exam'
def _create_proctored_exam(self): def _create_proctored_exam(self):
""" """
Calls the api's create_exam to create an exam object. Calls the api's create_exam to create an exam object.
...@@ -75,7 +82,7 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -75,7 +82,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
external_id=self.external_id external_id=self.external_id
) )
def _create_started_exam_attempt(self): def _create_started_exam_attempt(self, started_at=None):
""" """
Creates the ProctoredExamStudentAttempt object. Creates the ProctoredExamStudentAttempt object.
""" """
...@@ -83,7 +90,7 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -83,7 +90,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
proctored_exam_id=self.proctored_exam_id, proctored_exam_id=self.proctored_exam_id,
user_id=self.user_id, user_id=self.user_id,
external_id=self.external_id, external_id=self.external_id,
started_at=datetime.now(pytz.UTC) started_at=started_at if started_at else datetime.now(pytz.UTC)
) )
def _add_allowance_for_user(self): def _add_allowance_for_user(self):
...@@ -201,7 +208,7 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -201,7 +208,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
def test_create_an_exam_attempt(self): def test_create_an_exam_attempt(self):
""" """
Start an exam attempt. Create an unstarted exam attempt.
""" """
attempt_id = create_exam_attempt(self.proctored_exam_id, self.user_id, '') attempt_id = create_exam_attempt(self.proctored_exam_id, self.user_id, '')
self.assertGreater(attempt_id, 0) self.assertGreater(attempt_id, 0)
...@@ -217,7 +224,7 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -217,7 +224,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
def test_get_exam_attempt(self): def test_get_exam_attempt(self):
""" """
Test to get the already made exam attempt. Test to get the existing exam attempt.
""" """
self._create_unstarted_exam_attempt() self._create_unstarted_exam_attempt()
exam_attempt = get_exam_attempt(self.proctored_exam_id, self.user_id) exam_attempt = get_exam_attempt(self.proctored_exam_id, self.user_id)
...@@ -295,3 +302,100 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -295,3 +302,100 @@ class ProctoredExamApiTests(LoggedInTestCase):
self.assertEqual(len(student_active_exams), 2) self.assertEqual(len(student_active_exams), 2)
self.assertEqual(len(student_active_exams[0]['allowances']), 2) self.assertEqual(len(student_active_exams[0]['allowances']), 2)
self.assertEqual(len(student_active_exams[1]['allowances']), 0) self.assertEqual(len(student_active_exams[1]['allowances']), 0)
def test_get_student_view(self):
"""
Test for get_student_view promting the user to take the exam
as a timed exam or a proctored exam.
"""
rendered_response = get_student_view(
user_id=self.user_id,
course_id=self.course_id,
content_id=self.content_id,
context={
'is_proctored': True,
'display_name': self.exam_name,
'default_time_limit_mins': 90
}
)
self.assertIn('data-exam-id="%d"' % self.proctored_exam_id, rendered_response)
self.assertIn(self.start_an_exam_msg % self.exam_name, rendered_response)
def test_getstudentview_timedexam(self):
"""
Test for get_student_view Timed exam which is not proctored.
"""
rendered_response = get_student_view(
user_id=self.user_id,
course_id="abc",
content_id=self.content_id,
context={
'is_proctored': False,
'display_name': self.exam_name,
'default_time_limit_mins': 90
}
)
self.assertNotIn('data-exam-id="%d"' % self.proctored_exam_id, rendered_response)
self.assertIn(self.timed_exam_msg % self.exam_name, rendered_response)
self.assertNotIn(self.start_an_exam_msg % self.exam_name, rendered_response)
def test_getstudentview_unstartedxm(self):
"""
Test for get_student_view proctored exam which has not started yet.
"""
self._create_unstarted_exam_attempt()
rendered_response = get_student_view(
user_id=self.user_id,
course_id=self.course_id,
content_id=self.content_id,
context={
'is_proctored': True,
'display_name': self.exam_name,
'default_time_limit_mins': 90
}
)
self.assertIn(self.chose_proctored_exam_msg % self.exam_name, rendered_response)
def test_getstudentview_startedxam(self):
"""
Test for get_student_view proctored exam which has started.
"""
self._create_started_exam_attempt()
get_student_view(
user_id=self.user_id,
course_id=self.course_id,
content_id=self.content_id,
context={
'is_proctored': True,
'display_name': self.exam_name,
'default_time_limit_mins': 90
}
)
# TO DO Not getting anything back in this call to get_student_view.
# Will have to implement the testcase later when functionality is implemented.
# TO DO No condition set for finished exam in the student view. Will have to test later.
# def test_getstudentview_finishedxam(self):
def test_getstudentview_expiredxam(self):
"""
Test for get_student_view proctored exam which has started.
"""
self._create_started_exam_attempt(started_at=datetime.now(pytz.UTC).replace(year=2010))
rendered_response = get_student_view(
user_id=self.user_id,
course_id=self.course_id,
content_id=self.content_id,
context={
'is_proctored': True,
'display_name': self.exam_name,
'default_time_limit_mins': 90
}
)
self.assertIn(self.exam_time_expired_msg, rendered_response)
...@@ -23,3 +23,11 @@ class TestProctoringService(unittest.TestCase): ...@@ -23,3 +23,11 @@ class TestProctoringService(unittest.TestCase):
attr = getattr(edx_proctoring_api, attr_name, None) attr = getattr(edx_proctoring_api, attr_name, None)
if isinstance(attr, types.FunctionType) and not attr_name.startswith('_'): if isinstance(attr, types.FunctionType) and not attr_name.startswith('_'):
self.assertTrue(hasattr(service, attr_name)) self.assertTrue(hasattr(service, attr_name))
def test_singleton(self):
"""
Test to make sure the ProctoringService is a singleton.
"""
service1 = ProctoringService()
service2 = ProctoringService()
self.assertIs(service1, service2)
...@@ -42,3 +42,6 @@ class TestHumanizedTime(unittest.TestCase): ...@@ -42,3 +42,6 @@ class TestHumanizedTime(unittest.TestCase):
human_time = humanized_time(180) human_time = humanized_time(180)
self.assertEqual(human_time, "3 Hours") self.assertEqual(human_time, "3 Hours")
human_time = humanized_time(-60)
self.assertEqual(human_time, "error")
...@@ -28,31 +28,33 @@ def humanized_time(time_in_minutes): ...@@ -28,31 +28,33 @@ def humanized_time(time_in_minutes):
hours = int(time_in_minutes / 60) hours = int(time_in_minutes / 60)
minutes = time_in_minutes % 60 minutes = time_in_minutes % 60
template = ""
hours_present = False hours_present = False
if hours == 0: if hours == 0:
hours_present = False hours_present = False
template = ""
elif hours == 1: elif hours == 1:
template = _("{num_of_hours} Hour") template = _("{num_of_hours} Hour")
hours_present = True hours_present = True
elif hours >= 2: elif hours >= 2:
template = _("{num_of_hours} Hours") template = _("{num_of_hours} Hours")
hours_present = True hours_present = True
else:
if minutes == 0: template = "error"
if not hours_present:
template = _("{num_of_minutes} Minutes") if template != "error":
elif minutes == 1: if minutes == 0:
if hours_present: if not hours_present:
template += _(" and {num_of_minutes} Minute") template = _("{num_of_minutes} Minutes")
else: elif minutes == 1:
template += _("{num_of_minutes} Minute") if hours_present:
elif minutes >= 2: template += _(" and {num_of_minutes} Minute")
if hours_present: else:
template += _(" and {num_of_minutes} Minutes") template += _("{num_of_minutes} Minute")
else: else:
template += _("{num_of_minutes} Minutes") if hours_present:
template += _(" and {num_of_minutes} Minutes")
else:
template += _("{num_of_minutes} Minutes")
human_time = template.format(num_of_hours=hours, num_of_minutes=minutes) human_time = template.format(num_of_hours=hours, num_of_minutes=minutes)
return human_time return human_time
...@@ -175,7 +175,8 @@ class ProctoredExamView(AuthenticatedAPIView): ...@@ -175,7 +175,8 @@ class ProctoredExamView(AuthenticatedAPIView):
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
data={"detail": "The exam_id does not exist."} data={"detail": "The exam_id does not exist."}
) )
elif course_id is not None and content_id is not None: else:
# get by course_id & content_id
try: try:
return Response( return Response(
data=get_exam_by_content_id(course_id, content_id), data=get_exam_by_content_id(course_id, content_id),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment