diff --git a/edx_proctoring/api.py b/edx_proctoring/api.py index 5954eb9921d..9b58ba08e29 100644 --- a/edx_proctoring/api.py +++ b/edx_proctoring/api.py @@ -391,7 +391,8 @@ def _check_for_attempt_timeout(attempt): update_attempt_status( attempt['proctored_exam']['id'], attempt['user']['id'], - ProctoredExamStudentAttemptStatus.timed_out + ProctoredExamStudentAttemptStatus.timed_out, + timeout_timestamp=expires_at ) attempt = get_exam_attempt_by_id(attempt['id']) @@ -715,7 +716,8 @@ def mark_exam_attempt_as_ready(exam_id, user_id): return update_attempt_status(exam_id, user_id, ProctoredExamStudentAttemptStatus.ready_to_start) -def update_attempt_status(exam_id, user_id, to_status, raise_if_not_found=True, cascade_effects=True): +def update_attempt_status(exam_id, user_id, to_status, + raise_if_not_found=True, cascade_effects=True, timeout_timestamp=None): """ Internal helper to handle state transitions of attempt status """ @@ -730,11 +732,11 @@ def update_attempt_status(exam_id, user_id, to_status, raise_if_not_found=True, # In some configuration we may treat timeouts the same # as the user saying he/she wises to submit the exam - alias_timeout = ( + treat_timeout_as_submitted = ( to_status == ProctoredExamStudentAttemptStatus.timed_out and not settings.PROCTORING_SETTINGS.get('ALLOW_TIMED_OUT_STATE', False) ) - if alias_timeout: + if treat_timeout_as_submitted: to_status = ProctoredExamStudentAttemptStatus.submitted exam_attempt_obj = ProctoredExamStudentAttempt.objects.get_exam_attempt(exam_id, user_id) @@ -792,6 +794,8 @@ def update_attempt_status(exam_id, user_id, to_status, raise_if_not_found=True, ) if add_start_time: exam_attempt_obj.started_at = datetime.now(pytz.UTC) + elif treat_timeout_as_submitted: + exam_attempt_obj.completed_at = timeout_timestamp elif to_status == ProctoredExamStudentAttemptStatus.submitted: # likewise, when we transition to submitted mark # when the exam has been completed diff --git a/edx_proctoring/tests/test_api.py b/edx_proctoring/tests/test_api.py index c18f885d803..05507ebf9ef 100644 --- a/edx_proctoring/tests/test_api.py +++ b/edx_proctoring/tests/test_api.py @@ -943,17 +943,19 @@ def test_illegal_status_transition(self, from_status, to_status): to_status ) - def test_alias_timed_out(self): + def test_time_out_as_submitted(self): """ Verified that timed_out will automatically state transition to submitted """ exam_attempt = self._create_started_exam_attempt() + random_timestamp = datetime.now(pytz.UTC) - timedelta(hours=4) update_attempt_status( exam_attempt.proctored_exam_id, self.user.id, - ProctoredExamStudentAttemptStatus.timed_out + ProctoredExamStudentAttemptStatus.timed_out, + timeout_timestamp=random_timestamp ) exam_attempt = get_exam_attempt_by_id(exam_attempt.id) @@ -963,6 +965,37 @@ def test_alias_timed_out(self): ProctoredExamStudentAttemptStatus.submitted ) + self.assertEqual( + exam_attempt['completed_at'], + random_timestamp + ) + + @patch.dict('django.conf.settings.PROCTORING_SETTINGS', {'ALLOW_TIMED_OUT_STATE': True}) + def test_timeout_not_submitted(self): + """ + Test that when the setting is disabled, the status remains timed_out + """ + exam_attempt = self._create_started_exam_attempt() + random_timestamp = datetime.now(pytz.UTC) - timedelta(hours=4) + update_attempt_status( + exam_attempt.proctored_exam_id, + self.user.id, + ProctoredExamStudentAttemptStatus.timed_out, + timeout_timestamp=random_timestamp + ) + + exam_attempt = get_exam_attempt_by_id(exam_attempt.id) + + self.assertEqual( + exam_attempt['status'], + ProctoredExamStudentAttemptStatus.timed_out + ) + + self.assertNotEqual( + exam_attempt['completed_at'], + random_timestamp + ) + def test_update_unexisting_attempt(self): """ Tests updating an non-existing attempt