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 ...@@ -6,6 +6,7 @@ from django.test.client import Client
from django.core.urlresolvers import reverse, NoReverseMatch from django.core.urlresolvers import reverse, NoReverseMatch
from edx_proctoring.models import ProctoredExam from edx_proctoring.models import ProctoredExam
from edx_proctoring.views import require_staff from edx_proctoring.views import require_staff
from django.contrib.auth.models import User
from .utils import ( from .utils import (
LoggedInTestCase LoggedInTestCase
...@@ -110,29 +111,77 @@ class ProctoredExamViewTests(LoggedInTestCase): ...@@ -110,29 +111,77 @@ class ProctoredExamViewTests(LoggedInTestCase):
self.assertEqual(response_data['external_id'], exam_data['external_id']) self.assertEqual(response_data['external_id'], exam_data['external_id'])
self.assertEqual(response_data['time_limit_mins'], exam_data['time_limit_mins']) 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( proctored_exam = ProctoredExam.objects.create(
course_id='test_course', course_id='a/b/c',
content_id='test_content', content_id='123aXqe0',
exam_name='Test Exam', exam_name='Test Exam',
external_id='123aXqe3', external_id='123aXqe3',
time_limit_mins=90 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( 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) self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content) response_data = json.loads(response.content)
self.assertEqual(response_data['course_id'], proctored_exam.course_id) 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['content_id'], proctored_exam.content_id)
self.assertEqual(response_data['external_id'], proctored_exam.external_id) self.assertEqual(response_data['exam_name'], updated_exam_data['exam_name'])
self.assertEqual(response_data['time_limit_mins'], proctored_exam.time_limit_mins) 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): def test_decorator_staff_user(self):
""" """
...@@ -154,3 +203,213 @@ class ProctoredExamViewTests(LoggedInTestCase): ...@@ -154,3 +203,213 @@ class ProctoredExamViewTests(LoggedInTestCase):
self.user.save() self.user.save()
request.user = self.user request.user = self.user
return request 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 ...@@ -19,7 +19,7 @@ urlpatterns = patterns( # pylint: disable=invalid-name
name='edx_proctoring.proctored_exam.exam_by_id' name='edx_proctoring.proctored_exam.exam_by_id'
), ),
url( 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), settings.COURSE_ID_PATTERN),
views.ProctoredExamView.as_view(), views.ProctoredExamView.as_view(),
name='edx_proctoring.proctored_exam.exam_by_content_id' name='edx_proctoring.proctored_exam.exam_by_content_id'
......
...@@ -8,7 +8,7 @@ from rest_framework import status ...@@ -8,7 +8,7 @@ from rest_framework import status
from rest_framework.response import Response 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, \ 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 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 StudentExamAttemptAlreadyExistsException, StudentExamAttemptDoesNotExistsException
from edx_proctoring.serializers import ProctoredExamSerializer from edx_proctoring.serializers import ProctoredExamSerializer
...@@ -101,22 +101,16 @@ class ProctoredExamView(AuthenticatedAPIView): ...@@ -101,22 +101,16 @@ class ProctoredExamView(AuthenticatedAPIView):
""" """
serializer = ProctoredExamSerializer(data=request.DATA) serializer = ProctoredExamSerializer(data=request.DATA)
if serializer.is_valid(): if serializer.is_valid():
try: exam_id = create_exam(
exam_id = create_exam( course_id=request.DATA.get('course_id', None),
course_id=request.DATA.get('course_id', None), content_id=request.DATA.get('content_id', None),
content_id=request.DATA.get('content_id', None), exam_name=request.DATA.get('exam_name', None),
exam_name=request.DATA.get('exam_name', None), time_limit_mins=request.DATA.get('time_limit_mins', None),
time_limit_mins=request.DATA.get('time_limit_mins', None), is_proctored=request.DATA.get('is_proctored', None),
is_proctored=request.DATA.get('is_proctored', None), external_id=request.DATA.get('external_id', None),
external_id=request.DATA.get('external_id', None), is_active=request.DATA.get('is_active', None)
is_active=request.DATA.get('is_active', None) )
) return Response({'exam_id': exam_id})
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."}
)
else: else:
return Response( return Response(
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
...@@ -131,12 +125,12 @@ class ProctoredExamView(AuthenticatedAPIView): ...@@ -131,12 +125,12 @@ class ProctoredExamView(AuthenticatedAPIView):
""" """
try: try:
exam_id = update_exam( exam_id = update_exam(
exam_id=request.DATA.get('exam_id', ""), exam_id=request.DATA.get('exam_id', None),
exam_name=request.DATA.get('exam_name', ""), exam_name=request.DATA.get('exam_name', None),
time_limit_mins=request.DATA.get('time_limit_mins', ""), time_limit_mins=request.DATA.get('time_limit_mins', None),
is_proctored=request.DATA.get('is_proctored', False), is_proctored=request.DATA.get('is_proctored', None),
external_id=request.DATA.get('external_id', ""), external_id=request.DATA.get('external_id', None),
is_active=request.DATA.get('is_active', False), is_active=request.DATA.get('is_active', None),
) )
return Response({'exam_id': exam_id}) return Response({'exam_id': exam_id})
except ProctoredExamNotFoundException: except ProctoredExamNotFoundException:
...@@ -176,18 +170,16 @@ class ProctoredExamView(AuthenticatedAPIView): ...@@ -176,18 +170,16 @@ class ProctoredExamView(AuthenticatedAPIView):
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
data={"detail": "The exam with course_id, content_id does not exist."} 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): class StudentProctoredExamAttempt(AuthenticatedAPIView):
""" """
Endpoint for the StudentProctoredExamAttempt 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 def get(self, request): # pylint: disable=unused-argument
...@@ -217,9 +209,9 @@ class StudentProctoredExamAttempt(AuthenticatedAPIView): ...@@ -217,9 +209,9 @@ class StudentProctoredExamAttempt(AuthenticatedAPIView):
""" """
try: try:
exam_attempt_id = start_exam_attempt( exam_attempt_id = start_exam_attempt(
exam_id=request.DATA.get('exam_id', ""), exam_id=request.DATA.get('exam_id', None),
user_id=request.DATA.get('user_id', ""), user_id=request.DATA.get('user_id', None),
external_id=request.DATA.get('external_id', "") external_id=request.DATA.get('external_id', None)
) )
return Response({'exam_attempt_id': exam_attempt_id}) return Response({'exam_attempt_id': exam_attempt_id})
...@@ -269,7 +261,7 @@ class ExamAllowanceView(AuthenticatedAPIView): ...@@ -269,7 +261,7 @@ class ExamAllowanceView(AuthenticatedAPIView):
value=request.DATA.get('value', "") value=request.DATA.get('value', "")
)) ))
@require_staff @method_decorator(require_staff)
def delete(self, request): def delete(self, request):
""" """
HTTP DELETE handler. Removes Allowance. 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