def delete(self, request, attempt_id): # pylint: disable=unused-argument """ HTTP DELETE handler. Removes an exam attempt. """ try: attempt = get_exam_attempt_by_id(attempt_id) if not attempt: err_msg = ( 'Attempted to access attempt_id {attempt_id} but ' 'it does not exist.'.format( attempt_id=attempt_id ) ) raise StudentExamAttemptDoesNotExistsException(err_msg) remove_exam_attempt(attempt_id) return Response( status=status.HTTP_200_OK, data={} ) except ProctoredBaseException, ex: LOG.exception(ex) return Response( status=status.HTTP_400_BAD_REQUEST, data={"detail": str(ex)} )
def test_review_on_archived_attempt(self): """ Make sure we can process a review report for an attempt which has been archived """ test_payload = self.get_review_payload(ReviewStatus.passed) # now delete the attempt, which puts it into the archive table remove_exam_attempt(self.attempt_id, requesting_user=self.user) # now process the report ProctoredExamReviewCallback().make_review(self.attempt, test_payload) # make sure that what we have in the Database matches what we expect review = ProctoredExamSoftwareSecureReview.get_review_by_attempt_code( self.attempt['attempt_code']) self.assertIsNotNone(review) self.assertEqual(review.review_status, SoftwareSecureReviewStatus.clean) self.assertFalse(review.video_url) self.assertIsNotNone(review.raw_data) # now check the comments that were stored comments = ProctoredExamSoftwareSecureComment.objects.filter( review_id=review.id) self.assertEqual(len(comments), 3)
def test_review_on_archived_attempt(self): """ Make sure we can process a review report for an attempt which has been archived """ test_payload = self.get_review_payload(ReviewStatus.passed) # now delete the attempt, which puts it into the archive table remove_exam_attempt(self.attempt_id, requesting_user=self.user) # now process the report ProctoredExamReviewCallback().make_review(self.attempt, test_payload) # make sure that what we have in the Database matches what we expect review = ProctoredExamSoftwareSecureReview.get_review_by_attempt_code(self.attempt['attempt_code']) self.assertIsNotNone(review) self.assertEqual(review.review_status, SoftwareSecureReviewStatus.clean) self.assertFalse(review.video_url) self.assertIsNotNone(review.raw_data) # now check the comments that were stored comments = ProctoredExamSoftwareSecureComment.objects.filter(review_id=review.id) self.assertEqual(len(comments), 3)
def delete(self, request, attempt_id): # pylint: disable=unused-argument """ HTTP DELETE handler. Removes an exam attempt. """ try: attempt = get_exam_attempt_by_id(attempt_id) if not attempt: err_msg = ( 'Attempted to access attempt_id {attempt_id} but ' 'it does not exist.'.format( attempt_id=attempt_id ) ) raise StudentExamAttemptDoesNotExistsException(err_msg) remove_exam_attempt(attempt_id, request.user) return Response() except ProctoredBaseException, ex: LOG.exception(ex) return Response( status=status.HTTP_400_BAD_REQUEST, data={"detail": str(ex)} )
def test_update_archived_attempt(self): """ Test calling the on_review_saved interface point with an attempt_code that was archived """ provider = get_backend_provider() exam_id = create_exam( course_id='foo/bar/baz', content_id='content', exam_name='Sample Exam', time_limit_mins=10, is_proctored=True ) # be sure to use the mocked out SoftwareSecure handlers with HTTMock(mock_response_content): attempt_id = create_exam_attempt( exam_id, self.user.id, taking_as_proctored=True ) attempt = get_exam_attempt_by_id(attempt_id) self.assertIsNotNone(attempt['external_id']) test_payload = Template(TEST_REVIEW_PAYLOAD).substitute( attempt_code=attempt['attempt_code'], external_id=attempt['external_id'] ) # now process the report provider.on_review_callback(json.loads(test_payload)) # now look at the attempt and make sure it did not # transition to failure on the callback, # as we'll need a manual confirmation via Django Admin pages attempt = get_exam_attempt_by_id(attempt_id) self.assertEqual(attempt['status'], attempt['status']) # now delete the attempt, which puts it into the archive table remove_exam_attempt(attempt_id, requesting_user=self.user) review = ProctoredExamSoftwareSecureReview.objects.get(attempt_code=attempt['attempt_code']) # now simulate a update via Django Admin table which will actually # push through the failure into our attempt status but # as this is an archived attempt, we don't do anything provider.on_review_saved(review, allow_rejects=True) # look at the attempt again, since it moved into Archived state # then it should still remain unchanged archived_attempt = ProctoredExamStudentAttemptHistory.objects.filter( attempt_code=attempt['attempt_code'] ).latest('created') self.assertEqual(archived_attempt.status, attempt['status'])
def test_review_on_archived_attempt(self): """ Make sure we can process a review report for an attempt which has been archived """ provider = get_backend_provider() exam_id = create_exam( course_id='foo/bar/baz', content_id='content', exam_name='Sample Exam', time_limit_mins=10, is_proctored=True ) # be sure to use the mocked out SoftwareSecure handlers with HTTMock(mock_response_content): attempt_id = create_exam_attempt( exam_id, self.user.id, taking_as_proctored=True ) attempt = get_exam_attempt_by_id(attempt_id) self.assertIsNotNone(attempt['external_id']) test_payload = Template(TEST_REVIEW_PAYLOAD).substitute( attempt_code=attempt['attempt_code'], external_id=attempt['external_id'] ) # now delete the attempt, which puts it into the archive table remove_exam_attempt(attempt_id) # now process the report provider.on_review_callback(json.loads(test_payload)) # make sure that what we have in the Database matches what we expect review = ProctoredExamSoftwareSecureReview.get_review_by_attempt_code(attempt['attempt_code']) self.assertIsNotNone(review) self.assertEqual(review.review_status, 'Clean') self.assertEqual( review.video_url, 'http://www.remoteproctor.com/AdminSite/Account/Reviewer/DirectLink-Generic.aspx?ID=foo' ) self.assertIsNotNone(review.raw_data) # now check the comments that were stored comments = ProctoredExamSoftwareSecureComment.objects.filter(review_id=review.id) self.assertEqual(len(comments), 6)
def test_update_archived_attempt(self): """ Test calling the on_review_saved interface point with an attempt_code that was archived """ provider = get_backend_provider() exam_id = create_exam(course_id='foo/bar/baz', content_id='content', exam_name='Sample Exam', time_limit_mins=10, is_proctored=True) # be sure to use the mocked out SoftwareSecure handlers with HTTMock(mock_response_content): attempt_id = create_exam_attempt(exam_id, self.user.id, taking_as_proctored=True) attempt = get_exam_attempt_by_id(attempt_id) self.assertIsNotNone(attempt['external_id']) test_payload = create_test_review_payload( attempt_code=attempt['attempt_code'], external_id=attempt['external_id']) # now process the report provider.on_review_callback(json.loads(test_payload)) # now look at the attempt and make sure it did not # transition to failure on the callback, # as we'll need a manual confirmation via Django Admin pages attempt = get_exam_attempt_by_id(attempt_id) self.assertEqual(attempt['status'], attempt['status']) # now delete the attempt, which puts it into the archive table remove_exam_attempt(attempt_id, requesting_user=self.user) review = ProctoredExamSoftwareSecureReview.objects.get( attempt_code=attempt['attempt_code']) # now simulate a update via Django Admin table which will actually # push through the failure into our attempt status but # as this is an archived attempt, we don't do anything provider.on_review_saved(review, allow_rejects=True) # look at the attempt again, since it moved into Archived state # then it should still remain unchanged archived_attempt = ProctoredExamStudentAttemptHistory.objects.filter( attempt_code=attempt['attempt_code']).latest('created') self.assertEqual(archived_attempt.status, attempt['status'])
def delete(self, request, attempt_id): # pylint: disable=unused-argument """ HTTP DELETE handler. Removes an exam attempt. """ attempt = get_exam_attempt_by_id(attempt_id) if not attempt: err_msg = (u'Attempted to access attempt_id {attempt_id} but ' u'it does not exist.'.format(attempt_id=attempt_id)) raise StudentExamAttemptDoesNotExistsException(err_msg) remove_exam_attempt(attempt_id, request.user) return Response()
def test_review_on_archived_attempt(self): """ Make sure we can process a review report for an attempt which has been archived """ provider = get_backend_provider() exam_id = create_exam(course_id='foo/bar/baz', content_id='content', exam_name='Sample Exam', time_limit_mins=10, is_proctored=True) # be sure to use the mocked out SoftwareSecure handlers with HTTMock(mock_response_content): attempt_id = create_exam_attempt(exam_id, self.user.id, taking_as_proctored=True) attempt = get_exam_attempt_by_id(attempt_id) self.assertIsNotNone(attempt['external_id']) test_payload = Template(TEST_REVIEW_PAYLOAD).substitute( attempt_code=attempt['attempt_code'], external_id=attempt['external_id']) # now delete the attempt, which puts it into the archive table remove_exam_attempt(attempt_id) # now process the report provider.on_review_callback(json.loads(test_payload)) # make sure that what we have in the Database matches what we expect review = ProctoredExamSoftwareSecureReview.get_review_by_attempt_code( attempt['attempt_code']) self.assertIsNotNone(review) self.assertEqual(review.review_status, 'Clean') self.assertEqual( review.video_url, 'http://www.remoteproctor.com/AdminSite/Account/Reviewer/DirectLink-Generic.aspx?ID=foo' ) self.assertIsNotNone(review.raw_data) # now check the comments that were stored comments = ProctoredExamSoftwareSecureComment.objects.filter( review_id=review.id) self.assertEqual(len(comments), 6)
def delete(self, request, attempt_code): try: attempt = ProctoredExamStudentAttempt.objects.get_exam_attempt_by_code(attempt_code) except Exception as e: logging.error("Wrong proctored exam attempt code: {}; Exception: {}".format(attempt_code, str(e))) return Response(data={"message": "Wrong attempt_code"}, status=status.HTTP_400_BAD_REQUEST) if not self.is_allowed_for_session(attempt): return Response(status=status.HTTP_404_NOT_FOUND) try: remove_exam_attempt(attempt_id=attempt.id, requesting_user=attempt.user) except Exception as e: logging.error("Failed to remove proctored exam attempt {}".format(attempt_code)) return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR) return Response(status=200)
def delete(self, request, attempt_id): # pylint: disable=unused-argument """ HTTP DELETE handler. Removes an exam attempt. """ attempt = get_exam_attempt_by_id(attempt_id) if not attempt: err_msg = ( 'Attempted to access attempt_id {attempt_id} but ' 'it does not exist.'.format( attempt_id=attempt_id ) ) raise StudentExamAttemptDoesNotExistsException(err_msg) remove_exam_attempt(attempt_id, request.user) return Response()
def test_review_on_archived_attempt(self): """ Make sure we can process a review report for an attempt which has been archived """ provider = get_backend_provider() exam_id = create_exam( course_id="foo/bar/baz", content_id="content", exam_name="Sample Exam", time_limit_mins=10, is_proctored=True, ) # be sure to use the mocked out SoftwareSecure handlers with HTTMock(mock_response_content): attempt_id = create_exam_attempt(exam_id, self.user.id, taking_as_proctored=True) attempt = get_exam_attempt_by_id(attempt_id) self.assertIsNotNone(attempt["external_id"]) test_payload = Template(TEST_REVIEW_PAYLOAD).substitute( attempt_code=attempt["attempt_code"], external_id=attempt["external_id"] ) # now delete the attempt, which puts it into the archive table remove_exam_attempt(attempt_id, requesting_user=self.user) # now process the report provider.on_review_callback(json.loads(test_payload)) # make sure that what we have in the Database matches what we expect review = ProctoredExamSoftwareSecureReview.get_review_by_attempt_code(attempt["attempt_code"]) self.assertIsNotNone(review) self.assertEqual(review.review_status, "Clean") self.assertFalse(review.video_url) self.assertIsNotNone(review.raw_data) # now check the comments that were stored comments = ProctoredExamSoftwareSecureComment.objects.filter(review_id=review.id) self.assertEqual(len(comments), 6)
def test_run_command(self): """ Run the management command """ # check that review is there reviews = ProctoredExamSoftwareSecureReview.objects.all() self.assertEqual(len(reviews), 1) archive_reviews = ProctoredExamSoftwareSecureReviewHistory.objects.all( ) self.assertEqual(len(archive_reviews), 0) # archive attempt remove_exam_attempt(self.attempt_id, requesting_user=self.user) # check that field is false review = ProctoredExamSoftwareSecureReview.objects.get( attempt_code=self.attempt['attempt_code']) self.assertFalse(review.is_attempt_active) # change field back to true for testing review.is_attempt_active = True review.save() # expect there to be two archived reviews, one from removing the attempt, and one because we changed a field archive_reviews = ProctoredExamSoftwareSecureReviewHistory.objects.all( ) self.assertEqual(len(archive_reviews), 2) call_command('set_is_attempt_active', batch_size=5, sleep_time=0) review = ProctoredExamSoftwareSecureReview.objects.get( attempt_code=self.attempt['attempt_code']) self.assertFalse(review.is_attempt_active) archive_reviews = ProctoredExamSoftwareSecureReviewHistory.objects.filter( is_attempt_active=False) self.assertEqual(len(archive_reviews), 2)
def test_update_archived_attempt(self): """ Test calling the interface point with an attempt_code that was archived """ test_payload = self.get_review_payload() # now process the report ProctoredExamReviewCallback().make_review(self.attempt, test_payload) # now look at the attempt and make sure it did not # transition to failure on the callback, # as we'll need a manual confirmation via Django Admin pages attempt = get_exam_attempt_by_id(self.attempt_id) self.assertEqual(attempt['status'], 'verified') # now delete the attempt, which puts it into the archive table remove_exam_attempt(self.attempt_id, requesting_user=self.user) review = ProctoredExamSoftwareSecureReview.objects.get( attempt_code=self.attempt['attempt_code']) # look at the attempt again, since it moved into Archived state # then it should still remain unchanged archived_attempt = ProctoredExamStudentAttemptHistory.objects.filter( attempt_code=self.attempt['attempt_code']).latest('created') self.assertEqual(archived_attempt.status, attempt['status']) self.assertEqual(review.review_status, SoftwareSecureReviewStatus.clean) # now we'll make another review for the archived attempt. It should NOT update the status test_payload = self.get_review_payload(ReviewStatus.suspicious) self.attempt['is_archived'] = True ProctoredExamReviewCallback().make_review(self.attempt, test_payload) attempt, is_archived = locate_attempt_by_attempt_code( self.attempt['attempt_code']) self.assertTrue(is_archived) self.assertEqual(attempt.status, 'verified')
def test_review_update_attempt_active_field(self): """ Make sure we update the is_active_attempt field when an attempt is archived """ test_payload = self.get_review_payload(ReviewStatus.passed) ProctoredExamReviewCallback().make_review(self.attempt, test_payload) review = ProctoredExamSoftwareSecureReview.get_review_by_attempt_code( self.attempt['attempt_code']) self.assertTrue(review.is_attempt_active) # now delete the attempt, which puts it into the archive table with mock.patch('edx_proctoring.api.update_attempt_status' ) as mock_update_status: remove_exam_attempt(self.attempt_id, requesting_user=self.user) # check that the field has been updated review = ProctoredExamSoftwareSecureReview.get_review_by_attempt_code( self.attempt['attempt_code']) self.assertFalse(review.is_attempt_active) # check that update_attempt_status has not been called, as the attempt has been archived mock_update_status.assert_not_called()
def test_update_archived_attempt(self): """ Test calling the interface point with an attempt_code that was archived """ test_payload = self.get_review_payload() # now process the report ProctoredExamReviewCallback().make_review(self.attempt, test_payload) # now look at the attempt and make sure it did not # transition to failure on the callback, # as we'll need a manual confirmation via Django Admin pages attempt = get_exam_attempt_by_id(self.attempt_id) self.assertEqual(attempt['status'], 'verified') # now delete the attempt, which puts it into the archive table remove_exam_attempt(self.attempt_id, requesting_user=self.user) review = ProctoredExamSoftwareSecureReview.objects.get(attempt_code=self.attempt['attempt_code']) # look at the attempt again, since it moved into Archived state # then it should still remain unchanged archived_attempt = ProctoredExamStudentAttemptHistory.objects.filter( attempt_code=self.attempt['attempt_code'] ).latest('created') self.assertEqual(archived_attempt.status, attempt['status']) self.assertEqual(review.review_status, SoftwareSecureReviewStatus.clean) # now we'll make another review for the archived attempt. It should NOT update the status test_payload = self.get_review_payload(ReviewStatus.suspicious) self.attempt['is_archived'] = True ProctoredExamReviewCallback().make_review(self.attempt, test_payload) attempt, is_archived = locate_attempt_by_attempt_code(self.attempt['attempt_code']) self.assertTrue(is_archived) self.assertEqual(attempt.status, 'verified')