Integration of edx_proctoring into the LMS
This commit is contained in:
@@ -101,6 +101,12 @@ REPORTS_DATA = (
|
||||
'instructor_api_endpoint': 'get_students_who_may_enroll',
|
||||
'task_api_endpoint': 'instructor_task.api.submit_calculate_may_enroll_csv',
|
||||
'extra_instructor_api_kwargs': {},
|
||||
},
|
||||
{
|
||||
'report_type': 'proctored exam results',
|
||||
'instructor_api_endpoint': 'get_proctored_exam_results',
|
||||
'task_api_endpoint': 'instructor_task.api.submit_proctored_exam_results_report',
|
||||
'extra_instructor_api_kwargs': {},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -223,6 +229,7 @@ class TestInstructorAPIDenyLevels(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
('get_enrollment_report', {}),
|
||||
('get_students_who_may_enroll', {}),
|
||||
('get_exec_summary_report', {}),
|
||||
('get_proctored_exam_results', {}),
|
||||
]
|
||||
# Endpoints that only Instructors can access
|
||||
self.instructor_level_endpoints = [
|
||||
@@ -2316,6 +2323,29 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
|
||||
self.assertIn('status', res_json)
|
||||
self.assertIn('currently being created', res_json['status'])
|
||||
|
||||
def test_get_student_exam_results(self):
|
||||
"""
|
||||
Test whether get_proctored_exam_results returns an appropriate
|
||||
status message when users request a CSV file.
|
||||
"""
|
||||
url = reverse(
|
||||
'get_proctored_exam_results',
|
||||
kwargs={'course_id': unicode(self.course.id)}
|
||||
)
|
||||
# Successful case:
|
||||
response = self.client.get(url, {})
|
||||
res_json = json.loads(response.content)
|
||||
self.assertIn('status', res_json)
|
||||
self.assertNotIn('currently being created', res_json['status'])
|
||||
# CSV generation already in progress:
|
||||
with patch('instructor_task.api.submit_proctored_exam_results_report') as submit_task_function:
|
||||
error = AlreadyRunningError()
|
||||
submit_task_function.side_effect = error
|
||||
response = self.client.get(url, {})
|
||||
res_json = json.loads(response.content)
|
||||
self.assertIn('status', res_json)
|
||||
self.assertIn('currently being created', res_json['status'])
|
||||
|
||||
def test_access_course_finance_admin_with_invalid_course_key(self):
|
||||
"""
|
||||
Test assert require_course fiance_admin before generating
|
||||
|
||||
44
lms/djangoapps/instructor/tests/test_proctoring.py
Normal file
44
lms/djangoapps/instructor/tests/test_proctoring.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""
|
||||
Unit tests for Edx Proctoring feature flag in new instructor dashboard.
|
||||
"""
|
||||
|
||||
from mock import patch
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from nose.plugins.attrib import attr
|
||||
|
||||
from student.roles import CourseFinanceAdminRole
|
||||
from student.tests.factories import AdminFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_PROCTORED_EXAMS': True})
|
||||
class TestProctoringDashboardViews(ModuleStoreTestCase):
|
||||
"""
|
||||
Check for Proctoring view on the new instructor dashboard
|
||||
"""
|
||||
def setUp(self):
|
||||
super(TestProctoringDashboardViews, self).setUp()
|
||||
self.course = CourseFactory.create()
|
||||
self.course.enable_proctored_exams = True
|
||||
|
||||
# Create instructor account
|
||||
self.instructor = AdminFactory.create()
|
||||
self.client.login(username=self.instructor.username, password="test")
|
||||
self.course = self.update_course(self.course, self.instructor.id)
|
||||
|
||||
# URL for instructor dash
|
||||
self.url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id.to_deprecated_string()})
|
||||
self.proctoring_link = '<a href="" data-section="proctoring">Proctoring</a>'
|
||||
CourseFinanceAdminRole(self.course.id).add_users(self.instructor)
|
||||
|
||||
def test_pass_proctoring_tab_in_instructor_dashboard(self):
|
||||
"""
|
||||
Test Pass Proctoring Tab is in the Instructor Dashboard
|
||||
"""
|
||||
response = self.client.get(self.url)
|
||||
self.assertTrue(self.proctoring_link in response.content)
|
||||
self.assertTrue('Allowance Section' in response.content)
|
||||
116
lms/djangoapps/instructor/tests/test_services.py
Normal file
116
lms/djangoapps/instructor/tests/test_services.py
Normal file
@@ -0,0 +1,116 @@
|
||||
"""
|
||||
Tests for the InstructorService
|
||||
"""
|
||||
|
||||
import json
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from courseware.models import StudentModule
|
||||
from instructor.services import InstructorService
|
||||
from instructor.tests.test_tools import msk_from_problem_urlname
|
||||
from nose.plugins.attrib import attr
|
||||
|
||||
from student.models import CourseEnrollment
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class InstructorServiceTests(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for the InstructorService
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(InstructorServiceTests, self).setUp()
|
||||
|
||||
self.course = CourseFactory.create()
|
||||
self.student = UserFactory()
|
||||
CourseEnrollment.enroll(self.student, self.course.id)
|
||||
|
||||
self.problem_location = msk_from_problem_urlname(
|
||||
self.course.id,
|
||||
'robot-some-problem-urlname'
|
||||
)
|
||||
|
||||
self.other_problem_location = msk_from_problem_urlname(
|
||||
self.course.id,
|
||||
'robot-some-other_problem-urlname'
|
||||
)
|
||||
|
||||
self.problem_urlname = unicode(self.problem_location)
|
||||
self.other_problem_urlname = unicode(self.other_problem_location)
|
||||
|
||||
self.service = InstructorService()
|
||||
self.module_to_reset = StudentModule.objects.create(
|
||||
student=self.student,
|
||||
course_id=self.course.id,
|
||||
module_state_key=self.problem_location,
|
||||
state=json.dumps({'attempts': 2}),
|
||||
)
|
||||
|
||||
def test_reset_student_attempts_delete(self):
|
||||
"""
|
||||
Test delete student state.
|
||||
"""
|
||||
|
||||
# make sure the attempt is there
|
||||
self.assertEqual(
|
||||
StudentModule.objects.filter(
|
||||
student=self.module_to_reset.student,
|
||||
course_id=self.course.id,
|
||||
module_state_key=self.module_to_reset.module_state_key,
|
||||
).count(),
|
||||
1
|
||||
)
|
||||
|
||||
self.service.delete_student_attempt(
|
||||
self.student.username,
|
||||
unicode(self.course.id),
|
||||
self.problem_urlname
|
||||
)
|
||||
|
||||
# make sure the module has been deleted
|
||||
self.assertEqual(
|
||||
StudentModule.objects.filter(
|
||||
student=self.module_to_reset.student,
|
||||
course_id=self.course.id,
|
||||
module_state_key=self.module_to_reset.module_state_key,
|
||||
).count(),
|
||||
0
|
||||
)
|
||||
|
||||
def test_reset_bad_content_id(self):
|
||||
"""
|
||||
Negative test of trying to reset attempts with bad content_id
|
||||
"""
|
||||
|
||||
result = self.service.delete_student_attempt(
|
||||
self.student.username,
|
||||
unicode(self.course.id),
|
||||
'foo/bar/baz'
|
||||
)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_reset_bad_user(self):
|
||||
"""
|
||||
Negative test of trying to reset attempts with bad user identifier
|
||||
"""
|
||||
|
||||
result = self.service.delete_student_attempt(
|
||||
'bad_student',
|
||||
unicode(self.course.id),
|
||||
'foo/bar/baz'
|
||||
)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_reset_non_existing_attempt(self):
|
||||
"""
|
||||
Negative test of trying to reset attempts with bad user identifier
|
||||
"""
|
||||
|
||||
result = self.service.delete_student_attempt(
|
||||
self.student.username,
|
||||
unicode(self.course.id),
|
||||
self.other_problem_urlname
|
||||
)
|
||||
self.assertIsNone(result)
|
||||
Reference in New Issue
Block a user