def test_get_studentview_submitted_status_with_duedate(self): """ Test for get_student_view proctored exam which has been submitted And due date has passed """ proctored_exam = ProctoredExam.objects.create( course_id='a/b/c', content_id='test_content', exam_name='Test Exam', external_id='123aXqe3', time_limit_mins=30, is_proctored=True, is_active=True, due_date=datetime.now(pytz.UTC) + timedelta(minutes=40) ) exam_attempt = ProctoredExamStudentAttempt.objects.create( proctored_exam=proctored_exam, user=self.user, allowed_time_limit_mins=30, taking_as_proctored=True, external_id=proctored_exam.external_id, status=ProctoredExamStudentAttemptStatus.submitted, ) # due date is after 10 minutes reset_time = datetime.now(pytz.UTC) + timedelta(minutes=20) with freeze_time(reset_time): rendered_response = get_student_view( user_id=self.user.id, course_id='a/b/c', content_id='test_content', context={ 'is_proctored': True, 'display_name': 'Test Exam', 'default_time_limit_mins': 30 } ) self.assertIn(self.proctored_exam_submitted_msg, rendered_response) exam_attempt.is_status_acknowledged = True exam_attempt.save() rendered_response = get_student_view( user_id=self.user.id, course_id='a/b/c', content_id='test_content', context={ 'is_proctored': True, 'display_name': 'Test Exam', 'default_time_limit_mins': 30 } ) self.assertIsNotNone(rendered_response)
def _render_exam(self, content_id, context_overrides=None): """ Renders a test exam. """ exam = get_exam_by_id(content_id) context = { 'is_proctored': True, 'allow_proctoring_opt_out': True, 'display_name': self.exam_name, 'default_time_limit_mins': 90, 'is_practice_exam': False, 'credit_state': { 'enrollment_mode': 'verified', 'credit_requirement_status': [], }, 'verification_status': 'approved', 'verification_url': '/reverify', } if context_overrides: context.update(context_overrides) return get_student_view( user_id=self.user_id, course_id=exam['course_id'], content_id=exam['content_id'], context=context, )
def test_get_studentview_submitted_timed_exam_with_past_due_date( self, due_date, hide_after_due): """ Test for get_student_view timed exam with the due date. """ # exam is created with due datetime which has already passed exam_id = self._create_exam_with_due_time(is_proctored=False, due_date=due_date) if hide_after_due: update_exam(exam_id, hide_after_due=hide_after_due) # now create the timed_exam attempt in the submitted state self._create_exam_attempt(exam_id, status='submitted') rendered_response = get_student_view( user_id=self.user_id, course_id=self.course_id, content_id=self.content_id_for_exam_with_due_date, context={ 'is_proctored': False, 'display_name': self.exam_name, 'default_time_limit_mins': 10, 'due_date': due_date, }) if datetime.now(pytz.UTC) < due_date: self.assertIn(self.timed_exam_submitted, rendered_response) self.assertIn(self.submitted_timed_exam_msg_with_due_date, rendered_response) elif hide_after_due: self.assertIn(self.timed_exam_submitted, rendered_response) self.assertNotIn(self.submitted_timed_exam_msg_with_due_date, rendered_response) else: self.assertIsNone(rendered_response)
def _render_exam(self, content_id, context_overrides=None): """ Renders a test exam. """ exam = get_exam_by_id(content_id) context = { 'is_proctored': True, 'allow_proctoring_opt_out': True, 'display_name': self.exam_name, 'default_time_limit_mins': 90, 'is_practice_exam': False, 'credit_state': { 'enrollment_mode': 'verified', 'credit_requirement_status': [], }, 'verification_status': 'approved', 'verification_url': '/reverify', } if context_overrides: context.update(context_overrides) return get_student_view( user_id=self.user_id, course_id=exam['course_id'], content_id=exam['content_id'], context=context, )
def test_get_studentview_unstarted_timed_exam_with_allowance(self): """ Test for get_student_view Timed exam which is not proctored and has not started yet. But user has an allowance """ allowed_extra_time = 10 add_allowance_for_user( self.timed_exam_id, self.user.username, ProctoredExamStudentAllowance.ADDITIONAL_TIME_GRANTED, six.text_type(allowed_extra_time)) rendered_response = get_student_view(user_id=self.user_id, course_id=self.course_id, content_id=self.content_id_timed, context={}) self.assertNotIn( 'data-exam-id="{proctored_exam_id}"'.format( proctored_exam_id=self.proctored_exam_id), rendered_response) self.assertIn(self.timed_exam_msg.format(exam_name=self.exam_name), rendered_response) self.assertIn('31 minutes', rendered_response) self.assertNotIn( self.start_an_exam_msg.format(exam_name=self.exam_name), rendered_response)
def test_get_studentview_submitted_timed_exam_with_past_due_date(self, due_date, hide_after_due): """ Test for get_student_view timed exam with the due date. """ # exam is created with due datetime which has already passed exam_id = self._create_exam_with_due_time(is_proctored=False, due_date=due_date) if hide_after_due: update_exam(exam_id, hide_after_due=hide_after_due) # now create the timed_exam attempt in the submitted state self._create_exam_attempt(exam_id, status='submitted') rendered_response = get_student_view( user_id=self.user_id, course_id=self.course_id, content_id=self.content_id_for_exam_with_due_date, context={ 'is_proctored': False, 'display_name': self.exam_name, 'default_time_limit_mins': 10, 'due_date': due_date, } ) if datetime.now(pytz.UTC) < due_date: self.assertIn(self.timed_exam_submitted, rendered_response) self.assertIn(self.submitted_timed_exam_msg_with_due_date, rendered_response) elif hide_after_due: self.assertIn(self.timed_exam_submitted, rendered_response) self.assertNotIn(self.submitted_timed_exam_msg_with_due_date, rendered_response) else: self.assertIsNone(rendered_response)
def test_get_studentview_unstarted_timed_exam_with_allowance(self): """ Test for get_student_view Timed exam which is not proctored and has not started yet. But user has an allowance """ allowed_extra_time = 10 add_allowance_for_user( self.timed_exam_id, self.user.username, ProctoredExamStudentAllowance.ADDITIONAL_TIME_GRANTED, six.text_type(allowed_extra_time) ) rendered_response = get_student_view( user_id=self.user_id, course_id=self.course_id, content_id=self.content_id_timed, context={} ) self.assertNotIn( 'data-exam-id="{proctored_exam_id}"'.format(proctored_exam_id=self.proctored_exam_id), rendered_response ) self.assertIn(self.timed_exam_msg.format(exam_name=self.exam_name), rendered_response) self.assertIn('31 minutes', rendered_response) self.assertNotIn(self.start_an_exam_msg.format(exam_name=self.exam_name), rendered_response)
def test_timed_exam_attempt_with_past_due_datetime(self): """ Test for get_student_view for timed exam with past due datetime """ due_date = datetime.now(pytz.UTC) + timedelta(days=1) # exam is created with due datetime which has already passed self._create_exam_with_due_time( due_date=due_date, is_proctored=False ) # due_date is exactly after 24 hours, if student arrives after 2 days # then he can not attempt the proctored exam reset_time = due_date + timedelta(days=2) with freeze_time(reset_time): rendered_response = get_student_view( user_id=self.user_id, course_id=self.course_id, content_id=self.content_id_for_exam_with_due_date, context={ 'is_proctored': False, 'display_name': self.exam_name, 'default_time_limit_mins': self.default_time_limit, 'due_date': due_date, } ) self.assertIn(self.exam_expired_msg, rendered_response) # call the view again, because the first call set the exam attempt to 'expired' # this second call will render the view based on the state rendered_response = get_student_view( user_id=self.user_id, course_id=self.course_id, content_id=self.content_id_for_exam_with_due_date, context={ 'is_proctored': True, 'is_practice_exam': True, 'display_name': self.exam_name, 'default_time_limit_mins': self.default_time_limit, 'due_date': due_date, } ) self.assertIn(self.exam_expired_msg, rendered_response)
def test_expired_exam(self): """ Test that an expired exam shows a difference message when the exam is expired just recently """ # create exam with completed_at equal to current time and started_at equal to allowed_time_limit_mins ago attempt = self._create_started_exam_attempt(is_proctored=False) attempt.status = "submitted" attempt.started_at = attempt.started_at - timedelta( minutes=attempt.allowed_time_limit_mins) attempt.completed_at = attempt.started_at + timedelta( minutes=attempt.allowed_time_limit_mins) attempt.save() rendered_response = get_student_view(user_id=self.user_id, course_id=self.course_id, content_id=self.content_id_timed, context={ 'display_name': self.exam_name, }) self.assertIn(self.timed_exam_expired, rendered_response) # update start and completed time such that completed_time is allowed_time_limit_mins ago than the current time attempt.started_at = attempt.started_at - timedelta( minutes=attempt.allowed_time_limit_mins) attempt.completed_at = attempt.completed_at - timedelta( minutes=attempt.allowed_time_limit_mins) attempt.save() rendered_response = get_student_view(user_id=self.user_id, course_id=self.course_id, content_id=self.content_id_timed, context={ 'display_name': self.exam_name, }) self.assertIn(self.timed_exam_submitted, rendered_response)
def test_get_disabled_student_view(self): """ Assert that a disabled proctored exam will not override the student_view """ self.assertIsNone( get_student_view(user_id=self.user_id, course_id=self.course_id, content_id=self.disabled_content_id, context={ 'is_proctored': True, 'display_name': self.exam_name, 'default_time_limit_mins': 90 }))
def test_expired_exam(self): """ Test that an expired exam shows a difference message when the exam is expired just recently """ # create exam with completed_at equal to current time and started_at equal to allowed_time_limit_mins ago attempt = self._create_started_exam_attempt(is_proctored=False) attempt.status = "submitted" attempt.started_at = attempt.started_at - timedelta(minutes=attempt.allowed_time_limit_mins) attempt.completed_at = attempt.started_at + timedelta(minutes=attempt.allowed_time_limit_mins) attempt.save() rendered_response = get_student_view( user_id=self.user_id, course_id=self.course_id, content_id=self.content_id_timed, context={ 'display_name': self.exam_name, } ) self.assertIn(self.timed_exam_expired, rendered_response) # update start and completed time such that completed_time is allowed_time_limit_mins ago than the current time attempt.started_at = attempt.started_at - timedelta(minutes=attempt.allowed_time_limit_mins) attempt.completed_at = attempt.completed_at - timedelta(minutes=attempt.allowed_time_limit_mins) attempt.save() rendered_response = get_student_view( user_id=self.user_id, course_id=self.course_id, content_id=self.content_id_timed, context={ 'display_name': self.exam_name, } ) self.assertIn(self.timed_exam_submitted, rendered_response)
def test_get_studentview_started_timed_exam(self): """ Test for get_student_view timed exam which has started. """ self._create_started_exam_attempt(is_proctored=False) rendered_response = get_student_view(user_id=self.user_id, course_id=self.course_id, content_id=self.content_id_timed, context={ 'is_proctored': True, 'display_name': self.exam_name, 'default_time_limit_mins': 90 }) self.assertIsNone(rendered_response)
def test_student_response_without_credit_state(self): """ Test that response is not None for users who are not enrolled. """ set_runtime_service('credit', MockCreditServiceNone()) 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 }, user_role='student') self.assertIsNotNone(rendered_response)
def test_get_disabled_student_view(self): """ Assert that a disabled proctored exam will not override the student_view """ self.assertIsNone( get_student_view( user_id=self.user_id, course_id=self.course_id, content_id=self.disabled_content_id, context={ 'is_proctored': True, 'display_name': self.exam_name, 'default_time_limit_mins': 90 } ) )
def test_get_studentview_started_timed_exam(self): """ Test for get_student_view timed exam which has started. """ self._create_started_exam_attempt(is_proctored=False) rendered_response = get_student_view( user_id=self.user_id, course_id=self.course_id, content_id=self.content_id_timed, context={ 'is_proctored': True, 'display_name': self.exam_name, 'default_time_limit_mins': 90 } ) self.assertIsNone(rendered_response)
def test_student_view_non_student(self): """ Make sure that if we ask for a student view if we are not in a student role, then we don't see any proctoring views """ 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 }, user_role='staff') self.assertIsNone(rendered_response)
def test_student_response_without_credit_state(self): """ Test that response is not None for users who are not enrolled. """ set_runtime_service('credit', MockCreditServiceNone()) 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 }, user_role='student' ) self.assertIsNotNone(rendered_response)
def test_student_view_non_student(self): """ Make sure that if we ask for a student view if we are not in a student role, then we don't see any proctoring views """ 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 }, user_role='staff' ) self.assertIsNone(rendered_response)
def test_wrong_exam_combo(self): """ Verify that we get a None back when rendering a view for a practice, non-proctored exam. This is unsupported. """ rendered_response = get_student_view(user_id=self.user_id, course_id='foo', content_id='bar', context={ 'is_proctored': False, 'is_practice_exam': True, 'display_name': self.exam_name, 'default_time_limit_mins': 90, 'hide_after_due': False, }, user_role='student') self.assertIsNone(rendered_response)
def test_get_studentview_completed_timed_exam(self, status, expected_content): """ Test for get_student_view timed exam which has completed. """ exam_attempt = self._create_started_exam_attempt(is_proctored=False) exam_attempt.status = status if status == 'submitted': exam_attempt.completed_at = datetime.now(pytz.UTC) exam_attempt.save() rendered_response = get_student_view(user_id=self.user_id, course_id=self.course_id, content_id=self.content_id_timed, context={ 'display_name': self.exam_name, }) self.assertIn(expected_content, rendered_response)
def test_get_studentview_completed_timed_exam(self, status, expected_content): """ Test for get_student_view timed exam which has completed. """ exam_attempt = self._create_started_exam_attempt(is_proctored=False) exam_attempt.status = status if status == 'submitted': exam_attempt.completed_at = datetime.now(pytz.UTC) exam_attempt.save() rendered_response = get_student_view( user_id=self.user_id, course_id=self.course_id, content_id=self.content_id_timed, context={ 'display_name': self.exam_name, } ) self.assertIn(expected_content, rendered_response)
def test_wrong_exam_combo(self): """ Verify that we get a None back when rendering a view for a practice, non-proctored exam. This is unsupported. """ rendered_response = get_student_view( user_id=self.user_id, course_id='foo', content_id='bar', context={ 'is_proctored': False, 'is_practice_exam': True, 'display_name': self.exam_name, 'default_time_limit_mins': 90, 'hide_after_due': False, }, user_role='student' ) self.assertIsNone(rendered_response)
def test_get_studentview_unstarted_timed_exam(self): """ Test for get_student_view Timed exam which is not proctored and has not started yet. """ rendered_response = get_student_view( user_id=self.user_id, course_id="abc", content_id=self.content_id, context={ 'is_proctored': False, 'display_name': self.exam_name, 'default_time_limit_mins': 90, 'hide_after_due': False, } ) self.assertNotIn( 'data-exam-id="{proctored_exam_id}"'.format(proctored_exam_id=self.proctored_exam_id), rendered_response ) self.assertIn(self.timed_exam_msg.format(exam_name=self.exam_name), rendered_response) self.assertIn('1 hour and 30 minutes', rendered_response) self.assertNotIn(self.start_an_exam_msg.format(exam_name=self.exam_name), rendered_response)
def test_practice_exam_passed_end_date(self): """ Verify that we get a None back on a practice exam if the course end date is passed """ set_runtime_service('credit', MockCreditServiceWithCourseEndDate()) rendered_response = get_student_view(user_id=self.user_id, course_id='foo', content_id='bar', context={ 'is_proctored': True, 'is_practice_exam': True, 'display_name': self.exam_name, 'default_time_limit_mins': 90, 'due_date': None, 'hide_after_due': False, }, user_role='student') self.assertIsNone(rendered_response)
def test_get_studentview_long_limit(self, under_exception): """ Test for hide_extra_time_footer on exams with > 20 hours time limit """ exam_id = self._create_exam_with_due_time(is_proctored=False, ) if under_exception: update_exam(exam_id, time_limit_mins=((20 * 60))) # exactly 20 hours else: update_exam(exam_id, time_limit_mins=((20 * 60) + 1)) # 1 minute greater than 20 hours rendered_response = get_student_view( user_id=self.user_id, course_id=self.course_id, content_id=self.content_id_for_exam_with_due_date, context={ 'is_proctored': False, 'display_name': self.exam_name, } ) if under_exception: self.assertIn(self.timed_footer_msg, rendered_response) else: self.assertNotIn(self.timed_footer_msg, rendered_response)
def test_practice_exam_passed_end_date(self): """ Verify that we get a None back on a practice exam if the course end date is passed """ credit_state = MockCreditServiceWithCourseEndDate().get_credit_state(self.user_id, 'foo', True) rendered_response = get_student_view( user_id=self.user_id, course_id='foo', content_id='bar', context={ 'is_proctored': True, 'is_practice_exam': True, 'display_name': self.exam_name, 'default_time_limit_mins': 90, 'due_date': None, 'hide_after_due': False, 'credit_state': credit_state, }, user_role='student' ) self.assertIsNone(rendered_response)