Skip to content

Commit

Permalink
Merge pull request #508 from edx/matthugs/expose-proctoring-error-state
Browse files Browse the repository at this point in the history
Expose error state to proctoring rest API implementers
  • Loading branch information
matthugs committed Jan 16, 2019
2 parents 9b99d84 + 647636a commit 498a0eb
Show file tree
Hide file tree
Showing 12 changed files with 101 additions and 22 deletions.
2 changes: 1 addition & 1 deletion docs/backends.rst
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ The PS system should respond with an object containing at least the following fi
"status": "submitted",
}

Open edX will issue a ``PATCH`` request with a ``started`` status when the learner starts the proctored exam, and a ``submitted`` status when the learner finishes the exam.
Open edX will issue a ``PATCH`` request with a ``started`` status when the learner starts the proctored exam, and a ``submitted`` status when the learner finishes the exam. A status of ``error`` may be used in case of a technical error being associated with a learner's proctoring session.

``GET``: returns PS information about the attempt

Expand Down
2 changes: 1 addition & 1 deletion edx_proctoring/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
from __future__ import absolute_import

# Be sure to update the version number in edx_proctoring/package.json
__version__ = '1.5.5'
__version__ = '1.5.6'

default_app_config = 'edx_proctoring.apps.EdxProctoringConfig' # pylint: disable=invalid-name
13 changes: 9 additions & 4 deletions edx_proctoring/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1034,11 +1034,16 @@ def update_attempt_status(exam_id, user_id, to_status,
if backend:
# only proctored exams have a backend
# timed exams have no backend
backend_method = None
if to_status == ProctoredExamStudentAttemptStatus.started:
backend.start_exam_attempt(exam['external_id'], attempt['external_id'])
if to_status == ProctoredExamStudentAttemptStatus.submitted:
backend.stop_exam_attempt(exam['external_id'], attempt['external_id'])
# we user the 'status' field as the name of the event 'verb'
backend_method = backend.start_exam_attempt
elif to_status == ProctoredExamStudentAttemptStatus.submitted:
backend_method = backend.stop_exam_attempt
elif to_status == ProctoredExamStudentAttemptStatus.error:
backend_method = backend.mark_erroneous_exam_attempt
if backend_method:
backend_method(exam['external_id'], attempt['external_id'])
# we use the 'status' field as the name of the event 'verb'
emit_event(exam, attempt['status'], attempt=attempt)

return attempt['id']
Expand Down
8 changes: 8 additions & 0 deletions edx_proctoring/backends/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ def stop_exam_attempt(self, exam, attempt):
"""
raise NotImplementedError()

@abc.abstractmethod
def mark_erroneous_exam_attempt(self, exam, attempt):
"""
Method that is responsible for communicating with the backend provider
to establish a new proctored exam
"""
raise NotImplementedError()

@abc.abstractmethod
def get_software_download_url(self):
"""
Expand Down
8 changes: 8 additions & 0 deletions edx_proctoring/backends/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ def stop_exam_attempt(self, exam, attempt):
"""
return None

def mark_erroneous_exam_attempt(self, exam, attempt):
"""
Method that would be responsible for communicating with the
backend provider to mark a proctored session as having
encountered a technical error
"""
return None

def get_software_download_url(self):
"""
Returns
Expand Down
8 changes: 8 additions & 0 deletions edx_proctoring/backends/null.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ def stop_exam_attempt(self, exam, attempt):
"""
return None

def mark_erroneous_exam_attempt(self, exam, attempt):
"""
Method that would be responsible for communicating with the
backend provider to mark a proctored session as having
encountered a technical error
"""
return None

def get_software_download_url(self):
"""
Returns the URL that the user needs to go to in order to download
Expand Down
12 changes: 12 additions & 0 deletions edx_proctoring/backends/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,18 @@ def stop_exam_attempt(self, exam, attempt):
method='PATCH')
return response.get('status')

def mark_erroneous_exam_attempt(self, exam, attempt):
"""
Method that is responsible for communicating with the backend provider
to mark an unfinished exam to be in error
"""
response = self._make_attempt_request(
exam,
attempt,
status=ProctoredExamStudentAttemptStatus.error,
method='PATCH')
return response.get('status')

def on_review_callback(self, attempt, payload):
"""
Called when the reviewing 3rd party service posts back the results
Expand Down
8 changes: 8 additions & 0 deletions edx_proctoring/backends/software_secure.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ def stop_exam_attempt(self, exam, attempt):
"""
return None

def mark_erroneous_exam_attempt(self, exam, attempt):
"""
Method that would be responsible for communicating with the
backend provider to mark a proctored session as having
encountered a technical error
"""
return None

def get_software_download_url(self):
"""
Returns the URL that the user needs to go to in order to download
Expand Down
24 changes: 24 additions & 0 deletions edx_proctoring/backends/tests/test_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ def stop_exam_attempt(self, exam, attempt):
"""
return None

def mark_erroneous_exam_attempt(self, exam, attempt):
"""
Method that would be responsible for communicating with the
backend provider to mark a proctored session as having
encountered a technical error
"""
return None

def get_software_download_url(self):
"""
Returns the URL that the user needs to go to in order to download
Expand Down Expand Up @@ -111,6 +119,17 @@ def stop_exam_attempt(self, exam, attempt):
attempt
)

def mark_erroneous_exam_attempt(self, exam, attempt):
"""
Method that would be responsible for communicating with the
backend provider to mark a proctored session as having
encountered a technical error
"""
return super(PassthroughBackendProvider, self).mark_erroneous_exam_attempt(
exam,
attempt
)

def get_software_download_url(self):
"""
Returns the URL that the user needs to go to in order to download
Expand Down Expand Up @@ -155,6 +174,9 @@ def test_raises_exception(self):
with self.assertRaises(NotImplementedError):
provider.on_review_callback(None, None)

with self.assertRaises(NotImplementedError):
provider.mark_erroneous_exam_attempt(None, None)

with self.assertRaises(NotImplementedError):
provider.on_exam_saved(None)

Expand All @@ -170,6 +192,7 @@ def test_null_provider(self):
self.assertIsNone(provider.register_exam_attempt(None, None))
self.assertIsNone(provider.start_exam_attempt(None, None))
self.assertIsNone(provider.stop_exam_attempt(None, None))
self.assertIsNone(provider.mark_erroneous_exam_attempt(None, None))
self.assertIsNone(provider.get_software_download_url())
self.assertIsNone(provider.on_review_callback(None, None))
self.assertIsNone(provider.on_exam_saved(None))
Expand All @@ -196,6 +219,7 @@ def test_mock_provider(self):
)
self.assertIsNone(provider.start_exam_attempt(None, None))
self.assertIsNone(provider.stop_exam_attempt(None, None))
self.assertIsNone(provider.mark_erroneous_exam_attempt(None, None))
self.assertIsNone(provider.on_review_callback(None, None))
self.assertIsNone(provider.on_exam_saved(None))

Expand Down
27 changes: 12 additions & 15 deletions edx_proctoring/backends/tests/test_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""
import json

import ddt
import jwt

import responses
Expand All @@ -16,6 +17,7 @@
from edx_proctoring.exceptions import BackendProviderCannotRegisterAttempt


@ddt.ddt
class RESTBackendTests(TestCase):
"""
Tests for the REST backend
Expand Down Expand Up @@ -197,27 +199,22 @@ def test_register_exam_attempt_failure(self):
with self.assertRaises(BackendProviderCannotRegisterAttempt):
self.provider.register_exam_attempt(self.backend_exam, context)

@ddt.data(
['start_exam_attempt', 'start'],
['stop_exam_attempt', 'stop'],
['mark_erroneous_exam_attempt', 'error'],
)
@ddt.unpack
@responses.activate
def test_start_exam_attempt(self):
def test_update_exam_attempt_status(self, provider_method_name, corresponding_status):
attempt_id = 2
responses.add(
responses.PATCH,
url=self.provider.exam_attempt_url.format(exam_id=self.backend_exam['external_id'], attempt_id=attempt_id),
json={'id': 2, 'status': 'start'}
json={'id': 2, 'status': corresponding_status}
)
status = self.provider.start_exam_attempt(self.backend_exam['external_id'], attempt_id)
self.assertEqual(status, 'start')

@responses.activate
def test_stop_exam_attempt(self):
attempt_id = 2
responses.add(
responses.PATCH,
url=self.provider.exam_attempt_url.format(exam_id=self.backend_exam['external_id'], attempt_id=attempt_id),
json={'id': 2, 'status': 'stop'}
)
status = self.provider.stop_exam_attempt(self.backend_exam['external_id'], attempt_id)
self.assertEqual(status, 'stop')
status = getattr(self.provider, provider_method_name)(self.backend_exam['external_id'], attempt_id)
self.assertEqual(status, corresponding_status)

def test_on_review_callback(self):
"""
Expand Down
9 changes: 9 additions & 0 deletions edx_proctoring/backends/tests/test_software_secure.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,15 @@ def test_stop_proctored_exam(self):
provider = get_backend_provider()
self.assertIsNone(provider.stop_exam_attempt(None, None))

def test_mark_erroneous_proctored_exam(self):
"""
Test that SoftwareSecure's implementation returns None, because there is no
work that needs to happen right now
"""

provider = get_backend_provider()
self.assertIsNone(provider.mark_erroneous_exam_attempt(None, None))

def test_split_fullname(self):
"""
Make sure we are splitting up full names correctly
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@edx/edx-proctoring",
"//": "Be sure to update the version number in edx_proctoring/__init__.py",
"//": "Note that the version format is slightly different than that of the Python version when using prereleases.",
"version": "1.5.5",
"version": "1.5.6",
"main": "edx_proctoring/static/index.js",
"repository": {
"type": "git",
Expand Down

0 comments on commit 498a0eb

Please sign in to comment.