Commit 0a429c11 by chrisndodge

Merge pull request #106 from edx/cdodge/make-sure-callback-timesout-attempt

when the client app polls, be sure to timeout when time expires
parents 3343f983 23855534
...@@ -254,10 +254,14 @@ def _check_for_attempt_timeout(attempt): ...@@ -254,10 +254,14 @@ def _check_for_attempt_timeout(attempt):
def _get_exam_attempt(exam_attempt_obj): def _get_exam_attempt(exam_attempt_obj):
""" """
Helper method to commonalize the two query patterns Helper method to commonalize all query patterns
""" """
if not exam_attempt_obj:
return None
serialized_attempt_obj = ProctoredExamStudentAttemptSerializer(exam_attempt_obj) serialized_attempt_obj = ProctoredExamStudentAttemptSerializer(exam_attempt_obj)
attempt = serialized_attempt_obj.data if exam_attempt_obj else None attempt = serialized_attempt_obj.data
attempt = _check_for_attempt_timeout(attempt) attempt = _check_for_attempt_timeout(attempt)
return attempt return attempt
...@@ -279,6 +283,16 @@ def get_exam_attempt_by_id(attempt_id): ...@@ -279,6 +283,16 @@ def get_exam_attempt_by_id(attempt_id):
return _get_exam_attempt(exam_attempt_obj) return _get_exam_attempt(exam_attempt_obj)
def get_exam_attempt_by_code(attempt_code):
"""
Signals the beginning of an exam attempt when we only have
an attempt code
"""
exam_attempt_obj = ProctoredExamStudentAttempt.objects.get_exam_attempt_by_code(attempt_code)
return _get_exam_attempt(exam_attempt_obj)
def update_exam_attempt(attempt_id, **kwargs): def update_exam_attempt(attempt_id, **kwargs):
""" """
update exam_attempt update exam_attempt
...@@ -297,17 +311,6 @@ def update_exam_attempt(attempt_id, **kwargs): ...@@ -297,17 +311,6 @@ def update_exam_attempt(attempt_id, **kwargs):
exam_attempt_obj.save() exam_attempt_obj.save()
def get_exam_attempt_by_code(attempt_code):
"""
Signals the beginning of an exam attempt when we only have
an attempt code
"""
exam_attempt_obj = ProctoredExamStudentAttempt.objects.get_exam_attempt_by_code(attempt_code)
serialized_attempt_obj = ProctoredExamStudentAttemptSerializer(exam_attempt_obj)
return serialized_attempt_obj.data if exam_attempt_obj else None
def create_exam_attempt(exam_id, user_id, taking_as_proctored=False): def create_exam_attempt(exam_id, user_id, taking_as_proctored=False):
""" """
Creates an exam attempt for user_id against exam_id. There should only be Creates an exam attempt for user_id against exam_id. There should only be
......
# pylint: disable=too-many-lines # pylint: disable=too-many-lines, invalid-name
""" """
All tests for the models.py All tests for the models.py
...@@ -34,7 +34,8 @@ from edx_proctoring.api import ( ...@@ -34,7 +34,8 @@ from edx_proctoring.api import (
mark_exam_attempt_as_ready, mark_exam_attempt_as_ready,
update_attempt_status, update_attempt_status,
get_attempt_status_summary, get_attempt_status_summary,
update_exam_attempt update_exam_attempt,
_check_for_attempt_timeout
) )
from edx_proctoring.exceptions import ( from edx_proctoring.exceptions import (
ProctoredExamAlreadyExists, ProctoredExamAlreadyExists,
...@@ -384,6 +385,18 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -384,6 +385,18 @@ class ProctoredExamApiTests(LoggedInTestCase):
attempt = get_exam_attempt_by_id(attempt_id) attempt = get_exam_attempt_by_id(attempt_id)
self.assertEqual(attempt['allowed_time_limit_mins'], self.default_time_limit + allowed_extra_time) self.assertEqual(attempt['allowed_time_limit_mins'], self.default_time_limit + allowed_extra_time)
def test_no_existing_attempt(self):
"""
Make sure we get back a None when calling get_exam_attempt_by_id() with a non existing attempt
"""
self.assertIsNone(get_exam_attempt_by_id(0))
def test_check_for_attempt_timeout_with_none(self):
"""
Make sure that we can safely pass in a None into _check_for_attempt_timeout
"""
self.assertIsNone(_check_for_attempt_timeout(None))
def test_recreate_an_exam_attempt(self): def test_recreate_an_exam_attempt(self):
""" """
Start an exam attempt that has already been created. Start an exam attempt that has already been created.
......
...@@ -557,6 +557,68 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase): ...@@ -557,6 +557,68 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase):
response_data = json.loads(response.content) response_data = json.loads(response.content)
self.assertEqual(response_data['status'], 'error') self.assertEqual(response_data['status'], 'error')
def test_attempt_callback_timeout(self):
"""
Ensures that the polling from the client will cause the
server to transition to timed_out if the user runs out of time
"""
# Create an exam.
proctored_exam = ProctoredExam.objects.create(
course_id='a/b/c',
content_id='test_content',
exam_name='Test Exam',
external_id='123aXqe3',
time_limit_mins=90
)
attempt_data = {
'exam_id': proctored_exam.id,
'external_id': proctored_exam.external_id,
'start_clock': True,
}
response = self.client.post(
reverse('edx_proctoring.proctored_exam.attempt.collection'),
attempt_data
)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
attempt_id = response_data['exam_attempt_id']
self.assertEqual(attempt_id, 1)
response = self.client.get(
reverse('edx_proctoring.proctored_exam.attempt', args=[attempt_id])
)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
self.assertEqual(response_data['status'], 'started')
attempt_code = response_data['attempt_code']
# test the polling callback point
response = self.client.get(
reverse(
'edx_proctoring.anonymous.proctoring_poll_status',
args=[attempt_code]
)
)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
self.assertEqual(response_data['status'], 'started')
# set time to be in future
reset_time = datetime.now(pytz.UTC) + timedelta(minutes=180)
with freeze_time(reset_time):
# Now the callback should transition us away from started
response = self.client.get(
reverse(
'edx_proctoring.anonymous.proctoring_poll_status',
args=[attempt_code]
)
)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
self.assertEqual(response_data['status'], 'submitted')
def test_remove_attempt(self): def test_remove_attempt(self):
""" """
Confirms that an attempt can be removed Confirms that an attempt can be removed
......
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