Commit f2c320fb by Andy Armstrong Committed by GitHub

Merge pull request #308 from edx/andya/clean-up-tests

Clean up the Python tests
parents 2356452f 1faad3ad
......@@ -53,4 +53,8 @@ coverage/
htmlcov/
acceptance_tests/*.png
node_modules/
\ No newline at end of file
node_modules/
# Devstack
edx-proctoring
edx_proctoring.egg-info
# coding=utf-8
"""
All tests for proctored exam emails.
"""
import ddt
from django.core import mail
from mock import patch
from edx_proctoring.api import (
update_attempt_status,
)
from edx_proctoring.models import (
ProctoredExamStudentAttemptStatus,
)
from edx_proctoring.runtime import set_runtime_service, get_runtime_service
from .test_services import (
MockCreditService,
)
from .utils import (
ProctoredExamTestCase,
)
@ddt.ddt
class ProctoredExamEmailTests(ProctoredExamTestCase):
"""
All tests for proctored exam emails.
"""
def setUp(self):
"""
Build out test harnessing
"""
super(ProctoredExamEmailTests, self).setUp()
# Messages for get_student_view
self.proctored_exam_email_subject = 'Proctoring Session Results Update'
self.proctored_exam_email_body = 'the status of your proctoring session review'
@ddt.data(
ProctoredExamStudentAttemptStatus.submitted,
ProctoredExamStudentAttemptStatus.verified,
ProctoredExamStudentAttemptStatus.rejected
)
def test_send_email(self, status):
"""
Assert that email is sent on the following statuses of proctoring attempt.
"""
exam_attempt = self._create_started_exam_attempt()
credit_state = get_runtime_service('credit').get_credit_state(self.user_id, self.course_id)
update_attempt_status(
exam_attempt.proctored_exam_id,
self.user.id,
status
)
self.assertEquals(len(mail.outbox), 1)
self.assertIn(self.proctored_exam_email_subject, mail.outbox[0].subject)
self.assertIn(self.proctored_exam_email_body, mail.outbox[0].body)
self.assertIn(ProctoredExamStudentAttemptStatus.get_status_alias(status), mail.outbox[0].body)
self.assertIn(credit_state['course_name'], mail.outbox[0].body)
@ddt.data(
ProctoredExamStudentAttemptStatus.second_review_required,
ProctoredExamStudentAttemptStatus.error
)
def test_email_not_sent(self, status):
"""
Assert than email is not sent on the following statuses of proctoring attempt
"""
exam_attempt = self._create_started_exam_attempt()
update_attempt_status(
exam_attempt.proctored_exam_id,
self.user.id,
status
)
self.assertEquals(len(mail.outbox), 0)
def test_send_email_unicode(self):
"""
Assert that email can be sent with a unicode course name.
"""
course_name = u'अआईउऊऋऌ अआईउऊऋऌ'
set_runtime_service('credit', MockCreditService(course_name=course_name))
exam_attempt = self._create_started_exam_attempt()
credit_state = get_runtime_service('credit').get_credit_state(self.user_id, self.course_id)
update_attempt_status(
exam_attempt.proctored_exam_id,
self.user.id,
ProctoredExamStudentAttemptStatus.submitted
)
self.assertEquals(len(mail.outbox), 1)
self.assertIn(self.proctored_exam_email_subject, mail.outbox[0].subject)
self.assertIn(course_name, mail.outbox[0].subject)
self.assertIn(self.proctored_exam_email_body, mail.outbox[0].body)
self.assertIn(
ProctoredExamStudentAttemptStatus.get_status_alias(
ProctoredExamStudentAttemptStatus.submitted
),
mail.outbox[0].body
)
self.assertIn(credit_state['course_name'], mail.outbox[0].body)
@ddt.data(
ProctoredExamStudentAttemptStatus.eligible,
ProctoredExamStudentAttemptStatus.created,
ProctoredExamStudentAttemptStatus.download_software_clicked,
ProctoredExamStudentAttemptStatus.ready_to_start,
ProctoredExamStudentAttemptStatus.started,
ProctoredExamStudentAttemptStatus.ready_to_submit,
ProctoredExamStudentAttemptStatus.declined,
ProctoredExamStudentAttemptStatus.timed_out,
ProctoredExamStudentAttemptStatus.error
)
@patch.dict('settings.PROCTORING_SETTINGS', {'ALLOW_TIMED_OUT_STATE': True})
def test_not_send_email(self, status):
"""
Assert that email is not sent on the following statuses of proctoring attempt.
"""
exam_attempt = self._create_started_exam_attempt()
update_attempt_status(
exam_attempt.proctored_exam_id,
self.user.id,
status
)
self.assertEquals(len(mail.outbox), 0)
@ddt.data(
ProctoredExamStudentAttemptStatus.submitted,
ProctoredExamStudentAttemptStatus.verified,
ProctoredExamStudentAttemptStatus.rejected
)
def test_not_send_email_sample_exam(self, status):
"""
Assert that email is not sent when there is practice/sample exam
"""
exam_attempt = self._create_started_exam_attempt(is_sample_attempt=True)
update_attempt_status(
exam_attempt.proctored_exam_id,
self.user.id,
status
)
self.assertEquals(len(mail.outbox), 0)
@ddt.data(
ProctoredExamStudentAttemptStatus.submitted,
ProctoredExamStudentAttemptStatus.verified,
ProctoredExamStudentAttemptStatus.rejected
)
def test_not_send_email_timed_exam(self, status):
"""
Assert that email is not sent when exam is timed/not-proctoring
"""
exam_attempt = self._create_started_exam_attempt(is_proctored=False)
update_attempt_status(
exam_attempt.proctored_exam_id,
self.user.id,
status
)
self.assertEquals(len(mail.outbox), 0)
# coding=utf-8
# pylint: disable=invalid-name
"""
Subclasses Django test client to allow for easy login
"""
from datetime import datetime
from importlib import import_module
import pytz
from django.conf import settings
from django.contrib.auth import login
......@@ -11,6 +16,22 @@ from django.test.client import Client
from django.test import TestCase
from django.contrib.auth.models import User
from edx_proctoring.api import (
create_exam,
)
from edx_proctoring.models import (
ProctoredExamStudentAttempt,
ProctoredExamStudentAttemptStatus,
)
from edx_proctoring.tests.test_services import (
MockCreditService,
MockInstructorService,
)
from edx_proctoring.runtime import set_runtime_service
from eventtracking import tracker
from eventtracking.tracker import Tracker, TRACKERS
class TestClient(Client):
"""
......@@ -64,3 +85,256 @@ class LoggedInTestCase(TestCase):
self.user = User(username='tester', email='tester@test.com')
self.user.save()
self.client.login_user(self.user)
class MockTracker(Tracker):
"""
A mocked out tracker which implements the emit method
"""
def emit(self, name=None, data=None):
"""
Overload this method to do nothing
"""
pass
class ProctoredExamTestCase(LoggedInTestCase):
"""
All tests for the models.py
"""
def setUp(self):
"""
Build out test harnessing
"""
super(ProctoredExamTestCase, self).setUp()
self.default_time_limit = 21
self.course_id = 'test_course'
self.content_id_for_exam_with_due_date = 'test_content_due_date_id'
self.content_id = 'test_content_id'
self.content_id_timed = 'test_content_id_timed'
self.content_id_practice = 'test_content_id_practice'
self.disabled_content_id = 'test_disabled_content_id'
self.exam_name = 'Test Exam'
self.user_id = self.user.id
self.key = 'additional_time_granted'
self.value = '10'
self.external_id = 'test_external_id'
self.proctored_exam_id = self._create_proctored_exam()
self.timed_exam_id = self._create_timed_exam()
self.practice_exam_id = self._create_practice_exam()
self.disabled_exam_id = self._create_disabled_exam()
set_runtime_service('credit', MockCreditService())
set_runtime_service('instructor', MockInstructorService(is_user_course_staff=True))
tracker.register_tracker(MockTracker())
self.prerequisites = [
{
'namespace': 'proctoring',
'name': 'proc1',
'order': 2,
'status': 'satisfied',
},
{
'namespace': 'reverification',
'name': 'rever1',
'order': 1,
'status': 'satisfied',
},
{
'namespace': 'grade',
'name': 'grade1',
'order': 0,
'status': 'pending',
},
{
'namespace': 'reverification',
'name': 'rever2',
'order': 3,
'status': 'failed',
},
{
'namespace': 'proctoring',
'name': 'proc2',
'order': 4,
'status': 'pending',
},
]
self.declined_prerequisites = [
{
'namespace': 'proctoring',
'name': 'proc1',
'order': 2,
'status': 'satisfied',
},
{
'namespace': 'reverification',
'name': 'rever1',
'order': 1,
'status': 'satisfied',
},
{
'namespace': 'grade',
'name': 'grade1',
'order': 0,
'status': 'pending',
},
{
'namespace': 'reverification',
'name': 'rever2',
'order': 3,
'status': 'declined',
},
{
'namespace': 'proctoring',
'name': 'proc2',
'order': 4,
'status': 'pending',
},
]
def tearDown(self):
"""
Cleanup
"""
del TRACKERS['default']
def _create_proctored_exam(self):
"""
Calls the api's create_exam to create an exam object.
"""
return create_exam(
course_id=self.course_id,
content_id=self.content_id,
exam_name=self.exam_name,
time_limit_mins=self.default_time_limit
)
def _create_exam_with_due_time(self, is_proctored=True, is_practice_exam=False, due_date=None):
"""
Calls the api's create_exam to create an exam object.
"""
return create_exam(
course_id=self.course_id,
content_id=self.content_id_for_exam_with_due_date,
exam_name=self.exam_name,
time_limit_mins=self.default_time_limit,
is_proctored=is_proctored,
is_practice_exam=is_practice_exam,
due_date=due_date
)
def _create_timed_exam(self):
"""
Calls the api's create_exam to create an exam object.
"""
return create_exam(
course_id=self.course_id,
content_id=self.content_id_timed,
exam_name=self.exam_name,
time_limit_mins=self.default_time_limit,
is_proctored=False
)
def _create_practice_exam(self):
"""
Calls the api's create_exam to create a practice exam object.
"""
return create_exam(
course_id=self.course_id,
content_id=self.content_id_practice,
exam_name=self.exam_name,
time_limit_mins=self.default_time_limit,
is_practice_exam=True,
is_proctored=True
)
def _create_disabled_exam(self):
"""
Calls the api's create_exam to create an exam object.
"""
return create_exam(
course_id=self.course_id,
is_proctored=False,
content_id=self.disabled_content_id,
exam_name=self.exam_name,
time_limit_mins=self.default_time_limit,
is_active=False
)
def _create_exam_attempt(self, exam_id, status='created'):
"""
Creates the ProctoredExamStudentAttempt object.
"""
attempt = ProctoredExamStudentAttempt(
proctored_exam_id=exam_id,
user_id=self.user_id,
external_id=self.external_id,
allowed_time_limit_mins=10,
status=status
)
if status in (ProctoredExamStudentAttemptStatus.started,
ProctoredExamStudentAttemptStatus.ready_to_submit, ProctoredExamStudentAttemptStatus.submitted):
attempt.started_at = datetime.now(pytz.UTC)
if ProctoredExamStudentAttemptStatus.is_completed_status(status):
attempt.completed_at = datetime.now(pytz.UTC)
attempt.save()
return attempt
def _create_unstarted_exam_attempt(self, is_proctored=True, is_practice=False):
"""
Creates the ProctoredExamStudentAttempt object.
"""
if is_proctored:
if is_practice:
exam_id = self.practice_exam_id
else:
exam_id = self.proctored_exam_id
else:
exam_id = self.timed_exam_id
return ProctoredExamStudentAttempt.objects.create(
proctored_exam_id=exam_id,
user_id=self.user_id,
external_id=self.external_id,
allowed_time_limit_mins=10,
status='created'
)
def _create_started_exam_attempt(self, started_at=None, is_proctored=True, is_sample_attempt=False):
"""
Creates the ProctoredExamStudentAttempt object.
"""
return ProctoredExamStudentAttempt.objects.create(
proctored_exam_id=self.proctored_exam_id if is_proctored else self.timed_exam_id,
user_id=self.user_id,
external_id=self.external_id,
started_at=started_at if started_at else datetime.now(pytz.UTC),
status=ProctoredExamStudentAttemptStatus.started,
allowed_time_limit_mins=10,
taking_as_proctored=is_proctored,
is_sample_attempt=is_sample_attempt
)
def _create_started_practice_exam_attempt(self, started_at=None):
"""
Creates the ProctoredExamStudentAttempt object.
"""
return ProctoredExamStudentAttempt.objects.create(
proctored_exam_id=self.practice_exam_id,
taking_as_proctored=True,
user_id=self.user_id,
external_id=self.external_id,
started_at=started_at if started_at else datetime.now(pytz.UTC),
is_sample_attempt=True,
status=ProctoredExamStudentAttemptStatus.started,
allowed_time_limit_mins=10
)
......@@ -5,6 +5,8 @@ djangorestframework>=3.1,<3.2
django-ipware==1.1.0
pytz>=2012h
pycrypto>=2.6
python-dateutil==2.1
# edX packages
-e git+https://github.com/edx/event-tracking.git@0.2.1#egg=event-tracking==0.2.1
-e git+https://github.com/edx/opaque-keys.git@27dc382ea587483b1e3889a3d19cbd90b9023a06#egg=opaque-keys
edx-opaque-keys==0.3.4
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