Commit e01228a7 by Chris Dodge

allow for some rutime services to be registered with our subsystem, so we can…

allow for some rutime services to be registered with our subsystem, so we can get information in models that are outside of edx_proctoring
parent 85b670d9
......@@ -34,6 +34,7 @@ from edx_proctoring.serializers import (
from edx_proctoring.utils import humanized_time
from edx_proctoring.backends import get_backend_provider
from edx_proctoring.runtime import get_runtime_service
def is_feature_enabled():
......@@ -235,13 +236,24 @@ def create_exam_attempt(exam_id, user_id, taking_as_proctored=False):
)
)
# get the name of the user, if the service is available
full_name = None
profile_service = get_runtime_service('profile')
if profile_service:
profile = profile_service(user_id)
full_name = profile['name']
# now call into the backend provider to register exam attempt
external_id = get_backend_provider().register_exam_attempt(
exam,
allowed_time_limit_mins,
attempt_code,
False,
callback_url
context={
'time_limit_mins': allowed_time_limit_mins,
'attempt_code': attempt_code,
'is_sample_attempt': False,
'callback_url': callback_url,
'full_name': full_name,
}
)
attempt = ProctoredExamStudentAttempt.create_exam_attempt(
......
......@@ -14,8 +14,7 @@ class ProctoringBackendProvider(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def register_exam_attempt(self, exam, time_limit_mins, attempt_code,
is_sample_attempt, callback_url):
def register_exam_attempt(self, exam, context):
"""
Called when the exam attempt has been created but not started
"""
......
......@@ -10,8 +10,7 @@ class NullBackendProvider(ProctoringBackendProvider):
Implementation of the ProctoringBackendProvider that does nothing
"""
def register_exam_attempt(self, exam, time_limit_mins, attempt_code,
is_sample_attempt, callback_url):
def register_exam_attempt(self, exam, context):
"""
Called when the exam attempt has been created but not started
"""
......
......@@ -40,19 +40,17 @@ class SoftwareSecureBackendProvider(ProctoringBackendProvider):
self.timeout = 10
self.software_download_url = software_download_url
def register_exam_attempt(self, exam, time_limit_mins, attempt_code,
is_sample_attempt, callback_url):
def register_exam_attempt(self, exam, context):
"""
Method that is responsible for communicating with the backend provider
to establish a new proctored exam
"""
attempt_code = context['attempt_code']
data = self._get_payload(
exam,
time_limit_mins,
attempt_code,
is_sample_attempt,
callback_url
context
)
headers = {
"Content-Type": 'application/json'
......@@ -114,11 +112,25 @@ class SoftwareSecureBackendProvider(ProctoringBackendProvider):
encrypted_text = cipher.encrypt(pad(pwd))
return base64.b64encode(encrypted_text)
def _get_payload(self, exam, time_limit_mins, attempt_code,
is_sample_attempt, callback_url):
def _get_payload(self, exam, context):
"""
Constructs the data payload that Software Secure expects
"""
attempt_code = context['attempt_code']
time_limit_mins = context['time_limit_mins']
is_sample_attempt = context['is_sample_attempt']
callback_url = context['callback_url']
full_name = context['full_name']
first_name = ''
last_name = ''
if full_name:
name_elements = full_name.split(' ')
first_name = name_elements[0]
if len(name_elements) > 1:
last_name = ' '.join(name_elements[1:])
now = datetime.datetime.utcnow()
start_time_str = now.strftime("%a, %d %b %Y %H:%M:%S GMT")
end_time_str = (now + datetime.timedelta(minutes=time_limit_mins)).strftime("%a, %d %b %Y %H:%M:%S GMT")
......@@ -140,6 +152,8 @@ class SoftwareSecureBackendProvider(ProctoringBackendProvider):
"noOfStudents": 1,
"examID": exam['id'],
"courseID": exam['course_id'],
"firstName": first_name,
"lastName": last_name,
}
}
......
......@@ -12,8 +12,7 @@ class TestBackendProvider(ProctoringBackendProvider):
Implementation of the ProctoringBackendProvider that does nothing
"""
def register_exam_attempt(self, exam, time_limit_mins, attempt_code,
is_sample_attempt, callback_url):
def register_exam_attempt(self, exam, context):
"""
Called when the exam attempt has been created but not started
"""
......@@ -46,17 +45,13 @@ class PassthroughBackendProvider(ProctoringBackendProvider):
Implementation of the ProctoringBackendProvider that just calls the base class
"""
def register_exam_attempt(self, exam, time_limit_mins, attempt_code,
is_sample_attempt, callback_url):
def register_exam_attempt(self, exam, context):
"""
Called when the exam attempt has been created but not started
"""
return super(PassthroughBackendProvider, self).register_exam_attempt(
exam,
time_limit_mins,
attempt_code,
is_sample_attempt,
callback_url
context
)
def start_exam_attempt(self, exam, attempt):
......@@ -100,7 +95,7 @@ class TestBackends(TestCase):
provider = PassthroughBackendProvider()
with self.assertRaises(NotImplementedError):
provider.register_exam_attempt(None, None, None, None, None)
provider.register_exam_attempt(None, None)
with self.assertRaises(NotImplementedError):
provider.start_exam_attempt(None, None)
......@@ -118,7 +113,7 @@ class TestBackends(TestCase):
provider = NullBackendProvider()
self.assertIsNone(provider.register_exam_attempt(None, None, None, None, None))
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.get_software_download_url())
......@@ -8,13 +8,13 @@ import json
from django.test import TestCase
from django.contrib.auth.models import User
from edx_proctoring.runtime import set_runtime_service
from edx_proctoring.backends import get_backend_provider
from edx_proctoring.exceptions import BackendProvideCannotRegisterAttempt
from edx_proctoring.api import get_exam_attempt_by_id
from edx_proctoring.api import (
get_exam_attempt_by_id,
create_exam,
create_exam_attempt,
)
......@@ -71,6 +71,20 @@ class SoftwareSecureTests(TestCase):
self.user = User(username='foo', email='foo@bar.com')
self.user.save()
def mock_profile_service(user_id): # pylint: disable=unused-argument
"""
Mocked out Profile callback endpoint
"""
return {'name': 'Wolfgang von Strucker'}
set_runtime_service('profile', mock_profile_service)
def tearDown(self):
"""
When tests are done
"""
set_runtime_service('profile', None)
def test_provider_instance(self):
"""
Makes sure the instance of the proctoring module can be created
......@@ -108,6 +122,31 @@ class SoftwareSecureTests(TestCase):
self.assertEqual(attempt['external_id'], 'foobar')
self.assertIsNone(attempt['started_at'])
def test_single_name_attempt(self):
"""
Tests to make sure we can parse a fullname which does not have any spaces in it
"""
def mock_profile_service(user_id): # pylint: disable=unused-argument
"""
Mocked out Profile callback endpoint
"""
return {'name': 'Bono'}
set_runtime_service('profile', mock_profile_service)
exam_id = create_exam(
course_id='foo/bar/baz',
content_id='content',
exam_name='Sample Exam',
time_limit_mins=10,
is_proctored=True
)
with HTTMock(response_content):
attempt_id = create_exam_attempt(exam_id, self.user.id, taking_as_proctored=True)
self.assertIsNotNone(attempt_id)
def test_failing_register_attempt(self):
"""
Makes sure we can register an attempt
......
"""
Runtime services that the LMS can register than we can callback on
"""
_RUNTIME_SERVICES = {}
def set_runtime_service(name, callback):
"""
Adds a service provided by the runtime (aka LMS) to our directory
"""
_RUNTIME_SERVICES[name] = callback
def get_runtime_service(name):
"""
Returns a registered runtime service, None if no match is found
"""
return _RUNTIME_SERVICES.get(name)
......@@ -26,8 +26,12 @@
</div>
</div>
<div class="footer-sequence border-b-0 padding-b-0">
<span> {% trans "Note: As soon as you finish installing and setting up the proctoring software,
you will be prompted to start your timed exam." %} </span>
<span>
{% blocktrans %}
Note: As soon as you finish installing and setting up the proctoring software,
you will be prompted to start your timed exam.
{% endblocktrans %}
</span>
<p>
{% blocktrans %}
Be prepared to start your exam and to complete it while adhering to the {{platform_name}}
......
......@@ -3,7 +3,9 @@ Test for the xBlock service
"""
import unittest
from edx_proctoring.services import ProctoringService
from edx_proctoring.services import (
ProctoringService
)
from edx_proctoring import api as edx_proctoring_api
import types
......
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