Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions lms/djangoapps/instructor/tests/views/test_special_exams_api_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
create_exam_attempt,
get_allowances_for_course,
)
from edx_proctoring.exceptions import ProctoredBaseException
from edx_proctoring.models import ProctoredExamStudentAttempt
from rest_framework import status
from rest_framework.test import APIClient
Expand Down Expand Up @@ -217,6 +218,30 @@ def test_reset_no_attempts(self):
response = self.client.post(self._url())
assert response.status_code == status.HTTP_404_NOT_FOUND

def test_reset_provider_unavailable_returns_descriptive_error(self):
"""
When removing the attempt raises a ProctoredBaseException (e.g. the proctoring
provider is unavailable), the view surfaces the exception's HTTP status and
message so the instructor dashboard shows a descriptive error rather than a 500.
"""
create_exam_attempt(self.exam_id, self.student.id)
message = (
'The proctoring provider is temporarily unavailable, so this attempt '
'could not be fully reset. Please try again in a few minutes.'
)
# Simulate a proctoring-provider failure during removal (e.g. edx-proctoring's
# BackendProviderCannotRemoveAttempt), which resolves to a 502. Using a base
# ProctoredBaseException keeps the test independent of the edx-proctoring release.
provider_error = ProctoredBaseException(message)
provider_error.http_status = status.HTTP_502_BAD_GATEWAY
with patch(
'lms.djangoapps.instructor.views.api_v2.remove_exam_attempt',
side_effect=provider_error,
):
response = self.client.post(self._url())
assert response.status_code == status.HTTP_502_BAD_GATEWAY
assert response.json()['detail'] == message


@override_settings(**PROCTORING_SETTINGS)
@override_settings(ENABLE_SPECIAL_EXAMS=True)
Expand Down
11 changes: 9 additions & 2 deletions lms/djangoapps/instructor/views/api_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -4270,8 +4270,15 @@ def post(self, request, course_id, exam_id, username):
status=status.HTTP_404_NOT_FOUND,
)

for attempt in user_attempts:
remove_exam_attempt(attempt['id'], requesting_user=request.user)
try:
for attempt in user_attempts:
remove_exam_attempt(attempt['id'], requesting_user=request.user)
except ProctoredBaseException as err:
# The proctoring provider (or another proctoring-layer error) prevented the
# attempt from being removed. Surface the typed exception's status and message
# (e.g. a 502 when the provider is unavailable) so the instructor dashboard can
# display a descriptive error instead of a generic 500.
return Response({'detail': str(err)}, status=err.http_status)

return Response(
{'success': True, 'message': f'Exam attempt reset for user {username}'},
Expand Down
Loading