diff --git a/edx_exams/apps/api/v1/tests/test_views.py b/edx_exams/apps/api/v1/tests/test_views.py index 1dafc6c7..da14d1c3 100644 --- a/edx_exams/apps/api/v1/tests/test_views.py +++ b/edx_exams/apps/api/v1/tests/test_views.py @@ -1121,7 +1121,7 @@ def test_put_attempt_does_not_exist(self): ) @ddt.unpack @patch('edx_exams.apps.api.v1.views.update_attempt_status') - def test_put_update_exam_attempt(self, action, expected_status, mock_update_attempt_status): + def test_put_learner_update_exam_attempt(self, action, expected_status, mock_update_attempt_status): """ Test that an exam can be updated """ @@ -1137,6 +1137,41 @@ def test_put_update_exam_attempt(self, action, expected_status, mock_update_atte self.assertEqual(response.status_code, 200) mock_update_attempt_status.assert_called_once_with(attempt.id, expected_status) + @ddt.data( + ('verify', ExamAttemptStatus.verified), + ('reject', ExamAttemptStatus.rejected), + ) + @ddt.unpack + @patch('edx_exams.apps.api.v1.views.update_attempt_status') + def test_put_staff_update_exam_attempt(self, action, expected_status, mock_update_attempt_status): + """ + Test staff/instructor updates + """ + # create exam attempt for user + attempt = ExamAttemptFactory( + user=self.non_staff_user, + exam=self.exam, + ) + + mock_update_attempt_status.return_value = attempt.id + + response = self.put_api(self.staff_user, attempt.id, {'action': action}) + self.assertEqual(response.status_code, 200) + mock_update_attempt_status.assert_called_once_with(attempt.id, expected_status) + + def test_put_learner_verify(self): + """ + Test that a learner account cannot verify an attempt + """ + # create exam attempt for user + attempt = ExamAttemptFactory( + user=self.non_staff_user, + exam=self.exam, + ) + + response = self.put_api(self.non_staff_user, attempt.id, {'action': 'verify'}) + self.assertEqual(response.status_code, 400) + @patch('edx_exams.apps.api.v1.views.update_attempt_status') def test_put_exception_raised(self, mock_update_attempt_status): """ diff --git a/edx_exams/apps/api/v1/views.py b/edx_exams/apps/api/v1/views.py index 271cf7ca..0747a671 100644 --- a/edx_exams/apps/api/v1/views.py +++ b/edx_exams/apps/api/v1/views.py @@ -535,8 +535,22 @@ def put(self, request, attempt_id): data={'detail': f'Attempt with attempt_id={attempt_id} does not exit.'} ) - # user should only be able to update their own attempt - if attempt.user.id != request.user.id: + action_mapping = {} + if request.user.is_staff: + action_mapping = { + 'verify': ExamAttemptStatus.verified, + 'reject': ExamAttemptStatus.rejected, + } + # instructors/staff cannot take exams so they do not need these actions + elif attempt.user.id == request.user.id: + action_mapping = { + 'stop': ExamAttemptStatus.ready_to_submit, + 'start': ExamAttemptStatus.started, + 'submit': ExamAttemptStatus.submitted, + 'click_download_software': ExamAttemptStatus.download_software_clicked, + 'error': ExamAttemptStatus.error, + } + else: error_msg = ( f'user_id={attempt.user.id} attempted to update attempt_id={attempt.id} in ' f'course_id={attempt.exam.course_id} but does not have access to it. (action={action})' @@ -544,14 +558,6 @@ def put(self, request, attempt_id): error = {'detail': error_msg} return Response(status=status.HTTP_403_FORBIDDEN, data=error) - action_mapping = { - 'stop': ExamAttemptStatus.ready_to_submit, - 'start': ExamAttemptStatus.started, - 'submit': ExamAttemptStatus.submitted, - 'click_download_software': ExamAttemptStatus.download_software_clicked, - 'error': ExamAttemptStatus.error, - } - to_status = action_mapping.get(action) if to_status: attempt_id = update_attempt_status(attempt_id, to_status)