Commit 7291ebec by Diana Huang

Create a mock backend that bypasses proctoring step.

parent d6e8ff15
"""
Implements a mock proctoring backend provider to be used for testing,
which doesn't require the setup and configuration of the Software Secure backend provider.
"""
import threading
from edx_proctoring.callbacks import start_exam_callback
from edx_proctoring.backends.backend import ProctoringBackendProvider
class MockProctoringBackendProvider(ProctoringBackendProvider):
"""
Implementation of the ProctoringBackendProvider that bypasses proctoring setup.
"""
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
def register_exam_attempt(self, exam, context):
"""
Called when the exam attempt has been created but not started
Args
exam (dict) - a dictionary containing information about the exam
keys:
exam_name (str) - the name of the exam
course_id (str) - serialized course key of the course that the exam belongs to
id (str) - the id of the given exam
context (dict) - a dictionary containing information about the current exam context
keys:
attempt_code (str) - code that represents this current attempt
time_limit_mins (str) - time limit of exam in minutes
is_sample_attempt (bool) - True if this is a practice exam
callback_url (str) - url that we would like the external grader to call back to
full_name (str) - full name of the student taking the exam
review_policy (dict) - the policy for reviewing this exam
review_policy_exception (dict) - an exceptions that may exist for this review
email (str) - the email address of the student
Returns
string that corresponds to the backend record locator of the attempt
"""
# Since the code expects this callback to be coming from the external software, we wait
# to call it so that the attempt is created properly before the callback is called.
timer = threading.Timer(1.0, start_exam_callback, args=(None, context['attempt_code']))
timer.start()
return context['attempt_code']
def start_exam_attempt(self, exam, attempt):
"""
Method that is responsible for communicating with the backend provider
to establish a new proctored exam
"""
return None
def stop_exam_attempt(self, exam, attempt):
"""
Method that is responsible for communicating with the backend provider
to establish a new proctored exam
"""
return None
def get_software_download_url(self):
"""
Returns
the URL that the user needs to go to in order to download
the corresponding desktop software
"""
return "mockurl"
def on_review_callback(self, payload):
"""
Called when the reviewing 3rd party service posts back the results
Args
payload (dict) -
the payload from the external service - is service dependent
Returns nothing
Side Effects
- updates review status
"""
return None
def on_review_saved(self, review):
"""
called when a review has been save - either through API or via Django Admin panel
in order to trigger any workflow
"""
return None
"""
Tests for backend.py
"""
import time
from mock import patch
from django.test import TestCase
from edx_proctoring.backends.backend import ProctoringBackendProvider
from edx_proctoring.backends.null import NullBackendProvider
from edx_proctoring.backends.mock import MockProctoringBackendProvider
class TestBackendProvider(ProctoringBackendProvider):
......@@ -149,3 +151,28 @@ class TestBackends(TestCase):
self.assertIsNone(provider.get_software_download_url())
self.assertIsNone(provider.on_review_callback(None))
self.assertIsNone(provider.on_review_saved(None))
def test_mock_provider(self):
"""
Test that the mock backend provider does what we expect it to do.
"""
provider = MockProctoringBackendProvider()
attempt_code = "test_code"
with patch('edx_proctoring.backends.mock.start_exam_callback') as exam_callback_mock:
exam_callback_mock.return_value = '5'
self.assertEqual(
attempt_code,
provider.register_exam_attempt(None, {'attempt_code': attempt_code})
)
# Wait for the thread to run.
time.sleep(2)
self.assertTrue(exam_callback_mock.called)
self.assertEqual(
"mockurl",
provider.get_software_download_url()
)
self.assertIsNone(provider.start_exam_attempt(None, None))
self.assertIsNone(provider.stop_exam_attempt(None, None))
self.assertIsNone(provider.on_review_callback(None))
self.assertIsNone(provider.on_review_saved(None))
......@@ -43,6 +43,7 @@ def start_exam_callback(request, attempt_code): # pylint: disable=unused-argume
attempt = get_exam_attempt_by_code(attempt_code)
if not attempt:
log.warn("Attempt code %r cannot be found.", attempt_code)
return HttpResponse(
content='You have entered an exam code that is not valid.',
status=404
......@@ -52,6 +53,7 @@ def start_exam_callback(request, attempt_code): # pylint: disable=unused-argume
ProctoredExamStudentAttemptStatus.download_software_clicked]:
mark_exam_attempt_as_ready(attempt['proctored_exam']['id'], attempt['user']['id'])
log.info("Exam %r has been marked as ready", attempt['proctored_exam']['id'])
template = loader.get_template('proctored_exam/proctoring_launch_callback.html')
poll_url = reverse(
......
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