def test_catalog_program_serializer(has_page, has_thumbnail): """Tests that the catalog serializer returns a correct data structure""" page = ProgramPageFactory.create( has_thumbnail=has_thumbnail) if has_page else None program = page.program if page else ProgramFactory.create() courses = CourseFactory.create_batch(3, program=program) for course in courses: CourseRunFactory.create_batch(2, course=course) faculty_name = "faculty" if has_page: ProgramFaculty.objects.create( program_page=page, name=faculty_name, ) serialized = CatalogProgramSerializer(program).data # coerce OrderedDict objects to dict serialized = { **serialized, "courses": [{ **course, "course_runs": [dict(run) for run in course["course_runs"]] } for course in serialized["courses"]] } assert serialized == { "id": program.id, "title": program.title, "programpage_url": page.get_full_url() if has_page else None, "thumbnail_url": (page.thumbnail_image.get_rendition('fill-300x186').url if has_page and has_thumbnail else None), "courses": [{ "id": course.id, "edx_key": course.edx_key, "position_in_program": course.position_in_program, "course_runs": [{ "id": course_run.id, "edx_course_key": course_run.edx_course_key, } for course_run in course.courserun_set.all()] } for course in courses], 'topics': [{ 'name': topic.name } for topic in program.topics.iterator()], "instructors": [{ "name": faculty_name }] if has_page else [], "start_date": courses[0].first_unexpired_run().start_date, "enrollment_start": courses[0].first_unexpired_run().enrollment_start, "end_date": courses[-1].courserun_set.last().end_date, "total_price": str(program.price * program.num_required_courses), }
def test_program_view(client, user, home_page, is_enrolled, has_product, has_unexpired_run, is_anonymous): """ Test that the program detail view has the right context and shows the right HTML for the enroll/view button """ program = ProgramFactory.create(live=True, page__parent=home_page) if has_unexpired_run: now = now_in_utc() CourseRunFactory.create_batch( 3, course=CourseFactory.create(program=program, live=True, position_in_program=1), live=True, start_date=now + timedelta(hours=2), ) if has_product: product_id = ProductVersionFactory.create(product=ProductFactory( content_object=program)).product.id else: product_id = None if is_enrolled: ProgramEnrollmentFactory.create(user=user, program=program) if not is_anonymous: client.force_login(user) resp = client.get(program.page.get_url()) assert resp.context["user"] == user if not is_anonymous else AnonymousUser( ) assert resp.context["product_id"] == product_id assert resp.context["enrolled"] == (is_enrolled and not is_anonymous) # Anynoymous users don't see the enrolled/enroll-now button. # For logged in users: # a) product should exist, next courserun should be there, user not enrolled (enroll now button) # b) user is enrolled (enrolled button) has_button = ((has_product and has_unexpired_run and not is_enrolled) or is_enrolled) and not is_anonymous url = "" # make linter happy class_name = "" if not is_anonymous: if not is_enrolled and has_product and has_unexpired_run: url = f'{reverse("checkout-page")}?product={product_id}' class_name = "enroll-now" if is_enrolled: url = reverse("user-dashboard") class_name = "enrolled" assert ( f'<a class="enroll-button {class_name}" href="{url}">'.encode("utf-8") in resp.content) is has_button assert ("Please Sign In to MITx PRO to enroll in a course".encode("utf-8") in resp.content) is (is_anonymous and has_product and has_unexpired_run)
def test_generate_program_certificate_failure(user, program): """ Test that generate_program_certificate return (None, False) and not create program certificate if there is not any course_run certificate for the given course. """ course = CourseFactory.create(program=program) CourseRunFactory.create_batch(3, course=course) result = generate_program_certificate(user=user, program=program) assert result == (None, False) assert len(ProgramCertificate.objects.all()) == 0
def test_lists_catalog(self): """Course Runs should show up""" program = ProgramFactory.create(live=True) for course in CourseFactory.create_batch(3, program=program): CourseRunFactory.create_batch(2, course=course) resp = self.client.get(reverse('catalog-list')) assert len(resp.json()) == 1 data = CatalogProgramSerializer(program).data assert_drf_json_equal([data], resp.json())
def test_create_run_enrollments_enroll_api_fail(mocker, user, keep_failed_enrollments, exception_cls, inner_exception): """ create_run_enrollments should log a message and still create local enrollment records when an enrollment exception is raised if a flag is set to true """ num_runs = 3 runs = CourseRunFactory.create_batch(num_runs) patched_edx_enroll = mocker.patch( "courses.api.enroll_in_edx_course_runs", side_effect=exception_cls(user, runs[2], inner_exception), ) patched_log_exception = mocker.patch("courses.api.log.exception") patched_send_enrollment_email = mocker.patch( "courses.api.mail_api.send_course_run_enrollment_email") successful_enrollments, edx_request_success = create_run_enrollments( user, runs, order=None, company=None, keep_failed_enrollments=keep_failed_enrollments, ) patched_edx_enroll.assert_called_once_with(user, runs) patched_log_exception.assert_called_once() patched_send_enrollment_email.assert_not_called() expected_enrollments = 0 if not keep_failed_enrollments else num_runs assert len(successful_enrollments) == expected_enrollments assert edx_request_success is False
def test_create_run_enrollments(mocker, user): """ create_run_enrollments should call the edX API to create enrollments, create or reactivate local enrollment records, and notify enrolled users via email """ num_runs = 3 order = OrderFactory.create() company = CompanyFactory.create() runs = CourseRunFactory.create_batch(num_runs) # Create an existing deactivate enrollment to test that it gets reactivated CourseRunEnrollmentFactory.create( user=user, run=runs[0], order=order, change_status=ENROLL_CHANGE_STATUS_REFUNDED, active=False, ) patched_edx_enroll = mocker.patch("courses.api.enroll_in_edx_course_runs") patched_send_enrollment_email = mocker.patch( "courses.api.mail_api.send_course_run_enrollment_email") successful_enrollments, edx_request_success = create_run_enrollments( user, runs, order=order, company=company) patched_edx_enroll.assert_called_once_with(user, runs) assert patched_send_enrollment_email.call_count == num_runs assert edx_request_success is True assert len(successful_enrollments) == num_runs enrollments = CourseRunEnrollment.objects.order_by("run__id").all() for (run, enrollment) in zip(runs, enrollments): assert enrollment.change_status is None assert enrollment.active is True assert enrollment.edx_enrolled is True assert enrollment.run == run patched_send_enrollment_email.assert_any_call(enrollment)
def generate_course_with_runs(program, course_params=None, course_run_count=1): """ Helper method to generate a Course with CourseRuns for a Program """ course_params = course_params or {} course = CourseFactory.create(program=program, **course_params) course_runs = CourseRunFactory.create_batch(course_run_count, course=course) return course, course_runs
def test_course_available_runs(): """enrolled runs for a user should not be in the list of available runs""" user = UserFactory.create() course = CourseFactory.create() runs = CourseRunFactory.create_batch(2, course=course, live=True) runs.sort(key=lambda run: run.start_date) CourseRunEnrollmentFactory.create(run=runs[0], user=user) assert course.available_runs(user) == [runs[1]] assert course.available_runs(UserFactory.create()) == runs
def test_program_first_course_unexpired_runs(): """ first_course_unexpired_runs should return the unexpired course runs of the first course in the program (position_in_program=1) """ program = ProgramFactory.create() now = now_in_utc() past_start_dates = [ now + timedelta(days=-10), now + timedelta(days=-11), now + timedelta(days=-12), ] past_end_dates = [now + timedelta(days=-5), now + timedelta(days=-6)] future_end_dates = [ now + timedelta(days=10), now + timedelta(days=11), now + timedelta(days=12), ] first_course = CourseFactory.create(live=True, program=program, position_in_program=1) second_course = CourseFactory.create(live=True, program=program, position_in_program=2) CourseRunFactory.create_batch( 2, course=second_course, start_date=factory.Iterator(past_start_dates), end_date=factory.Iterator(past_end_dates), live=True, ) CourseRunFactory.create_batch( 3, course=first_course, start_date=factory.Iterator(past_start_dates), end_date=factory.Iterator(future_end_dates), enrollment_end=factory.Iterator(future_end_dates), live=True, ) assert len(program.first_course_unexpired_runs) == 3
def test_course_next_run_date(): """ next_run_date should return the date of the CourseRun with the nearest future start date """ course = CourseFactory.create() CourseRunFactory.create_batch(2, course=course, past_start=True, live=True) assert course.next_run_date is None now = now_in_utc() future_dates = [now + timedelta(hours=1), now + timedelta(hours=2)] CourseRunFactory.create_batch(2, course=course, start_date=factory.Iterator(future_dates), live=True) # invlidate cached property del course.next_run_date assert course.next_run_date == future_dates[0]
def generate_course_with_runs(program, course_params=None, course_run_count=1): """ Helper method to generate a Course with CourseRuns for a Program """ course_params = course_params or {} course = CourseFactory.create(program=program, **course_params) course_runs = CourseRunFactory.create_batch(course_run_count, course=course) return course, course_runs
def test_course_unexpired_runs(): """unexpired_runs should return expected value""" course = CourseFactory.create() now = now_in_utc() start_dates = [now, now + timedelta(days=-3)] end_dates = [now + timedelta(hours=1), now + timedelta(days=-2)] CourseRunFactory.create_batch( 2, course=course, start_date=factory.Iterator(start_dates), end_date=factory.Iterator(end_dates), live=True, ) # Add a run that is not live and shouldn't show up in unexpired list CourseRunFactory.create(course=course, start_date=start_dates[0], end_date=end_dates[0], live=False) assert len(course.unexpired_runs) == 1 course_run = course.unexpired_runs[0] assert course_run.start_date == start_dates[0] assert course_run.end_date == end_dates[0]
def test_defer_enrollment(mocker, keep_failed_enrollments): """ defer_enrollment should deactivate a user's existing enrollment and create an enrollment in another course run """ course = CourseFactory.create() course_runs = CourseRunFactory.create_batch(3, course=course) order = OrderFactory.create() company = CompanyFactory.create() existing_enrollment = CourseRunEnrollmentFactory.create(run=course_runs[0], order=order, company=company) target_run = course_runs[1] mock_new_enrollment = mocker.Mock() patched_create_enrollments = mocker.patch( "courses.api.create_run_enrollments", autospec=True, return_value=([ mock_new_enrollment if keep_failed_enrollments else None ], True), ) patched_deactivate_enrollments = mocker.patch( "courses.api.deactivate_run_enrollment", autospec=True, return_value=existing_enrollment if keep_failed_enrollments else None, ) returned_from_enrollment, returned_to_enrollment = defer_enrollment( existing_enrollment.user, existing_enrollment.run.courseware_id, course_runs[1].courseware_id, keep_failed_enrollments=keep_failed_enrollments, ) assert returned_from_enrollment == patched_deactivate_enrollments.return_value assert returned_to_enrollment == patched_create_enrollments.return_value[ 0][0] patched_create_enrollments.assert_called_once_with( existing_enrollment.user, [target_run], order=order, company=company, keep_failed_enrollments=keep_failed_enrollments, ) patched_deactivate_enrollments.assert_called_once_with( existing_enrollment, ENROLL_CHANGE_STATUS_DEFERRED, keep_failed_enrollments=keep_failed_enrollments, )
def test_has_paid_for_any_in_program(self): """ Assert that has_paid_for_any_in_program returns True if any CourseRun associated with a Program has been paid for. """ new_program = ProgramFactory.create() new_course_runs = CourseRunFactory.create_batch(2, course__program=new_program) mmtrack = MMTrack( user=self.user, program=new_program, edx_user_data=self.cached_edx_user_data ) assert mmtrack.has_paid_for_any_in_program() is False fg = FinalGradeFactory.create(user=self.user, course_run=new_course_runs[0], course_run_paid_on_edx=True) assert mmtrack.has_paid_for_any_in_program() is True fg.delete() FinalGradeFactory.create(user=self.user, course_run=new_course_runs[1], course_run_paid_on_edx=True) assert mmtrack.has_paid_for_any_in_program() is True
def test_has_paid_for_any_in_program(self): """ Assert that has_paid_for_any_in_program returns True if any CourseRun associated with a Program has been paid for. """ new_program = ProgramFactory.create() new_course_runs = CourseRunFactory.create_batch(2, course__program=new_program) mmtrack = MMTrack( user=self.user, program=new_program, edx_user_data=self.cached_edx_user_data ) assert mmtrack.has_paid_for_any_in_program() is False fg = FinalGradeFactory.create(user=self.user, course_run=new_course_runs[0], course_run_paid_on_edx=True) assert mmtrack.has_paid_for_any_in_program() is True fg.delete() FinalGradeFactory.create(user=self.user, course_run=new_course_runs[1], course_run_paid_on_edx=True) assert mmtrack.has_paid_for_any_in_program() is True
def test_enrollment_is_ended(): """Verify that is_ended returns True, if all of course runs in a program/course are ended.""" past_date = now_in_utc() - timedelta(days=1) past_program = ProgramFactory.create() past_course = CourseFactory.create() past_course_runs = CourseRunFactory.create_batch( 3, end_date=past_date, course=past_course, course__program=past_program) program_enrollment = ProgramEnrollmentFactory.create(program=past_program) course_enrollment = CourseRunEnrollmentFactory.create( run=past_course_runs[0]) assert program_enrollment.is_ended assert course_enrollment.is_ended
def test_program_is_catalog_visible(): """ is_catalog_visible should return True if a program has any course run that has a start date or enrollment end date in the future """ program = ProgramFactory.create() runs = CourseRunFactory.create_batch(2, course__program=program, past_start=True, past_enrollment_end=True) assert program.is_catalog_visible is False now = now_in_utc() run = runs[0] run.start_date = now + timedelta(hours=1) run.save() assert program.is_catalog_visible is True run.start_date = now - timedelta(hours=1) run.enrollment_end = now + timedelta(hours=1) run.save() assert program.is_catalog_visible is True
def test_create_run_enrollments_creation_fail(mocker, user): """ create_run_enrollments should log a message and send an admin email if there's an error during the creation of local enrollment records """ runs = CourseRunFactory.create_batch(2) enrollment = CourseRunEnrollmentFactory.build(run=runs[1]) mocker.patch( "courses.api.CourseRunEnrollment.all_objects.get_or_create", side_effect=[Exception(), (enrollment, True)], ) patched_edx_enroll = mocker.patch("courses.api.enroll_in_edx_course_runs") patched_log_exception = mocker.patch("courses.api.log.exception") patched_mail_api = mocker.patch("courses.api.mail_api") successful_enrollments, edx_request_success = create_run_enrollments( user, runs, order=None, company=None) patched_edx_enroll.assert_called_once_with(user, runs) patched_log_exception.assert_called_once() patched_mail_api.send_course_run_enrollment_email.assert_not_called() patched_mail_api.send_enrollment_failure_message.assert_called_once() assert successful_enrollments == [enrollment] assert edx_request_success is True
def test_program_next_run_date(): """ next_run_date should return the date of the CourseRun with the nearest future start date and first position in program (course__position_in_program=1) """ program = ProgramFactory.create() CourseRunFactory.create_batch( 2, course=CourseFactory.create(program=program, position_in_program=3), past_start=True, ) assert program.next_run_date is None now = now_in_utc() second_course_future_dates = [ now + timedelta(hours=1), now + timedelta(hours=3) ] CourseRunFactory.create_batch( 2, course=CourseFactory.create(program=program, position_in_program=2), start_date=factory.Iterator(second_course_future_dates), live=True, ) first_course_future_dates = [ now + timedelta(hours=2), now + timedelta(hours=4) ] CourseRunFactory.create_batch( 2, course=CourseFactory.create(program=program, position_in_program=1), start_date=factory.Iterator(first_course_future_dates), live=True, ) # invalidate cached property del program.next_run_date assert program.next_run_date == first_course_future_dates[0]
def course_runs(): """Fixture for a set of CourseRuns in the database""" return CourseRunFactory.create_batch(3)
def test_get_user_enrollments(user): """Test that get_user_enrollments returns an object with a user's program and course enrollments""" past_date = now_in_utc() - timedelta(days=1) past_start_dates = [ now_in_utc() - timedelta(days=2), now_in_utc() - timedelta(days=3), now_in_utc() - timedelta(days=4), ] program = ProgramFactory.create() past_program = ProgramFactory.create() program_course_runs = CourseRunFactory.create_batch( 3, course__program=program) past_program_course_runs = CourseRunFactory.create_batch( 3, start_date=factory.Iterator(past_start_dates), end_date=past_date, course__program=past_program, ) non_program_course_runs = CourseRunFactory.create_batch( 2, course__program=None) past_non_program_course_runs = CourseRunFactory.create_batch( 2, start_date=factory.Iterator(past_start_dates), end_date=past_date, course__program=None, ) all_course_runs = (program_course_runs + past_program_course_runs + non_program_course_runs + past_non_program_course_runs) course_run_enrollments = CourseRunEnrollmentFactory.create_batch( len(all_course_runs), run=factory.Iterator(all_course_runs), user=user) program_enrollment = ProgramEnrollmentFactory.create(program=program, user=user) past_program_enrollment = ProgramEnrollmentFactory.create( program=past_program, user=user) # Add a non-active enrollment so we can confirm that it isn't returned CourseRunEnrollmentFactory.create(user=user, active=False) def key_func(enrollment): """ Function for sorting runs by start_date""" return enrollment.run.start_date user_enrollments = get_user_enrollments(user) assert list(user_enrollments.programs) == [program_enrollment] assert list(user_enrollments.past_programs) == [past_program_enrollment] assert list(user_enrollments.program_runs) == sorted( [ run_enrollment for run_enrollment in course_run_enrollments if run_enrollment.run in program_course_runs + past_program_course_runs ], key=key_func, ) assert list(user_enrollments.non_program_runs) == sorted( [ run_enrollment for run_enrollment in course_run_enrollments if run_enrollment.run in non_program_course_runs ], key=key_func, ) assert list(user_enrollments.past_non_program_runs) == sorted( [ run_enrollment for run_enrollment in course_run_enrollments if run_enrollment.run in past_non_program_course_runs ], key=key_func, )