Commit fe4464aa by Chris Dodge

if a verified user views a proctored exam without the prerequisites, then we…

if a verified user views a proctored exam without the prerequisites, then we should auto-mark the attempt as declined
parent c057412c
......@@ -498,7 +498,7 @@ def mark_exam_attempt_as_ready(exam_id, user_id):
return update_attempt_status(exam_id, user_id, ProctoredExamStudentAttemptStatus.ready_to_start)
def update_attempt_status(exam_id, user_id, to_status):
def update_attempt_status(exam_id, user_id, to_status, raise_if_not_found=True):
"""
Internal helper to handle state transitions of attempt status
"""
......@@ -522,7 +522,10 @@ def update_attempt_status(exam_id, user_id, to_status):
exam_attempt_obj = ProctoredExamStudentAttempt.objects.get_exam_attempt(exam_id, user_id)
if exam_attempt_obj is None:
raise StudentExamAttemptDoesNotExistsException('Error. Trying to look up an exam that does not exist.')
if raise_if_not_found:
raise StudentExamAttemptDoesNotExistsException('Error. Trying to look up an exam that does not exist.')
else:
return
#
# don't allow state transitions from a completed state to an incomplete state
......@@ -738,22 +741,39 @@ def _check_credit_eligibility(credit_state):
been passed and False if otherwise
"""
# check both conditions
return (
_check_eligibility_of_enrollment_mode(credit_state) and
_check_eligibility_of_prerequisites(credit_state)
)
def _check_eligibility_of_enrollment_mode(credit_state):
"""
Inspects that the enrollment mode of the user
is valid for proctoring
"""
# Allow only the verified students to take the exam as a proctored exam
# Also make an exception for the honor students to take the "practice exam" as a proctored exam.
# For the rest of the enrollment modes, None is returned which shows the exam content
# to the student rather than the proctoring prompt.
if credit_state['enrollment_mode'] != 'verified':
return False
return credit_state['enrollment_mode'] == 'verified'
def _check_eligibility_of_prerequisites(credit_state):
"""
Inspects that the user has completed all prerequiste
steps required to be allowed to take a proctored exam
"""
# also, if there are in-course reverifications requirements
# then make sure those has a 'satisfied' status
for requirement in credit_state['credit_requirement_status']:
if requirement['namespace'] == 'reverification':
if requirement['status'] != 'satisfied':
return False
# passed everything, so we can return True
return True
......@@ -912,14 +932,35 @@ def get_student_view(user_id, course_id, content_id,
exam['is_practice_exam']
)
attempt = None
if check_mode_and_eligibility:
credit_state = context['credit_state']
has_mode = _check_eligibility_of_enrollment_mode(credit_state)
has_prerequisites = False
if has_mode:
has_prerequisites = _check_eligibility_of_prerequisites(credit_state)
# see if the user has passed all pre-requisite credit eligibility
# checks, otherwise just show the user the exam unproctored
if not _check_credit_eligibility(credit_state):
# Nope, has not fulfilled pre-requisites, thus we
# just show the unproctored version
if not has_mode or not has_prerequisites:
# if we are in the right mode and if we don't have
# pre-requisites, then we implicitly decline the exam
if has_mode:
attempt = get_exam_attempt(exam_id, user_id)
if not attempt:
# user hasn't a record of attempt, create one now
# so we can mark it as declined
create_exam_attempt(exam_id, user_id)
update_attempt_status(
exam_id,
user_id,
ProctoredExamStudentAttemptStatus.declined,
raise_if_not_found=False
)
# don't override context, let the courseware show
return None
attempt = get_exam_attempt(exam_id, user_id)
......
......@@ -649,7 +649,15 @@ class ProctoredExamApiTests(LoggedInTestCase):
)
self.assertIsNone(rendered_response)
def test_prerequirements_view(self):
@ddt.data(
('reverification', None, False, True, ProctoredExamStudentAttemptStatus.declined),
('reverification', 'failed', False, False, ProctoredExamStudentAttemptStatus.declined),
('reverification', 'satisfied', True, True, None),
('grade', 'failed', True, False, None)
)
@ddt.unpack
def test_prereq_scarios(self, namespace, req_status, show_proctored,
pre_create_attempt, mark_as_declined):
"""
This test asserts that proctoring will not be displayed under the following
conditions:
......@@ -657,99 +665,42 @@ class ProctoredExamApiTests(LoggedInTestCase):
- Verified student has not completed all 'reverification' requirements
"""
# user hasn't attempted reverifications
rendered_response = get_student_view(
user_id=self.user_id,
course_id=self.course_id,
content_id=self.content_id,
context={
'is_proctored': True,
'display_name': self.exam_name,
'default_time_limit_mins': 90,
'credit_state': {
'enrollment_mode': 'verified',
'credit_requirement_status': [
{
'namespace': 'reverification',
'status': None,
}
]
},
'is_practice_exam': False
}
)
self.assertIsNone(rendered_response)
exam = get_exam_by_id(self.proctored_exam_id)
# user failed reverifications
rendered_response = get_student_view(
user_id=self.user_id,
course_id=self.course_id,
content_id=self.content_id,
context={
'is_proctored': True,
'display_name': self.exam_name,
'default_time_limit_mins': 90,
'credit_state': {
'enrollment_mode': 'verified',
'credit_requirement_status': [
{
'namespace': 'reverification',
'status': 'failed',
}
]
},
'is_practice_exam': False
}
)
self.assertIsNone(rendered_response)
if pre_create_attempt:
create_exam_attempt(self.proctored_exam_id, self.user_id)
# user passed reverifications
# user hasn't attempted reverifications
rendered_response = get_student_view(
user_id=self.user_id,
course_id=self.course_id,
content_id=self.content_id,
course_id=exam['course_id'],
content_id=exam['content_id'],
context={
'is_proctored': True,
'display_name': self.exam_name,
'default_time_limit_mins': 90,
'is_practice_exam': False,
'credit_state': {
'enrollment_mode': 'verified',
'credit_requirement_status': [
{
'namespace': 'reverification',
'status': 'satisfied',
'namespace': namespace,
'status': req_status,
}
]
},
'is_practice_exam': False
}
}
)
# here, we should get proctoring content
self.assertIsNotNone(rendered_response)
if show_proctored:
self.assertIsNotNone(rendered_response)
else:
self.assertIsNone(rendered_response)
# user doesn't have pre-requisites on reverification
rendered_response = get_student_view(
user_id=self.user_id,
course_id=self.course_id,
content_id=self.content_id,
context={
'is_proctored': True,
'display_name': self.exam_name,
'default_time_limit_mins': 90,
'credit_state': {
'enrollment_mode': 'verified',
'credit_requirement_status': [
{
'namespace': 'grade',
'status': 'failed',
}
]
},
'is_practice_exam': False
}
)
# here, we should get proctoring content
self.assertIsNotNone(rendered_response)
# also the user should have been marked as declined in certain
# cases
if mark_as_declined:
attempt = get_exam_attempt(self.proctored_exam_id, self.user_id)
self.assertEqual(attempt['status'], ProctoredExamStudentAttemptStatus.declined)
def test_student_view_non_student(self):
"""
......@@ -1182,6 +1133,24 @@ class ProctoredExamApiTests(LoggedInTestCase):
ProctoredExamStudentAttemptStatus.ready_to_submit
)
def test_update_unexisting_attempt(self):
"""
Tests updating an non-existing attempt
"""
with self.assertRaises(StudentExamAttemptDoesNotExistsException):
update_attempt_status(0, 0, ProctoredExamStudentAttemptStatus.timed_out)
# also check the raise_if_not_found flag
self.assertIsNone(
update_attempt_status(
0,
0,
ProctoredExamStudentAttemptStatus.timed_out,
raise_if_not_found=False
)
)
@ddt.data(
(
ProctoredExamStudentAttemptStatus.eligible, {
......
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