def test_get_best_proctored_exam_grade(self): """ Test get_best_proctorate_exam_grade to return a passed exam with the highest score """ mmtrack = MMTrack( user=self.user, program=self.program_financial_aid, edx_user_data=self.cached_edx_user_data ) finaid_course = self.crun_fa.course last_week = now_in_utc() - timedelta(weeks=1) ProctoredExamGradeFactory.create(user=self.user, course=finaid_course, passed=False, percentage_grade=0.6) assert mmtrack.get_best_proctored_exam_grade(finaid_course) is None best_exam = ProctoredExamGradeFactory.create( user=self.user, course=finaid_course, passed=True, percentage_grade=0.9, exam_run__date_grades_available=last_week ) assert mmtrack.get_best_proctored_exam_grade(finaid_course) == best_exam ProctoredExamGradeFactory.create( user=self.user, course=finaid_course, passed=True, percentage_grade=0.8, exam_run__date_grades_available=last_week ) assert mmtrack.get_best_proctored_exam_grade(finaid_course) == best_exam
def test_combined_grade_created_updated(self): """ Test create and update """ combined_grade_qset = CombinedFinalGrade.objects.filter(user=self.user, course=self.course_run.course) # no passing final grade api.update_or_create_combined_final_grade(self.user, self.course_run.course) assert combined_grade_qset.exists() is False FinalGradeFactory.create(user=self.user, course_run__course=self.course_run.course, grade=0.6, passed=True) # no passing exam grade api.update_or_create_combined_final_grade(self.user, self.course_run.course) assert combined_grade_qset.exists() is False ProctoredExamGradeFactory.create( user=self.user, course=self.course_run.course, percentage_grade=0.8, passed=True, exam_run=self.exam_run ) # now should create combined grade api.update_or_create_combined_final_grade(self.user, self.course_run.course) assert combined_grade_qset.exists() is True # now update it with a new grade FinalGradeFactory.create(user=self.user, course_run__course=self.course_run.course, grade=0.8, passed=True) api.update_or_create_combined_final_grade(self.user, self.course_run.course) assert combined_grade_qset.first().grade == 80.0
def test_create_exam_grade(self, update_grade_mock, mock_on_commit): """ Test that update_or_create_combined_final_grade is called when a proctored exam grade is created or updated """ exam_grade = ProctoredExamGradeFactory.create(user=self.user, course=self.course) update_grade_mock.assert_called_once_with(self.user, self.course) exam_grade.save() assert update_grade_mock.call_count == 2 # create another exam grade for a different exam run ProctoredExamGradeFactory.create(user=self.user, course=self.course) assert update_grade_mock.call_count == 3
def setUpTestData(cls): cls.proct_grade = ProctoredExamGradeFactory() cls.proct_grade_2 = ProctoredExamGradeFactory( user=cls.proct_grade.user, course=cls.proct_grade.course) cls.expected_fields = sorted([ 'exam_date', 'passing_score', 'score', 'grade', 'client_authorization_id', 'passed', 'percentage_grade', ])
def test_for_user_course(self): """Tests that for_user_course() does not return unavailable grades""" user = UserFactory.create() course = CourseFactory.create() available_grade = ProctoredExamGradeFactory.create( user=user, course=course, exam_run__course=course, exam_run__eligibility_past=True) ProctoredExamGradeFactory.create(user=user, course=course, exam_run__course=course, exam_run__eligibility_future=True) grades = ProctoredExamGrade.for_user_course(user, course) assert list(grades) == [available_grade]
def test_create_exam_grade(self, update_grade_mock, mock_on_commit): """ Test that update_existing_combined_final_grade_for_exam_run is called when a proctored exam grade is created and ExamRun updated """ exam_grade = ProctoredExamGradeFactory.create(user=self.user, course=self.course) assert update_grade_mock.call_count == 0 exam_grade.exam_run.save() assert update_grade_mock.call_count == 1 # create another exam grade for a different exam run exam_grade = ProctoredExamGradeFactory.create(user=self.user, course=self.course) assert update_grade_mock.call_count == 1 exam_grade.exam_run.save() assert update_grade_mock.call_count == 2
def test_update_existing_combined_final_grade_for_exam_run(self, update_or_create_mock): """ Test update_existing_combined_final_grade_for_exam_run """ ProctoredExamGradeFactory.create( user=self.user, course=self.course_run.course, percentage_grade=0.6, passed=True, exam_run=self.exam_run ) FinalGradeFactory.create(user=self.user, course_run__course=self.course_run.course, grade=0.8, passed=True) # should only update if combined grade already exists for user api.update_existing_combined_final_grade_for_exam_run(self.exam_run) assert update_or_create_mock.called is False CombinedFinalGrade.objects.create(user=self.user, course=self.course_run.course, grade=0.7) # should call it once since there is an existing combined grade api.update_existing_combined_final_grade_for_exam_run(self.exam_run) update_or_create_mock.assert_called_once_with(self.user, self.course_run.course) exam_run = ExamRunFactory.create( course=self.course_run.course, date_grades_available=now_in_utc() - timedelta(weeks=1) ) ProctoredExamGradeFactory.create( user=self.user, course=self.course_run.course, percentage_grade=0.8, passed=True, exam_run=exam_run ) # should call it again for a different exam grade api.update_existing_combined_final_grade_for_exam_run(exam_run) assert update_or_create_mock.call_count == 2
def test_set_score(self, grade_adjust, expected_passed_value, expected_grade_str): """Tests that the set_score helper method sets score-related fields appropriately""" passing_score = 60.0 grade = ProctoredExamGradeFactory.build( passing_score=passing_score, score=None, percentage_grade=None, passed=None, ) grade.set_score(passing_score + grade_adjust) assert grade.score == passing_score + grade_adjust assert grade.percentage_grade == grade.score / 100.0 assert grade.passed == expected_passed_value assert grade.grade == expected_grade_str
def test_create_combined_final_grade(mocker): """ Test create_combined_final_grade creates the grade when it is missing """ update_mock = mocker.patch( 'grades.api.update_or_create_combined_final_grade', autospec=True) course_run = CourseRunFactory.create( freeze_grade_date=now_in_utc() - timedelta(days=1), course__program__financial_aid_availability=True, course__program__live=True) course = course_run.course CourseRunGradingStatus.objects.create(course_run=course_run, status='complete') # Create exam run for course with date_grades_available True exam_run_grades_available = ExamRunFactory.create( course=course, date_grades_available=now_in_utc() - timedelta(weeks=1)) exam_grades = ProctoredExamGradeFactory.create_batch( 5, course=course, exam_run=exam_run_grades_available, passed=True, ) for exam_grade in exam_grades[:3]: CombinedFinalGrade.objects.create(user=exam_grade.user, course=course, grade=0.7) # Only 3 users will have combined grades for exam_grade in exam_grades[3:]: FinalGradeFactory.create(user=exam_grade.user, course_run=course_run, passed=True) tasks.create_combined_final_grades.delay() assert update_mock.call_count == 2 update_mock.assert_has_calls( [call(exam_grades[3].user, course), call(exam_grades[4].user, course)], any_order=True)
def test_create_combined_final_grade(mocker): """ Test create_combined_final_grade creates the grade when it is missing """ update_mock = mocker.patch('grades.api.update_or_create_combined_final_grade', autospec=True) course_run = CourseRunFactory.create( freeze_grade_date=now_in_utc()-timedelta(days=1), course__program__financial_aid_availability=True, course__program__live=True ) course = course_run.course CourseRunGradingStatus.objects.create(course_run=course_run, status='complete') # Create exam run for course with date_grades_available True exam_run_grades_available = ExamRunFactory.create( course=course, date_grades_available=now_in_utc() - timedelta(weeks=1)) exam_grades = ProctoredExamGradeFactory.create_batch( 5, course=course, exam_run=exam_run_grades_available, passed=True, ) for exam_grade in exam_grades[:3]: CombinedFinalGrade.objects.create(user=exam_grade.user, course=course, grade=0.7) # Only 3 users will have combined grades for exam_grade in exam_grades[3:]: FinalGradeFactory.create(user=exam_grade.user, course_run=course_run, passed=True) tasks.create_combined_final_grades.delay() assert update_mock.call_count == 2 update_mock.assert_has_calls( [call(exam_grades[3].user, course), call(exam_grades[4].user, course)], any_order=True )
def create_exams(self, current, edx_passed, exam_passed, new_offering, can_schedule, future_exam, need_to_pay): """Create an exam and mark it and the related course as passed or not passed""" # pylint: disable-msg=too-many-arguments self.make_fa_program_enrollment(FinancialAidStatus.AUTO_APPROVED) course = Course.objects.get(title='Digital Learning 200') if current: course_run = CourseRunFactory(course=course) call_command( "alter_data", 'set_to_enrolled', '--username', 'staff', '--course-run-key', course_run.edx_course_key ) FinalGradeFactory.create( user=self.user, course_run=course_run, grade=0.8 if edx_passed else 0.2, passed=True ) else: if edx_passed: call_command( "alter_data", 'set_to_passed', '--username', 'staff', '--course-title', 'Digital Learning 200', '--grade', '75', ) else: call_command( "alter_data", 'set_to_failed', '--username', 'staff', '--course-title', 'Digital Learning 200', '--grade', '45', ) course_run = course.courserun_set.first() ExamProfileFactory.create(status='success', profile=self.user.profile) exam_run = ExamRunFactory.create(course=course, eligibility_past=True, scheduling_past=True) ExamAuthorizationFactory.create( user=self.user, course=course, exam_run=exam_run, status='success', exam_taken=True ) LineFactory.create( order__status=Order.FULFILLED, course_key=course_run ) ProctoredExamGradeFactory.create( user=self.user, course=course, exam_run=exam_run, passed=exam_passed, percentage_grade=0.8 if exam_passed else 0.3 ) if new_offering: CourseRunFactory.create(course=course) if can_schedule: exam_run = ExamRunFactory.create( scheduling_past=False, scheduling_future=False, authorized=True, course=course ) ExamAuthorizationFactory.create( user=self.user, course=course, exam_run=exam_run, status='success', ) if future_exam: ExamRunFactory.create( scheduling_past=False, scheduling_future=True, authorized=True, course=course ) if need_to_pay: exam_run = ExamRunFactory.create(course=course, eligibility_past=True, scheduling_past=True) ExamAuthorizationFactory.create( user=self.user, course=course, exam_run=exam_run, status='success', exam_taken=True ) ProctoredExamGradeFactory.create( user=self.user, course=course, exam_run=exam_run, passed=False, percentage_grade=0.3 )
def test_set_score_none(self): """Tests that set_score fails if the provided score is None""" grade = ProctoredExamGradeFactory.build() with self.assertRaises(TypeError): grade.set_score(None)
def test_generate_course_certificates(): """ Test that generate_course_certificates_for_fa_students creates certificates for appropriate FinalGrades """ program = ProgramFactory.create(financial_aid_availability=True, live=True) week_ago = now_in_utc() - timedelta(weeks=1) # Course without exams course = CourseFactory.create(program=program) passed_final_grades = FinalGradeFactory.create_batch( 4, course_run__course=course, course_run__freeze_grade_date=week_ago, passed=True ) # create a duplicate final grade for course for user FinalGradeFactory.create( user=passed_final_grades[0].user, course_run__course=course, course_run__freeze_grade_date=week_ago, passed=True ) # 2nd course for user course_2 = CourseFactory.create(program=program) FinalGradeFactory.create( course_run__course=course_2, course_run__freeze_grade_date=week_ago, passed=True, user=passed_final_grades[1].user ) # Another non-fa course non_fa_course = CourseFactory.create(program__financial_aid_availability=False) # Course with exams # Create two exam runs for course with different date_grades_available exam_run_grades_available = ExamRunFactory.create( course__program=program, date_grades_available=now_in_utc() - timedelta(weeks=1)) course_with_exams = exam_run_grades_available.course exam_run_no_grades = ExamRunFactory.create( course=course_with_exams, date_grades_available=now_in_utc() + timedelta(weeks=1)) passed_final_grades_with_exam = FinalGradeFactory.create_batch( 6, course_run__course=course_with_exams, passed=True ) # Create ProctoredExamGrade records with a mix of passed and failed outcomes, and exam grade availability final_grades_with_passed_exam = passed_final_grades_with_exam[:2] ProctoredExamGradeFactory.create_batch( 2, user=factory.Iterator([final_grade.user for final_grade in final_grades_with_passed_exam]), course=course_with_exams, exam_run=exam_run_grades_available, passed=True, ) ProctoredExamGradeFactory.create_batch( 2, user=factory.Iterator([final_grade.user for final_grade in passed_final_grades_with_exam[2:4]]), course=course_with_exams, exam_run=exam_run_no_grades, passed=True, ) ProctoredExamGradeFactory.create_batch( 2, user=factory.Iterator([final_grade.user for final_grade in passed_final_grades_with_exam[4:6]]), course=course_with_exams, passed=False, ) # course runs need to have CourseRunGradingStatus to get certificates all_grades = FinalGrade.objects.filter(course_run__course__in=[course, course_2, course_with_exams, non_fa_course]) for final_grade in all_grades: CourseRunGradingStatus.objects.create(course_run=final_grade.course_run, status='complete') tasks.generate_course_certificates_for_fa_students.delay() # Make sure that certificates were created only for passed and 'complete' status FinalGrades that either # had no course exam, or had a passed ProctoredExamGrade. certificates = MicromastersCourseCertificate.objects.filter(course__in=[course, course_2, course_with_exams]) assert certificates.count() == 7 expected_certificate_final_grades = passed_final_grades + final_grades_with_passed_exam assert set(certificates.values_list('user', flat=True)) == { final_grade.user.id for final_grade in expected_certificate_final_grades }
def test_generate_course_certificates(): """ Test that generate_course_certificates_for_fa_students creates certificates for appropriate FinalGrades """ program = ProgramFactory.create(financial_aid_availability=True, live=True) week_ago = now_in_utc() - timedelta(weeks=1) # Course without exams course = CourseFactory.create(program=program) passed_final_grades = FinalGradeFactory.create_batch( 4, course_run__course=course, course_run__freeze_grade_date=week_ago, passed=True ) # create a duplicate final grade for course for user FinalGradeFactory.create( user=passed_final_grades[0].user, course_run__course=course, course_run__freeze_grade_date=week_ago, passed=True ) # 2nd course for user course_2 = CourseFactory.create(program=program) FinalGradeFactory.create( course_run__course=course_2, course_run__freeze_grade_date=week_ago, passed=True, user=passed_final_grades[1].user ) # Another non-fa course non_fa_course = CourseFactory.create(program__financial_aid_availability=False) # Course with exams # Create two exam runs for course with different date_grades_available exam_run_grades_available = ExamRunFactory.create( course__program=program, date_grades_available=now_in_utc() - timedelta(weeks=1)) course_with_exams = exam_run_grades_available.course exam_run_no_grades = ExamRunFactory.create( course=course_with_exams, date_grades_available=now_in_utc() + timedelta(weeks=1)) passed_final_grades_with_exam = FinalGradeFactory.create_batch( 6, course_run__course=course_with_exams, passed=True ) # Create ProctoredExamGrade records with a mix of passed and failed outcomes, and exam grade availability final_grades_with_passed_exam = passed_final_grades_with_exam[:2] ProctoredExamGradeFactory.create_batch( 2, user=factory.Iterator([final_grade.user for final_grade in final_grades_with_passed_exam]), course=course_with_exams, exam_run=exam_run_grades_available, passed=True, ) ProctoredExamGradeFactory.create_batch( 2, user=factory.Iterator([final_grade.user for final_grade in passed_final_grades_with_exam[2:4]]), course=course_with_exams, exam_run=exam_run_no_grades, passed=True, ) ProctoredExamGradeFactory.create_batch( 2, user=factory.Iterator([final_grade.user for final_grade in passed_final_grades_with_exam[4:6]]), course=course_with_exams, passed=False, ) # course runs need to have CourseRunGradingStatus to get certificates all_grades = FinalGrade.objects.filter(course_run__course__in=[course, course_2, course_with_exams, non_fa_course]) for final_grade in all_grades: CourseRunGradingStatus.objects.create(course_run=final_grade.course_run, status='complete') tasks.generate_course_certificates_for_fa_students.delay() # Make sure that certificates were created only for passed and 'complete' status FinalGrades that either # had no course exam, or had a passed ProctoredExamGrade. certificates = MicromastersCourseCertificate.objects.filter(course__in=[course, course_2, course_with_exams]) assert certificates.count() == 7 expected_certificate_final_grades = passed_final_grades + final_grades_with_passed_exam assert set(certificates.values_list('user', flat=True)) == set( [final_grade.user.id for final_grade in expected_certificate_final_grades] )