Commit 79da1289 by Afzal Wali

More tests for the start exam attempt API.

parent 6596441a
"""
Tests for the custom StrictBooleanField serializer used by the ProctoredExamSerializer
"""
import unittest
from edx_proctoring.serializers import ProctoredExamSerializer
class TestProctoredExamSerializer(unittest.TestCase):
"""
Tests for ProctoredExamSerializer
"""
def test_boolean_fields(self):
"""
Tests the boolean fields. Should cause a validation error in case a field is required.
"""
data = {
'course_id': "a/b/c",
'exam_name': "midterm1",
'content_id': '123aXqe0',
'time_limit_mins': 90,
'external_id': '123',
'is_proctored': 'bla',
'is_active': 'f'
}
serializer = ProctoredExamSerializer(data=data)
self.assertFalse(serializer.is_valid())
self.assertDictEqual(
{'is_proctored': [u'This field is required.']}, serializer.errors
)
......@@ -6,6 +6,7 @@ from django.test.client import Client
from django.core.urlresolvers import reverse, NoReverseMatch
from edx_proctoring.models import ProctoredExam
from edx_proctoring.views import require_staff
from django.contrib.auth.models import User
from .utils import (
LoggedInTestCase
......@@ -110,29 +111,77 @@ class ProctoredExamViewTests(LoggedInTestCase):
self.assertEqual(response_data['external_id'], exam_data['external_id'])
self.assertEqual(response_data['time_limit_mins'], exam_data['time_limit_mins'])
def test_get_exam_by_id(self):
def test_create_duplicate_exam(self):
"""
Tests the Get Exam by id endpoint
Tests the POST method error handling if a duplicate exam is created.
"""
exam_data = {
'course_id': "a/b/c",
'exam_name': "midterm1",
'content_id': '123aXqe0',
'time_limit_mins': 90,
'external_id': '123',
'is_proctored': True,
'is_active': True
}
response = self.client.post(
reverse('edx_proctoring.proctored_exam.exam'),
exam_data
)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
self.assertGreater(response_data['exam_id'], 0)
response = self.client.post(
reverse('edx_proctoring.proctored_exam.exam'),
exam_data
)
self.assertEqual(response.status_code, 400)
def test_update_existing_exam(self):
"""
Test the PUT method of the exam endpoint to update an existing exam.
"""
# Create an exam.
proctored_exam = ProctoredExam.objects.create(
course_id='test_course',
content_id='test_content',
course_id='a/b/c',
content_id='123aXqe0',
exam_name='Test Exam',
external_id='123aXqe3',
time_limit_mins=90
)
exam_id = proctored_exam.id
updated_exam_data = {
'exam_id': exam_id,
'exam_name': "midterm1",
'time_limit_mins': 90,
'external_id': '123',
'is_proctored': True,
'is_active': True
}
response = self.client.put(
reverse('edx_proctoring.proctored_exam.exam'),
updated_exam_data
)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
self.assertEqual(response_data['exam_id'], exam_id)
# Now lookup the exam by giving the exam_id returned and match the data.
response = self.client.get(
reverse('edx_proctoring.proctored_exam.exam_by_id', kwargs={'exam_id': proctored_exam.id})
reverse('edx_proctoring.proctored_exam.exam_by_id', kwargs={'exam_id': response_data['exam_id']})
)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
self.assertEqual(response_data['course_id'], proctored_exam.course_id)
self.assertEqual(response_data['exam_name'], proctored_exam.exam_name)
self.assertEqual(response_data['content_id'], proctored_exam.content_id)
self.assertEqual(response_data['external_id'], proctored_exam.external_id)
self.assertEqual(response_data['time_limit_mins'], proctored_exam.time_limit_mins)
self.assertEqual(response_data['exam_name'], updated_exam_data['exam_name'])
self.assertEqual(response_data['external_id'], updated_exam_data['external_id'])
self.assertEqual(response_data['time_limit_mins'], updated_exam_data['time_limit_mins'])
def test_decorator_staff_user(self):
"""
......@@ -154,3 +203,213 @@ class ProctoredExamViewTests(LoggedInTestCase):
self.user.save()
request.user = self.user
return request
def test_update_non_existing_exam(self):
"""
Test the PUT method of the exam endpoint to update an existing exam.
In case the exam_id is not found, it should return a bad request.
"""
exam_id = 99999
updated_exam_data = {
'exam_id': exam_id,
'exam_name': "midterm1",
'time_limit_mins': 90,
'external_id': '123',
'is_proctored': True,
'is_active': True
}
response = self.client.put(
reverse('edx_proctoring.proctored_exam.exam'),
updated_exam_data
)
self.assertEqual(response.status_code, 400)
response_data = json.loads(response.content)
self.assertEqual(response_data, {'detail': 'The exam_id does not exist.'})
def test_get_exam_by_id(self):
"""
Tests the Get Exam by id endpoint
"""
# Create an exam.
proctored_exam = ProctoredExam.objects.create(
course_id='test_course',
content_id='test_content',
exam_name='Test Exam',
external_id='123aXqe3',
time_limit_mins=90
)
response = self.client.get(
reverse('edx_proctoring.proctored_exam.exam_by_id', kwargs={'exam_id': proctored_exam.id})
)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
self.assertEqual(response_data['course_id'], proctored_exam.course_id)
self.assertEqual(response_data['exam_name'], proctored_exam.exam_name)
self.assertEqual(response_data['content_id'], proctored_exam.content_id)
self.assertEqual(response_data['external_id'], proctored_exam.external_id)
self.assertEqual(response_data['time_limit_mins'], proctored_exam.time_limit_mins)
def test_get_exam_by_bad_id(self):
"""
Tests the Get Exam by id endpoint
"""
# Create an exam.
response = self.client.get(
reverse('edx_proctoring.proctored_exam.exam_by_id', kwargs={'exam_id': 99999})
)
self.assertEqual(response.status_code, 400)
response_data = json.loads(response.content)
self.assertEqual(response_data['detail'], 'The exam_id does not exist.')
def test_get_exam_by_content_id(self):
"""
Tests the Get Exam by content id endpoint
"""
# Create an exam.
proctored_exam = ProctoredExam.objects.create(
course_id='a/b/c',
content_id='test_content',
exam_name='Test Exam',
external_id='123aXqe3',
time_limit_mins=90
)
response = self.client.get(
reverse('edx_proctoring.proctored_exam.exam_by_content_id', kwargs={
'course_id': proctored_exam.course_id,
'content_id': proctored_exam.content_id
})
)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
self.assertEqual(response_data['course_id'], proctored_exam.course_id)
self.assertEqual(response_data['exam_name'], proctored_exam.exam_name)
self.assertEqual(response_data['content_id'], proctored_exam.content_id)
self.assertEqual(response_data['external_id'], proctored_exam.external_id)
self.assertEqual(response_data['time_limit_mins'], proctored_exam.time_limit_mins)
def test_get_exam_by_bad_content_id(self):
"""
Tests the Get Exam by content id endpoint
"""
# Create an exam.
proctored_exam = ProctoredExam.objects.create(
course_id='a/b/c',
content_id='test_content',
exam_name='Test Exam',
external_id='123aXqe3',
time_limit_mins=90
)
response = self.client.get(
reverse('edx_proctoring.proctored_exam.exam_by_content_id', kwargs={
'course_id': 'c/d/e',
'content_id': proctored_exam.content_id
})
)
self.assertEqual(response.status_code, 400)
response_data = json.loads(response.content)
self.assertEqual(response_data['detail'], 'The exam with course_id, content_id does not exist.')
def test_get_exam_insufficient_args(self):
"""
Tests the Get Exam by content id endpoint
"""
# Create an exam.
proctored_exam = ProctoredExam.objects.create(
course_id='a/b/c',
content_id='test_content',
exam_name='Test Exam',
external_id='123aXqe3',
time_limit_mins=90
)
response = self.client.get(
reverse('edx_proctoring.proctored_exam.exam_by_content_id', kwargs={
'course_id': proctored_exam.course_id,
'content_id': proctored_exam.content_id
})
)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
self.assertEqual(response_data['course_id'], proctored_exam.course_id)
self.assertEqual(response_data['exam_name'], proctored_exam.exam_name)
self.assertEqual(response_data['content_id'], proctored_exam.content_id)
self.assertEqual(response_data['external_id'], proctored_exam.external_id)
self.assertEqual(response_data['time_limit_mins'], proctored_exam.time_limit_mins)
class TestStudentProctoredExamAttempt(LoggedInTestCase):
"""
Tests for the StudentProctoredExamAttempt
"""
def setUp(self):
super(TestStudentProctoredExamAttempt, self).setUp()
self.user.is_staff = True
self.user.save()
self.client.login_user(self.user)
self.student_taking_exam = User()
self.student_taking_exam.save()
def test_start_exam_attempt(self):
"""
Start an exam (create an exam attempt)
"""
# Create an exam.
proctored_exam = ProctoredExam.objects.create(
course_id='a/b/c',
content_id='test_content',
exam_name='Test Exam',
external_id='123aXqe3',
time_limit_mins=90
)
attempt_data = {
'exam_id': proctored_exam.id,
'user_id': self.student_taking_exam.id,
'external_id': proctored_exam.external_id
}
response = self.client.post(
reverse('edx_proctoring.proctored_exam.attempt'),
attempt_data
)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
self.assertGreater(response_data['exam_attempt_id'], 0)
def test_restart_exam_attempt(self):
"""
Start an exam that has already started should raise error.
"""
# Create an exam.
proctored_exam = ProctoredExam.objects.create(
course_id='a/b/c',
content_id='test_content',
exam_name='Test Exam',
external_id='123aXqe3',
time_limit_mins=90
)
attempt_data = {
'exam_id': proctored_exam.id,
'user_id': self.student_taking_exam.id,
'external_id': proctored_exam.external_id
}
response = self.client.post(
reverse('edx_proctoring.proctored_exam.attempt'),
attempt_data
)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
self.assertGreater(response_data['exam_attempt_id'], 0)
response = self.client.post(
reverse('edx_proctoring.proctored_exam.attempt'),
attempt_data
)
self.assertEqual(response.status_code, 400)
response_data = json.loads(response.content)
self.assertEqual(response_data['detail'], 'Error. Trying to start an exam that has already started.')
......@@ -19,7 +19,7 @@ urlpatterns = patterns( # pylint: disable=invalid-name
name='edx_proctoring.proctored_exam.exam_by_id'
),
url(
r'edx_proctoring/v1/proctored_exam/exam/course_id/{}/content_id/(?P<content_id>\d+)$'.format(
r'edx_proctoring/v1/proctored_exam/exam/course_id/{}/content_id/(?P<content_id>[A-z0-9]+)$'.format(
settings.COURSE_ID_PATTERN),
views.ProctoredExamView.as_view(),
name='edx_proctoring.proctored_exam.exam_by_content_id'
......
......@@ -8,7 +8,7 @@ from rest_framework import status
from rest_framework.response import Response
from edx_proctoring.api import create_exam, update_exam, get_exam_by_id, get_exam_by_content_id, start_exam_attempt, \
stop_exam_attempt, add_allowance_for_user, remove_allowance_for_user, get_active_exams_for_user
from edx_proctoring.exceptions import ProctoredExamNotFoundException, ProctoredExamAlreadyExists, \
from edx_proctoring.exceptions import ProctoredExamNotFoundException, \
StudentExamAttemptAlreadyExistsException, StudentExamAttemptDoesNotExistsException
from edx_proctoring.serializers import ProctoredExamSerializer
......@@ -101,22 +101,16 @@ class ProctoredExamView(AuthenticatedAPIView):
"""
serializer = ProctoredExamSerializer(data=request.DATA)
if serializer.is_valid():
try:
exam_id = create_exam(
course_id=request.DATA.get('course_id', None),
content_id=request.DATA.get('content_id', None),
exam_name=request.DATA.get('exam_name', None),
time_limit_mins=request.DATA.get('time_limit_mins', None),
is_proctored=request.DATA.get('is_proctored', None),
external_id=request.DATA.get('external_id', None),
is_active=request.DATA.get('is_active', None)
)
return Response({'exam_id': exam_id})
except ProctoredExamAlreadyExists:
return Response(
status=status.HTTP_400_BAD_REQUEST,
data={"detail": "Error Trying to create a duplicate exam."}
)
exam_id = create_exam(
course_id=request.DATA.get('course_id', None),
content_id=request.DATA.get('content_id', None),
exam_name=request.DATA.get('exam_name', None),
time_limit_mins=request.DATA.get('time_limit_mins', None),
is_proctored=request.DATA.get('is_proctored', None),
external_id=request.DATA.get('external_id', None),
is_active=request.DATA.get('is_active', None)
)
return Response({'exam_id': exam_id})
else:
return Response(
status=status.HTTP_400_BAD_REQUEST,
......@@ -131,12 +125,12 @@ class ProctoredExamView(AuthenticatedAPIView):
"""
try:
exam_id = update_exam(
exam_id=request.DATA.get('exam_id', ""),
exam_name=request.DATA.get('exam_name', ""),
time_limit_mins=request.DATA.get('time_limit_mins', ""),
is_proctored=request.DATA.get('is_proctored', False),
external_id=request.DATA.get('external_id', ""),
is_active=request.DATA.get('is_active', False),
exam_id=request.DATA.get('exam_id', None),
exam_name=request.DATA.get('exam_name', None),
time_limit_mins=request.DATA.get('time_limit_mins', None),
is_proctored=request.DATA.get('is_proctored', None),
external_id=request.DATA.get('external_id', None),
is_active=request.DATA.get('is_active', None),
)
return Response({'exam_id': exam_id})
except ProctoredExamNotFoundException:
......@@ -176,18 +170,16 @@ class ProctoredExamView(AuthenticatedAPIView):
status=status.HTTP_400_BAD_REQUEST,
data={"detail": "The exam with course_id, content_id does not exist."}
)
else:
return Response(
status=status.HTTP_400_BAD_REQUEST,
data={"detail": "Bad input data."}
)
class StudentProctoredExamAttempt(AuthenticatedAPIView):
"""
Endpoint for the StudentProctoredExamAttempt
/edx_proctoring/v1/proctored_exam/exam
/edx_proctoring/v1/proctored_exam/attempt
Supports:
HTTP POST: Starts an exam attempt.
HTTP PUT: Stops an exam attempt.
HTTP GET: Returns the status of an exam attempt.
"""
def get(self, request): # pylint: disable=unused-argument
......@@ -217,9 +209,9 @@ class StudentProctoredExamAttempt(AuthenticatedAPIView):
"""
try:
exam_attempt_id = start_exam_attempt(
exam_id=request.DATA.get('exam_id', ""),
user_id=request.DATA.get('user_id', ""),
external_id=request.DATA.get('external_id', "")
exam_id=request.DATA.get('exam_id', None),
user_id=request.DATA.get('user_id', None),
external_id=request.DATA.get('external_id', None)
)
return Response({'exam_attempt_id': exam_attempt_id})
......@@ -269,7 +261,7 @@ class ExamAllowanceView(AuthenticatedAPIView):
value=request.DATA.get('value', "")
))
@require_staff
@method_decorator(require_staff)
def delete(self, request):
"""
HTTP DELETE handler. Removes Allowance.
......
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