class GradesServiceTests(ModuleStoreTestCase): """ Tests for the Grades service """ def setUp(self, **kwargs): super(GradesServiceTests, self).setUp() self.service = GradesService() self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course') self.subsection = ItemFactory.create(parent=self.course, category="subsection", display_name="Subsection") self.user = UserFactory() self.grade = PersistentSubsectionGrade.update_or_create_grade( user_id=self.user.id, course_id=self.course.id, usage_key=self.subsection.location, first_attempted=None, visible_blocks=[], earned_all=6.0, possible_all=6.0, earned_graded=5.0, possible_graded=5.0 ) self.signal_patcher = patch('lms.djangoapps.grades.signals.signals.SUBSECTION_OVERRIDE_CHANGED.send') self.mock_signal = self.signal_patcher.start() self.id_patcher = patch('lms.djangoapps.grades.services.create_new_event_transaction_id') self.mock_create_id = self.id_patcher.start() self.mock_create_id.return_value = 1 self.type_patcher = patch('lms.djangoapps.grades.services.set_event_transaction_type') self.mock_set_type = self.type_patcher.start() self.flag_patcher = patch('lms.djangoapps.grades.services.waffle_flags') self.mock_waffle_flags = self.flag_patcher.start() self.mock_waffle_flags.return_value = { REJECTED_EXAM_OVERRIDES_GRADE: MockWaffleFlag(True) } def tearDown(self): PersistentSubsectionGradeOverride.objects.all().delete() # clear out all previous overrides self.signal_patcher.stop() self.id_patcher.stop() self.type_patcher.stop() self.flag_patcher.stop() def subsection_grade_to_dict(self, grade): return { 'earned_all': grade.earned_all, 'earned_graded': grade.earned_graded } def subsection_grade_override_to_dict(self, grade): return { 'earned_all_override': grade.earned_all_override, 'earned_graded_override': grade.earned_graded_override } def test_get_subsection_grade(self): self.assertDictEqual(self.subsection_grade_to_dict(self.service.get_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location )), { 'earned_all': 6.0, 'earned_graded': 5.0 }) # test with id strings as parameters instead self.assertDictEqual(self.subsection_grade_to_dict(self.service.get_subsection_grade( user_id=self.user.id, course_key_or_id=unicode(self.course.id), usage_key_or_id=unicode(self.subsection.location) )), { 'earned_all': 6.0, 'earned_graded': 5.0 }) def test_get_subsection_grade_override(self): override, _ = PersistentSubsectionGradeOverride.objects.update_or_create(grade=self.grade) self.assertDictEqual(self.subsection_grade_override_to_dict(self.service.get_subsection_grade_override( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location )), { 'earned_all_override': override.earned_all_override, 'earned_graded_override': override.earned_graded_override }) override, _ = PersistentSubsectionGradeOverride.objects.update_or_create( grade=self.grade, defaults={ 'earned_all_override': 9.0 } ) # test with id strings as parameters instead self.assertDictEqual(self.subsection_grade_override_to_dict(self.service.get_subsection_grade_override( user_id=self.user.id, course_key_or_id=unicode(self.course.id), usage_key_or_id=unicode(self.subsection.location) )), { 'earned_all_override': override.earned_all_override, 'earned_graded_override': override.earned_graded_override }) @ddt.data( [{ 'earned_all': 0.0, 'earned_graded': 0.0 }, { 'earned_all': 0.0, 'earned_graded': 0.0 }], [{ 'earned_all': 0.0, 'earned_graded': None }, { 'earned_all': 0.0, 'earned_graded': 5.0 }], [{ 'earned_all': None, 'earned_graded': None }, { 'earned_all': 6.0, 'earned_graded': 5.0 }], [{ 'earned_all': 3.0, 'earned_graded': 2.0 }, { 'earned_all': 3.0, 'earned_graded': 2.0 }], ) @ddt.unpack def test_override_subsection_grade(self, override, expected): self.service.override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, earned_all=override['earned_all'], earned_graded=override['earned_graded'] ) override_obj = self.service.get_subsection_grade_override( self.user.id, self.course.id, self.subsection.location ) self.assertIsNotNone(override_obj) self.assertEqual(override_obj.earned_all_override, override['earned_all']) self.assertEqual(override_obj.earned_graded_override, override['earned_graded']) self.assertEqual( self.mock_signal.call_args, call( sender=None, user_id=self.user.id, course_id=unicode(self.course.id), usage_id=unicode(self.subsection.location), only_if_higher=False, modified=override_obj.modified, score_deleted=False, score_db_table=ScoreDatabaseTableEnum.overrides ) ) @freeze_time('2017-01-01') def test_undo_override_subsection_grade(self): override, _ = PersistentSubsectionGradeOverride.objects.update_or_create(grade=self.grade) self.service.undo_override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, ) override = self.service.get_subsection_grade_override(self.user.id, self.course.id, self.subsection.location) self.assertIsNone(override) self.assertEqual( self.mock_signal.call_args, call( sender=None, user_id=self.user.id, course_id=unicode(self.course.id), usage_id=unicode(self.subsection.location), only_if_higher=False, modified=datetime.now().replace(tzinfo=pytz.UTC), score_deleted=True, score_db_table=ScoreDatabaseTableEnum.overrides ) ) @ddt.data( ['edX/DemoX/Demo_Course', CourseKey.from_string('edX/DemoX/Demo_Course'), CourseKey], ['course-v1:edX+DemoX+Demo_Course', CourseKey.from_string('course-v1:edX+DemoX+Demo_Course'), CourseKey], [CourseKey.from_string('course-v1:edX+DemoX+Demo_Course'), CourseKey.from_string('course-v1:edX+DemoX+Demo_Course'), CourseKey], ['block-v1:edX+DemoX+Demo_Course+type@sequential+block@workflow', UsageKey.from_string('block-v1:edX+DemoX+Demo_Course+type@sequential+block@workflow'), UsageKey], [UsageKey.from_string('block-v1:edX+DemoX+Demo_Course+type@sequential+block@workflow'), UsageKey.from_string('block-v1:edX+DemoX+Demo_Course+type@sequential+block@workflow'), UsageKey], ) @ddt.unpack def test_get_key(self, input_key, output_key, key_cls): self.assertEqual(_get_key(input_key, key_cls), output_key) def test_should_override_grade_on_rejected_exam(self): self.assertTrue(self.service.should_override_grade_on_rejected_exam('course-v1:edX+DemoX+Demo_Course')) self.mock_waffle_flags.return_value = { REJECTED_EXAM_OVERRIDES_GRADE: MockWaffleFlag(False) } self.assertFalse(self.service.should_override_grade_on_rejected_exam('course-v1:edX+DemoX+Demo_Course'))
class GradesServiceTests(ModuleStoreTestCase): """ Tests for the Grades service """ def setUp(self): super(GradesServiceTests, self).setUp() self.service = GradesService() self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course', run='Spring2019') self.subsection = ItemFactory.create(parent=self.course, category="subsection", display_name="Subsection") self.subsection_without_grade = ItemFactory.create( parent=self.course, category="subsection", display_name="Subsection without grade" ) self.user = UserFactory() self.grade = PersistentSubsectionGrade.update_or_create_grade( user_id=self.user.id, course_id=self.course.id, usage_key=self.subsection.location, first_attempted=None, visible_blocks=[], earned_all=6.0, possible_all=6.0, earned_graded=5.0, possible_graded=5.0 ) self.signal_patcher = patch('lms.djangoapps.grades.signals.signals.SUBSECTION_OVERRIDE_CHANGED.send') self.mock_signal = self.signal_patcher.start() self.id_patcher = patch('lms.djangoapps.grades.services.create_new_event_transaction_id') self.mock_create_id = self.id_patcher.start() self.mock_create_id.return_value = 1 self.type_patcher = patch('lms.djangoapps.grades.services.set_event_transaction_type') self.mock_set_type = self.type_patcher.start() self.flag_patcher = patch('lms.djangoapps.grades.services.waffle_flags') self.mock_waffle_flags = self.flag_patcher.start() self.mock_waffle_flags.return_value = { REJECTED_EXAM_OVERRIDES_GRADE: MockWaffleFlag(True) } def tearDown(self): super(GradesServiceTests, self).tearDown() PersistentSubsectionGradeOverride.objects.all().delete() # clear out all previous overrides self.signal_patcher.stop() self.id_patcher.stop() self.type_patcher.stop() self.flag_patcher.stop() def subsection_grade_to_dict(self, grade): return { 'earned_all': grade.earned_all, 'earned_graded': grade.earned_graded } def subsection_grade_override_to_dict(self, grade): return { 'earned_all_override': grade.earned_all_override, 'earned_graded_override': grade.earned_graded_override } def test_get_subsection_grade(self): self.assertDictEqual(self.subsection_grade_to_dict(self.service.get_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location )), { 'earned_all': 6.0, 'earned_graded': 5.0 }) # test with id strings as parameters instead self.assertDictEqual(self.subsection_grade_to_dict(self.service.get_subsection_grade( user_id=self.user.id, course_key_or_id=unicode(self.course.id), usage_key_or_id=unicode(self.subsection.location) )), { 'earned_all': 6.0, 'earned_graded': 5.0 }) def test_get_subsection_grade_override(self): override, _ = PersistentSubsectionGradeOverride.objects.update_or_create(grade=self.grade) self.assertDictEqual(self.subsection_grade_override_to_dict(self.service.get_subsection_grade_override( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location )), { 'earned_all_override': override.earned_all_override, 'earned_graded_override': override.earned_graded_override }) override, _ = PersistentSubsectionGradeOverride.objects.update_or_create( grade=self.grade, defaults={ 'earned_all_override': 9.0 } ) # test with course key parameter as string instead self.assertDictEqual(self.subsection_grade_override_to_dict(self.service.get_subsection_grade_override( user_id=self.user.id, course_key_or_id=unicode(self.course.id), usage_key_or_id=self.subsection.location )), { 'earned_all_override': override.earned_all_override, 'earned_graded_override': override.earned_graded_override }) def _verify_override_history(self, override_history, history_action): self.assertIsNone(override_history.user) self.assertIsNotNone(override_history.created) self.assertEqual(override_history.feature, GradeOverrideFeatureEnum.proctoring) self.assertEqual(override_history.action, history_action) @ddt.data( { 'earned_all': 0.0, 'earned_graded': 0.0 }, { 'earned_all': 0.0, 'earned_graded': None }, { 'earned_all': None, 'earned_graded': None }, { 'earned_all': 3.0, 'earned_graded': 2.0 }, ) def test_override_subsection_grade(self, override): self.service.override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, earned_all=override['earned_all'], earned_graded=override['earned_graded'] ) override_obj = self.service.get_subsection_grade_override( self.user.id, self.course.id, self.subsection.location ) self.assertIsNotNone(override_obj) self.assertEqual(override_obj.earned_all_override, override['earned_all']) self.assertEqual(override_obj.earned_graded_override, override['earned_graded']) self.assertEqual( self.mock_signal.call_args, call( sender=None, user_id=self.user.id, course_id=unicode(self.course.id), usage_id=unicode(self.subsection.location), only_if_higher=False, modified=override_obj.modified, score_deleted=False, score_db_table=ScoreDatabaseTableEnum.overrides ) ) override_history = PersistentSubsectionGradeOverrideHistory.objects.filter(override_id=override_obj.id).first() self._verify_override_history(override_history, PersistentSubsectionGradeOverrideHistory.CREATE_OR_UPDATE) def test_override_subsection_grade_no_psg(self): """ When there is no PersistentSubsectionGrade associated with the learner and subsection to override, one should be created. """ earned_all_override = 2 earned_graded_override = 0 self.service.override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection_without_grade.location, earned_all=earned_all_override, earned_graded=earned_graded_override ) # Assert that a new PersistentSubsectionGrade was created subsection_grade = self.service.get_subsection_grade( self.user.id, self.course.id, self.subsection_without_grade.location ) self.assertIsNotNone(subsection_grade) self.assertEqual(0, subsection_grade.earned_all) self.assertEqual(0, subsection_grade.earned_graded) # Now assert things about the grade override override_obj = self.service.get_subsection_grade_override( self.user.id, self.course.id, self.subsection_without_grade.location ) self.assertIsNotNone(override_obj) self.assertEqual(override_obj.earned_all_override, earned_all_override) self.assertEqual(override_obj.earned_graded_override, earned_graded_override) self.assertEqual( self.mock_signal.call_args, call( sender=None, user_id=self.user.id, course_id=unicode(self.course.id), usage_id=unicode(self.subsection_without_grade.location), only_if_higher=False, modified=override_obj.modified, score_deleted=False, score_db_table=ScoreDatabaseTableEnum.overrides ) ) override_history = PersistentSubsectionGradeOverrideHistory.objects.filter(override_id=override_obj.id).first() self._verify_override_history(override_history, PersistentSubsectionGradeOverrideHistory.CREATE_OR_UPDATE) @freeze_time('2017-01-01') def test_undo_override_subsection_grade(self): override, _ = PersistentSubsectionGradeOverride.objects.update_or_create(grade=self.grade) override_id = override.id self.service.undo_override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, ) override = self.service.get_subsection_grade_override(self.user.id, self.course.id, self.subsection.location) self.assertIsNone(override) self.assertEqual( self.mock_signal.call_args, call( sender=None, user_id=self.user.id, course_id=unicode(self.course.id), usage_id=unicode(self.subsection.location), only_if_higher=False, modified=datetime.now().replace(tzinfo=pytz.UTC), score_deleted=True, score_db_table=ScoreDatabaseTableEnum.overrides ) ) override_history = PersistentSubsectionGradeOverrideHistory.objects.filter(override_id=override_id).first() self._verify_override_history(override_history, PersistentSubsectionGradeOverrideHistory.DELETE) @freeze_time('2018-01-01') def test_undo_override_subsection_grade_without_grade(self): """ Test exception handling of `undo_override_subsection_grade` when PersistentSubsectionGrade does not exist. """ self.grade.delete() try: self.service.undo_override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, ) except PersistentSubsectionGrade.DoesNotExist: assert False, 'Exception raised unexpectedly' self.assertFalse(self.mock_signal.called) def test_should_override_grade_on_rejected_exam(self): self.assertTrue(self.service.should_override_grade_on_rejected_exam('course-v1:edX+DemoX+Demo_Course')) self.mock_waffle_flags.return_value = { REJECTED_EXAM_OVERRIDES_GRADE: MockWaffleFlag(False) } self.assertFalse(self.service.should_override_grade_on_rejected_exam('course-v1:edX+DemoX+Demo_Course'))
class GradesServiceTests(ModuleStoreTestCase): """ Tests for the Grades service """ def setUp(self): super(GradesServiceTests, self).setUp() self.service = GradesService() self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course') self.subsection = ItemFactory.create(parent=self.course, category="subsection", display_name="Subsection") self.user = UserFactory() self.grade = PersistentSubsectionGrade.update_or_create_grade( user_id=self.user.id, course_id=self.course.id, usage_key=self.subsection.location, first_attempted=None, visible_blocks=[], earned_all=6.0, possible_all=6.0, earned_graded=5.0, possible_graded=5.0 ) self.signal_patcher = patch('lms.djangoapps.grades.signals.signals.SUBSECTION_OVERRIDE_CHANGED.send') self.mock_signal = self.signal_patcher.start() self.id_patcher = patch('lms.djangoapps.grades.services.create_new_event_transaction_id') self.mock_create_id = self.id_patcher.start() self.mock_create_id.return_value = 1 self.type_patcher = patch('lms.djangoapps.grades.services.set_event_transaction_type') self.mock_set_type = self.type_patcher.start() self.flag_patcher = patch('lms.djangoapps.grades.services.waffle_flags') self.mock_waffle_flags = self.flag_patcher.start() self.mock_waffle_flags.return_value = { REJECTED_EXAM_OVERRIDES_GRADE: MockWaffleFlag(True) } def tearDown(self): PersistentSubsectionGradeOverride.objects.all().delete() # clear out all previous overrides self.signal_patcher.stop() self.id_patcher.stop() self.type_patcher.stop() self.flag_patcher.stop() def subsection_grade_to_dict(self, grade): return { 'earned_all': grade.earned_all, 'earned_graded': grade.earned_graded } def subsection_grade_override_to_dict(self, grade): return { 'earned_all_override': grade.earned_all_override, 'earned_graded_override': grade.earned_graded_override } def test_get_subsection_grade(self): self.assertDictEqual(self.subsection_grade_to_dict(self.service.get_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location )), { 'earned_all': 6.0, 'earned_graded': 5.0 }) # test with id strings as parameters instead self.assertDictEqual(self.subsection_grade_to_dict(self.service.get_subsection_grade( user_id=self.user.id, course_key_or_id=unicode(self.course.id), usage_key_or_id=unicode(self.subsection.location) )), { 'earned_all': 6.0, 'earned_graded': 5.0 }) def test_get_subsection_grade_override(self): override, _ = PersistentSubsectionGradeOverride.objects.update_or_create(grade=self.grade) self.assertDictEqual(self.subsection_grade_override_to_dict(self.service.get_subsection_grade_override( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location )), { 'earned_all_override': override.earned_all_override, 'earned_graded_override': override.earned_graded_override }) override, _ = PersistentSubsectionGradeOverride.objects.update_or_create( grade=self.grade, defaults={ 'earned_all_override': 9.0 } ) # test with id strings as parameters instead self.assertDictEqual(self.subsection_grade_override_to_dict(self.service.get_subsection_grade_override( user_id=self.user.id, course_key_or_id=unicode(self.course.id), usage_key_or_id=unicode(self.subsection.location) )), { 'earned_all_override': override.earned_all_override, 'earned_graded_override': override.earned_graded_override }) @ddt.data( [{ 'earned_all': 0.0, 'earned_graded': 0.0 }, { 'earned_all': 0.0, 'earned_graded': 0.0 }], [{ 'earned_all': 0.0, 'earned_graded': None }, { 'earned_all': 0.0, 'earned_graded': 5.0 }], [{ 'earned_all': None, 'earned_graded': None }, { 'earned_all': 6.0, 'earned_graded': 5.0 }], [{ 'earned_all': 3.0, 'earned_graded': 2.0 }, { 'earned_all': 3.0, 'earned_graded': 2.0 }], ) @ddt.unpack def test_override_subsection_grade(self, override, expected): self.service.override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, earned_all=override['earned_all'], earned_graded=override['earned_graded'] ) override_obj = self.service.get_subsection_grade_override( self.user.id, self.course.id, self.subsection.location ) self.assertIsNotNone(override_obj) self.assertEqual(override_obj.earned_all_override, override['earned_all']) self.assertEqual(override_obj.earned_graded_override, override['earned_graded']) self.assertEqual( self.mock_signal.call_args, call( sender=None, user_id=self.user.id, course_id=unicode(self.course.id), usage_id=unicode(self.subsection.location), only_if_higher=False, modified=override_obj.modified, score_deleted=False, score_db_table=ScoreDatabaseTableEnum.overrides ) ) @freeze_time('2017-01-01') def test_undo_override_subsection_grade(self): override, _ = PersistentSubsectionGradeOverride.objects.update_or_create(grade=self.grade) self.service.undo_override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, ) override = self.service.get_subsection_grade_override(self.user.id, self.course.id, self.subsection.location) self.assertIsNone(override) self.assertEqual( self.mock_signal.call_args, call( sender=None, user_id=self.user.id, course_id=unicode(self.course.id), usage_id=unicode(self.subsection.location), only_if_higher=False, modified=datetime.now().replace(tzinfo=pytz.UTC), score_deleted=True, score_db_table=ScoreDatabaseTableEnum.overrides ) ) @freeze_time('2018-01-01') def test_undo_override_subsection_grade_without_grade(self): """ Test exception handling of `undo_override_subsection_grade` when PersistentSubsectionGrade does not exist. """ self.grade.delete() try: self.service.undo_override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, ) except PersistentSubsectionGrade.DoesNotExist: assert False, 'Exception raised unexpectedly' self.assertFalse(self.mock_signal.called) def test_should_override_grade_on_rejected_exam(self): self.assertTrue(self.service.should_override_grade_on_rejected_exam('course-v1:edX+DemoX+Demo_Course')) self.mock_waffle_flags.return_value = { REJECTED_EXAM_OVERRIDES_GRADE: MockWaffleFlag(False) } self.assertFalse(self.service.should_override_grade_on_rejected_exam('course-v1:edX+DemoX+Demo_Course'))
class GradesServiceTests(ModuleStoreTestCase): """ Tests for the Grades service """ def setUp(self): super(GradesServiceTests, self).setUp() self.service = GradesService() self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course', run='Spring2019') self.subsection = ItemFactory.create(parent=self.course, category="subsection", display_name="Subsection") self.subsection_without_grade = ItemFactory.create( parent=self.course, category="subsection", display_name="Subsection without grade") self.user = UserFactory() self.grade = PersistentSubsectionGrade.update_or_create_grade( user_id=self.user.id, course_id=self.course.id, usage_key=self.subsection.location, first_attempted=None, visible_blocks=[], earned_all=6.0, possible_all=6.0, earned_graded=5.0, possible_graded=5.0) self.signal_patcher = patch( 'lms.djangoapps.grades.signals.signals.SUBSECTION_OVERRIDE_CHANGED.send' ) self.mock_signal = self.signal_patcher.start() self.id_patcher = patch( 'lms.djangoapps.grades.api.create_new_event_transaction_id') self.mock_create_id = self.id_patcher.start() self.mock_create_id.return_value = 1 self.type_patcher = patch( 'lms.djangoapps.grades.api.set_event_transaction_type') self.mock_set_type = self.type_patcher.start() self.flag_patcher = patch( 'lms.djangoapps.grades.config.waffle.waffle_flags') self.mock_waffle_flags = self.flag_patcher.start() self.mock_waffle_flags.return_value = { REJECTED_EXAM_OVERRIDES_GRADE: MockWaffleFlag(True) } def tearDown(self): super(GradesServiceTests, self).tearDown() PersistentSubsectionGradeOverride.objects.all().delete( ) # clear out all previous overrides self.signal_patcher.stop() self.id_patcher.stop() self.type_patcher.stop() self.flag_patcher.stop() def subsection_grade_to_dict(self, grade): return { 'earned_all': grade.earned_all, 'earned_graded': grade.earned_graded } def subsection_grade_override_to_dict(self, grade): return { 'earned_all_override': grade.earned_all_override, 'earned_graded_override': grade.earned_graded_override } def test_get_subsection_grade(self): self.assertDictEqual( self.subsection_grade_to_dict( self.service.get_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location)), { 'earned_all': 6.0, 'earned_graded': 5.0 }) # test with id strings as parameters instead self.assertDictEqual( self.subsection_grade_to_dict( self.service.get_subsection_grade( user_id=self.user.id, course_key_or_id=six.text_type(self.course.id), usage_key_or_id=six.text_type(self.subsection.location))), { 'earned_all': 6.0, 'earned_graded': 5.0 }) def test_get_subsection_grade_override(self): override, _ = PersistentSubsectionGradeOverride.objects.update_or_create( grade=self.grade) self.assertDictEqual( self.subsection_grade_override_to_dict( self.service.get_subsection_grade_override( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location)), { 'earned_all_override': override.earned_all_override, 'earned_graded_override': override.earned_graded_override }) override, _ = PersistentSubsectionGradeOverride.objects.update_or_create( grade=self.grade, defaults={'earned_all_override': 9.0}) # test with course key parameter as string instead self.assertDictEqual( self.subsection_grade_override_to_dict( self.service.get_subsection_grade_override( user_id=self.user.id, course_key_or_id=six.text_type(self.course.id), usage_key_or_id=self.subsection.location)), { 'earned_all_override': override.earned_all_override, 'earned_graded_override': override.earned_graded_override }) def _verify_override_history(self, override_history, history_action): self.assertIsNone(override_history.user) self.assertIsNotNone(override_history.created) self.assertEqual(override_history.feature, GradeOverrideFeatureEnum.proctoring) self.assertEqual(override_history.action, history_action) @ddt.data( { 'earned_all': 0.0, 'earned_graded': 0.0 }, { 'earned_all': 0.0, 'earned_graded': None }, { 'earned_all': None, 'earned_graded': None }, { 'earned_all': 3.0, 'earned_graded': 2.0 }, ) def test_override_subsection_grade(self, override): self.service.override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, earned_all=override['earned_all'], earned_graded=override['earned_graded']) override_obj = self.service.get_subsection_grade_override( self.user.id, self.course.id, self.subsection.location) self.assertIsNotNone(override_obj) self.assertEqual(override_obj.earned_all_override, override['earned_all']) self.assertEqual(override_obj.earned_graded_override, override['earned_graded']) self.assertEqual( self.mock_signal.call_args, call(sender=None, user_id=self.user.id, course_id=six.text_type(self.course.id), usage_id=six.text_type(self.subsection.location), only_if_higher=False, modified=override_obj.modified, score_deleted=False, score_db_table=ScoreDatabaseTableEnum.overrides)) override_history = PersistentSubsectionGradeOverrideHistory.objects.filter( override_id=override_obj.id).first() self._verify_override_history( override_history, PersistentSubsectionGradeOverrideHistory.CREATE_OR_UPDATE) def test_override_subsection_grade_no_psg(self): """ When there is no PersistentSubsectionGrade associated with the learner and subsection to override, one should be created. """ earned_all_override = 2 earned_graded_override = 0 self.service.override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection_without_grade.location, earned_all=earned_all_override, earned_graded=earned_graded_override) # Assert that a new PersistentSubsectionGrade was created subsection_grade = self.service.get_subsection_grade( self.user.id, self.course.id, self.subsection_without_grade.location) self.assertIsNotNone(subsection_grade) self.assertEqual(0, subsection_grade.earned_all) self.assertEqual(0, subsection_grade.earned_graded) # Now assert things about the grade override override_obj = self.service.get_subsection_grade_override( self.user.id, self.course.id, self.subsection_without_grade.location) self.assertIsNotNone(override_obj) self.assertEqual(override_obj.earned_all_override, earned_all_override) self.assertEqual(override_obj.earned_graded_override, earned_graded_override) self.assertEqual( self.mock_signal.call_args, call(sender=None, user_id=self.user.id, course_id=six.text_type(self.course.id), usage_id=six.text_type( self.subsection_without_grade.location), only_if_higher=False, modified=override_obj.modified, score_deleted=False, score_db_table=ScoreDatabaseTableEnum.overrides)) override_history = PersistentSubsectionGradeOverrideHistory.objects.filter( override_id=override_obj.id).first() self._verify_override_history( override_history, PersistentSubsectionGradeOverrideHistory.CREATE_OR_UPDATE) @freeze_time('2017-01-01') def test_undo_override_subsection_grade(self): override, _ = PersistentSubsectionGradeOverride.objects.update_or_create( grade=self.grade, system=GradeOverrideFeatureEnum.proctoring) override_id = override.id self.service.undo_override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, ) override = self.service.get_subsection_grade_override( self.user.id, self.course.id, self.subsection.location) self.assertIsNone(override) self.assertEqual( self.mock_signal.call_args, call(sender=None, user_id=self.user.id, course_id=six.text_type(self.course.id), usage_id=six.text_type(self.subsection.location), only_if_higher=False, modified=datetime.now().replace(tzinfo=pytz.UTC), score_deleted=True, score_db_table=ScoreDatabaseTableEnum.overrides)) override_history = PersistentSubsectionGradeOverrideHistory.objects.filter( override_id=override_id).first() self._verify_override_history( override_history, PersistentSubsectionGradeOverrideHistory.DELETE) def test_undo_override_subsection_grade_across_features(self): """ Test that deletion of subsection grade overrides requested by one feature doesn't delete overrides created by another feature. """ override, _ = PersistentSubsectionGradeOverride.objects.update_or_create( grade=self.grade, system=GradeOverrideFeatureEnum.gradebook) self.service.undo_override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, feature=GradeOverrideFeatureEnum.proctoring, ) override = self.service.get_subsection_grade_override( self.user.id, self.course.id, self.subsection.location) self.assertIsNotNone(override) @freeze_time('2018-01-01') def test_undo_override_subsection_grade_without_grade(self): """ Test exception handling of `undo_override_subsection_grade` when PersistentSubsectionGrade does not exist. """ self.grade.delete() try: self.service.undo_override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, ) except PersistentSubsectionGrade.DoesNotExist: assert False, 'Exception raised unexpectedly' self.assertFalse(self.mock_signal.called) def test_should_override_grade_on_rejected_exam(self): self.assertTrue( self.service.should_override_grade_on_rejected_exam( 'course-v1:edX+DemoX+Demo_Course')) self.mock_waffle_flags.return_value = { REJECTED_EXAM_OVERRIDES_GRADE: MockWaffleFlag(False) } self.assertFalse( self.service.should_override_grade_on_rejected_exam( 'course-v1:edX+DemoX+Demo_Course'))