class TestProgramDataExtender(ModuleStoreTestCase): """Tests of the program data extender utility class.""" maxDiff = None sku = 'abc123' checkout_path = '/basket' instructors = { 'instructors': [ { 'name': 'test-instructor1', 'organization': 'TextX', }, { 'name': 'test-instructor2', 'organization': 'TextX', } ] } def setUp(self): super(TestProgramDataExtender, self).setUp() self.course = ModuleStoreCourseFactory() self.course.start = datetime.datetime.now(utc) - datetime.timedelta(days=1) self.course.end = datetime.datetime.now(utc) + datetime.timedelta(days=1) self.course = self.update_course(self.course, self.user.id) self.course_run = CourseRunFactory(key=unicode(self.course.id)) self.catalog_course = CourseFactory(course_runs=[self.course_run]) self.program = ProgramFactory(courses=[self.catalog_course]) self.course_price = 100 def _assert_supplemented(self, actual, **kwargs): """DRY helper used to verify that program data is extended correctly.""" self.course_run.update( dict( { 'certificate_url': None, 'course_url': reverse('course_root', args=[self.course.id]), 'enrollment_open_date': strftime_localized(DEFAULT_ENROLLMENT_START_DATE, 'SHORT_DATE'), 'is_course_ended': self.course.end < datetime.datetime.now(utc), 'is_enrolled': False, 'is_enrollment_open': True, 'upgrade_url': None, 'advertised_start': None, }, **kwargs ) ) self.catalog_course['course_runs'] = [self.course_run] self.program['courses'] = [self.catalog_course] self.assertEqual(actual, self.program) @ddt.data(-1, 0, 1) def test_is_enrollment_open(self, days_offset): """ Verify that changes to the course run end date do not affect our assessment of the course run being open for enrollment. """ self.course.end = datetime.datetime.now(utc) + datetime.timedelta(days=days_offset) self.course = self.update_course(self.course, self.user.id) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented(data) @ddt.data( (False, None, False), (True, MODES.audit, True), (True, MODES.verified, False), ) @ddt.unpack @mock.patch(UTILS_MODULE + '.CourseMode.mode_for_course') def test_student_enrollment_status(self, is_enrolled, enrolled_mode, is_upgrade_required, mock_get_mode): """Verify that program data is supplemented with the student's enrollment status.""" expected_upgrade_url = '{root}/{path}?sku={sku}'.format( root=ECOMMERCE_URL_ROOT, path=self.checkout_path.strip('/'), sku=self.sku, ) update_commerce_config(enabled=True, checkout_page=self.checkout_path) mock_mode = mock.Mock() mock_mode.sku = self.sku mock_get_mode.return_value = mock_mode if is_enrolled: CourseEnrollmentFactory(user=self.user, course_id=self.course.id, mode=enrolled_mode) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented( data, is_enrolled=is_enrolled, upgrade_url=expected_upgrade_url if is_upgrade_required else None ) @ddt.data(MODES.audit, MODES.verified) def test_inactive_enrollment_no_upgrade(self, enrolled_mode): """ Verify that a student with an inactive enrollment isn't encouraged to upgrade. """ update_commerce_config(enabled=True, checkout_page=self.checkout_path) CourseEnrollmentFactory( user=self.user, course_id=self.course.id, mode=enrolled_mode, is_active=False, ) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented(data) @mock.patch(UTILS_MODULE + '.CourseMode.mode_for_course') def test_ecommerce_disabled(self, mock_get_mode): """ Verify that the utility can operate when the ecommerce service is disabled. """ update_commerce_config(enabled=False, checkout_page=self.checkout_path) mock_mode = mock.Mock() mock_mode.sku = self.sku mock_get_mode.return_value = mock_mode CourseEnrollmentFactory(user=self.user, course_id=self.course.id, mode=MODES.audit) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented(data, is_enrolled=True, upgrade_url=None) @ddt.data( (1, 1, False), (1, -1, True), ) @ddt.unpack def test_course_run_enrollment_status(self, start_offset, end_offset, is_enrollment_open): """ Verify that course run enrollment status is reflected correctly. """ self.course.enrollment_start = datetime.datetime.now(utc) - datetime.timedelta(days=start_offset) self.course.enrollment_end = datetime.datetime.now(utc) - datetime.timedelta(days=end_offset) self.course = self.update_course(self.course, self.user.id) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented( data, is_enrollment_open=is_enrollment_open, enrollment_open_date=strftime_localized(self.course.enrollment_start, 'SHORT_DATE'), ) def test_no_enrollment_start_date(self): """ Verify that a closed course run with no explicit enrollment start date doesn't cause an error. Regression test for ECOM-4973. """ self.course.enrollment_end = datetime.datetime.now(utc) - datetime.timedelta(days=1) self.course = self.update_course(self.course, self.user.id) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented( data, is_enrollment_open=False, ) @ddt.data(True, False) @mock.patch(UTILS_MODULE + '.certificate_api.certificate_downloadable_status') @mock.patch(CERTIFICATES_API_MODULE + '.has_html_certificates_enabled') def test_certificate_url_retrieval(self, is_uuid_available, mock_html_certs_enabled, mock_get_cert_data): """ Verify that the student's run mode certificate is included, when available. """ test_uuid = uuid.uuid4().hex mock_get_cert_data.return_value = {'uuid': test_uuid} if is_uuid_available else {} mock_html_certs_enabled.return_value = True data = ProgramDataExtender(self.program, self.user).extend() expected_url = reverse( 'certificates:render_cert_by_uuid', kwargs={'certificate_uuid': test_uuid} ) if is_uuid_available else None self._assert_supplemented(data, certificate_url=expected_url) @ddt.data(True, False) def test_may_certify_attached(self, may_certify): """ Verify that the `may_certify` is included during data extension. """ self.course.certificates_show_before_end = may_certify self.course = self.update_course(self.course, self.user.id) data = ProgramDataExtender(self.program, self.user).extend() self.assertEqual(may_certify, data['courses'][0]['course_runs'][0]['may_certify']) self._assert_supplemented(data) def test_learner_eligibility_for_one_click_purchase(self): """ Learner should be eligible for one click purchase if: - program is eligible for one click purchase - There are courses remaining that have not been purchased and enrolled in. """ data = ProgramDataExtender(self.program, self.user).extend() self.assertFalse(data['is_learner_eligible_for_one_click_purchase']) courses = [_create_course(self, self.course_price)] program = ProgramFactory( courses=courses, is_program_eligible_for_one_click_purchase=False ) data = ProgramDataExtender(program, self.user).extend() self.assertFalse(data['is_learner_eligible_for_one_click_purchase']) course1 = _create_course(self, self.course_price) course2 = _create_course(self, self.course_price) CourseEnrollmentFactory(user=self.user, course_id=course1['course_runs'][0]['key'], mode='verified') CourseEnrollmentFactory(user=self.user, course_id=course2['course_runs'][0]['key'], mode='audit') program2 = ProgramFactory( courses=[course1, course2], is_program_eligible_for_one_click_purchase=True, applicable_seat_types=['verified'], ) data = ProgramDataExtender(program2, self.user).extend() self.assertTrue(data['is_learner_eligible_for_one_click_purchase']) def test_learner_eligibility_for_one_click_purchase_with_unpublished(self): """ Learner should be eligible for one click purchase if: - program is eligible for one click purchase - There are courses remaining that have not been purchased and enrolled in. """ course1 = _create_course(self, self.course_price, course_run_count=2) course2 = _create_course(self, self.course_price) CourseEnrollmentFactory(user=self.user, course_id=course1['course_runs'][0]['key'], mode='verified') course1['course_runs'][0]['status'] = 'unpublished' program2 = ProgramFactory( courses=[course1, course2], is_program_eligible_for_one_click_purchase=True, applicable_seat_types=['verified'], ) data = ProgramDataExtender(program2, self.user).extend() self.assertEqual(len(data['skus']), 1) self.assertTrue(data['is_learner_eligible_for_one_click_purchase']) def test_learner_eligibility_for_one_click_purchase_professional_no_id(self): """ Learner should not be eligible for one click purchase if: - There are no courses remaining that have not been purchased and enrolled in. This test is primarily for the case of no-id-professional enrollment modes """ course1 = _create_course(self, self.course_price) CourseEnrollmentFactory(user=self.user, course_id=course1['course_runs'][0]['key'], mode='no-id-professional') program2 = ProgramFactory( courses=[course1], is_program_eligible_for_one_click_purchase=True, applicable_seat_types=['professional'], # There is no seat type for no-id-professional, it # instead uses professional ) data = ProgramDataExtender(program2, self.user).extend() self.assertFalse(data['is_learner_eligible_for_one_click_purchase']) def test_multiple_published_course_runs(self): """ Learner should not be eligible for one click purchase if: - program has a course with more than one published course run """ course_run_1 = CourseRunFactory( key=str(ModuleStoreCourseFactory().id), status='published' ) course_run_2 = CourseRunFactory( key=str(ModuleStoreCourseFactory().id), status='published' ) course = CourseFactory(course_runs=[course_run_1, course_run_2]) program = ProgramFactory( courses=[ CourseFactory(course_runs=[ CourseRunFactory( key=str(ModuleStoreCourseFactory().id), status='published' ) ]), course, CourseFactory(course_runs=[ CourseRunFactory( key=str(ModuleStoreCourseFactory().id), status='published' ) ]) ], is_program_eligible_for_one_click_purchase=True, applicable_seat_types=['verified'] ) data = ProgramDataExtender(program, self.user).extend() self.assertFalse(data['is_learner_eligible_for_one_click_purchase']) course_run_2['status'] = 'unpublished' data = ProgramDataExtender(program, self.user).extend() self.assertTrue(data['is_learner_eligible_for_one_click_purchase'])
class TestProgramDataExtender(ModuleStoreTestCase): """Tests of the program data extender utility class.""" maxDiff = None sku = 'abc123' checkout_path = '/basket' instructors = { 'instructors': [ { 'name': 'test-instructor1', 'organization': 'TextX', }, { 'name': 'test-instructor2', 'organization': 'TextX', } ] } def setUp(self): super(TestProgramDataExtender, self).setUp() self.course = ModuleStoreCourseFactory() self.course.start = datetime.datetime.now(utc) - datetime.timedelta(days=1) self.course.end = datetime.datetime.now(utc) + datetime.timedelta(days=1) self.course = self.update_course(self.course, self.user.id) self.course_run = CourseRunFactory(key=unicode(self.course.id)) self.catalog_course = CourseFactory(course_runs=[self.course_run]) self.program = ProgramFactory(courses=[self.catalog_course]) self.course_price = 100 def _assert_supplemented(self, actual, **kwargs): """DRY helper used to verify that program data is extended correctly.""" self.course_run.update( dict( { 'certificate_url': None, 'course_url': reverse('course_root', args=[self.course.id]), 'enrollment_open_date': strftime_localized(DEFAULT_ENROLLMENT_START_DATE, 'SHORT_DATE'), 'is_course_ended': self.course.end < datetime.datetime.now(utc), 'is_enrolled': False, 'is_enrollment_open': True, 'upgrade_url': None, 'advertised_start': None, }, **kwargs ) ) self.catalog_course['course_runs'] = [self.course_run] self.program['courses'] = [self.catalog_course] self.assertEqual(actual, self.program) @ddt.data(-1, 0, 1) def test_is_enrollment_open(self, days_offset): """ Verify that changes to the course run end date do not affect our assessment of the course run being open for enrollment. """ self.course.end = datetime.datetime.now(utc) + datetime.timedelta(days=days_offset) self.course = self.update_course(self.course, self.user.id) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented(data) @ddt.data( (False, None, False), (True, MODES.audit, True), (True, MODES.verified, False), ) @ddt.unpack @mock.patch(UTILS_MODULE + '.CourseMode.mode_for_course') def test_student_enrollment_status(self, is_enrolled, enrolled_mode, is_upgrade_required, mock_get_mode): """Verify that program data is supplemented with the student's enrollment status.""" expected_upgrade_url = '{root}/{path}?sku={sku}'.format( root=ECOMMERCE_URL_ROOT, path=self.checkout_path.strip('/'), sku=self.sku, ) update_commerce_config(enabled=True, checkout_page=self.checkout_path) mock_mode = mock.Mock() mock_mode.sku = self.sku mock_get_mode.return_value = mock_mode if is_enrolled: CourseEnrollmentFactory(user=self.user, course_id=self.course.id, mode=enrolled_mode) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented( data, is_enrolled=is_enrolled, upgrade_url=expected_upgrade_url if is_upgrade_required else None ) @ddt.data(MODES.audit, MODES.verified) def test_inactive_enrollment_no_upgrade(self, enrolled_mode): """ Verify that a student with an inactive enrollment isn't encouraged to upgrade. """ update_commerce_config(enabled=True, checkout_page=self.checkout_path) CourseEnrollmentFactory( user=self.user, course_id=self.course.id, mode=enrolled_mode, is_active=False, ) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented(data) @mock.patch(UTILS_MODULE + '.CourseMode.mode_for_course') def test_ecommerce_disabled(self, mock_get_mode): """ Verify that the utility can operate when the ecommerce service is disabled. """ update_commerce_config(enabled=False, checkout_page=self.checkout_path) mock_mode = mock.Mock() mock_mode.sku = self.sku mock_get_mode.return_value = mock_mode CourseEnrollmentFactory(user=self.user, course_id=self.course.id, mode=MODES.audit) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented(data, is_enrolled=True, upgrade_url=None) @ddt.data( (1, 1, False), (1, -1, True), ) @ddt.unpack def test_course_run_enrollment_status(self, start_offset, end_offset, is_enrollment_open): """ Verify that course run enrollment status is reflected correctly. """ self.course.enrollment_start = datetime.datetime.now(utc) - datetime.timedelta(days=start_offset) self.course.enrollment_end = datetime.datetime.now(utc) - datetime.timedelta(days=end_offset) self.course = self.update_course(self.course, self.user.id) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented( data, is_enrollment_open=is_enrollment_open, enrollment_open_date=strftime_localized(self.course.enrollment_start, 'SHORT_DATE'), ) def test_no_enrollment_start_date(self): """ Verify that a closed course run with no explicit enrollment start date doesn't cause an error. Regression test for ECOM-4973. """ self.course.enrollment_end = datetime.datetime.now(utc) - datetime.timedelta(days=1) self.course = self.update_course(self.course, self.user.id) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented( data, is_enrollment_open=False, ) @ddt.data(True, False) @mock.patch(UTILS_MODULE + '.certificate_api.certificate_downloadable_status') @mock.patch(CERTIFICATES_API_MODULE + '.has_html_certificates_enabled') def test_certificate_url_retrieval(self, is_uuid_available, mock_html_certs_enabled, mock_get_cert_data): """ Verify that the student's run mode certificate is included, when available. """ test_uuid = uuid.uuid4().hex mock_get_cert_data.return_value = {'uuid': test_uuid} if is_uuid_available else {} mock_html_certs_enabled.return_value = True data = ProgramDataExtender(self.program, self.user).extend() expected_url = reverse( 'certificates:render_cert_by_uuid', kwargs={'certificate_uuid': test_uuid} ) if is_uuid_available else None self._assert_supplemented(data, certificate_url=expected_url) @ddt.data(True, False) def test_may_certify_attached(self, may_certify): """ Verify that the `may_certify` is included during data extension. """ self.course.certificates_show_before_end = may_certify self.course = self.update_course(self.course, self.user.id) data = ProgramDataExtender(self.program, self.user).extend() self.assertEqual(may_certify, data['courses'][0]['course_runs'][0]['may_certify']) self._assert_supplemented(data) def test_learner_eligibility_for_one_click_purchase(self): """ Learner should be eligible for one click purchase if: - program is eligible for one click purchase - There are courses remaining that have not been purchased and enrolled in. """ data = ProgramDataExtender(self.program, self.user).extend() self.assertFalse(data['is_learner_eligible_for_one_click_purchase']) courses = [_create_course(self, self.course_price)] program = ProgramFactory( courses=courses, is_program_eligible_for_one_click_purchase=False ) data = ProgramDataExtender(program, self.user).extend() self.assertFalse(data['is_learner_eligible_for_one_click_purchase']) course1 = _create_course(self, self.course_price) course2 = _create_course(self, self.course_price) CourseEnrollmentFactory(user=self.user, course_id=course1['course_runs'][0]['key'], mode='verified') CourseEnrollmentFactory(user=self.user, course_id=course2['course_runs'][0]['key'], mode='audit') program2 = ProgramFactory( courses=[course1, course2], is_program_eligible_for_one_click_purchase=True, applicable_seat_types=['verified'], ) data = ProgramDataExtender(program2, self.user).extend() self.assertTrue(data['is_learner_eligible_for_one_click_purchase']) def test_learner_eligibility_for_one_click_purchase_with_unpublished(self): """ Learner should be eligible for one click purchase if: - program is eligible for one click purchase - There are courses remaining that have not been purchased and enrolled in. """ course1 = _create_course(self, self.course_price, course_run_count=2) course2 = _create_course(self, self.course_price) CourseEnrollmentFactory(user=self.user, course_id=course1['course_runs'][0]['key'], mode='verified') course1['course_runs'][0]['status'] = 'unpublished' program2 = ProgramFactory( courses=[course1, course2], is_program_eligible_for_one_click_purchase=True, applicable_seat_types=['verified'], ) data = ProgramDataExtender(program2, self.user).extend() self.assertEqual(len(data['skus']), 1) self.assertTrue(data['is_learner_eligible_for_one_click_purchase']) def test_learner_eligibility_for_one_click_purchase_professional_no_id(self): """ Learner should not be eligible for one click purchase if: - There are no courses remaining that have not been purchased and enrolled in. This test is primarily for the case of no-id-professional enrollment modes """ course1 = _create_course(self, self.course_price) CourseEnrollmentFactory(user=self.user, course_id=course1['course_runs'][0]['key'], mode='no-id-professional') program2 = ProgramFactory( courses=[course1], is_program_eligible_for_one_click_purchase=True, applicable_seat_types=['professional'], # There is no seat type for no-id-professional, it # instead uses professional ) data = ProgramDataExtender(program2, self.user).extend() self.assertFalse(data['is_learner_eligible_for_one_click_purchase']) def test_multiple_published_course_runs(self): """ Learner should not be eligible for one click purchase if: - program has a course with more than one published course run """ course_run_1 = CourseRunFactory( key=str(ModuleStoreCourseFactory().id), status='published' ) course_run_2 = CourseRunFactory( key=str(ModuleStoreCourseFactory().id), status='published' ) course = CourseFactory(course_runs=[course_run_1, course_run_2]) program = ProgramFactory( courses=[ CourseFactory(course_runs=[ CourseRunFactory( key=str(ModuleStoreCourseFactory().id), status='published' ) ]), course, CourseFactory(course_runs=[ CourseRunFactory( key=str(ModuleStoreCourseFactory().id), status='published' ) ]) ], is_program_eligible_for_one_click_purchase=True, applicable_seat_types=['verified'] ) data = ProgramDataExtender(program, self.user).extend() self.assertFalse(data['is_learner_eligible_for_one_click_purchase']) course_run_2['status'] = 'unpublished' data = ProgramDataExtender(program, self.user).extend() self.assertTrue(data['is_learner_eligible_for_one_click_purchase'])
class TestProgramDataExtender(ModuleStoreTestCase): """Tests of the program data extender utility class.""" maxDiff = None sku = 'abc123' checkout_path = '/basket' def setUp(self): super(TestProgramDataExtender, self).setUp() self.course = ModuleStoreCourseFactory() self.course.start = datetime.datetime.now(utc) - datetime.timedelta(days=1) self.course.end = datetime.datetime.now(utc) + datetime.timedelta(days=1) self.course = self.update_course(self.course, self.user.id) self.course_run = CourseRunFactory(key=unicode(self.course.id)) self.catalog_course = CourseFactory(course_runs=[self.course_run]) self.program = ProgramFactory(courses=[self.catalog_course]) def _assert_supplemented(self, actual, **kwargs): """DRY helper used to verify that program data is extended correctly.""" self.course_run.update( dict( { 'certificate_url': None, 'course_url': reverse('course_root', args=[self.course.id]), 'enrollment_open_date': strftime_localized(DEFAULT_ENROLLMENT_START_DATE, 'SHORT_DATE'), 'is_course_ended': self.course.end < datetime.datetime.now(utc), 'is_enrolled': False, 'is_enrollment_open': True, 'upgrade_url': None, 'advertised_start': None, }, **kwargs ) ) self.catalog_course['course_runs'] = [self.course_run] self.program['courses'] = [self.catalog_course] self.assertEqual(actual, self.program) @ddt.data(-1, 0, 1) def test_is_enrollment_open(self, days_offset): """ Verify that changes to the course run end date do not affect our assessment of the course run being open for enrollment. """ self.course.end = datetime.datetime.now(utc) + datetime.timedelta(days=days_offset) self.course = self.update_course(self.course, self.user.id) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented(data) @ddt.data( (False, None, False), (True, MODES.audit, True), (True, MODES.verified, False), ) @ddt.unpack @mock.patch(UTILS_MODULE + '.CourseMode.mode_for_course') def test_student_enrollment_status(self, is_enrolled, enrolled_mode, is_upgrade_required, mock_get_mode): """Verify that program data is supplemented with the student's enrollment status.""" expected_upgrade_url = '{root}/{path}?sku={sku}'.format( root=ECOMMERCE_URL_ROOT, path=self.checkout_path.strip('/'), sku=self.sku, ) update_commerce_config(enabled=True, checkout_page=self.checkout_path) mock_mode = mock.Mock() mock_mode.sku = self.sku mock_get_mode.return_value = mock_mode if is_enrolled: CourseEnrollmentFactory(user=self.user, course_id=self.course.id, mode=enrolled_mode) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented( data, is_enrolled=is_enrolled, upgrade_url=expected_upgrade_url if is_upgrade_required else None ) @ddt.data(MODES.audit, MODES.verified) def test_inactive_enrollment_no_upgrade(self, enrolled_mode): """ Verify that a student with an inactive enrollment isn't encouraged to upgrade. """ update_commerce_config(enabled=True, checkout_page=self.checkout_path) CourseEnrollmentFactory( user=self.user, course_id=self.course.id, mode=enrolled_mode, is_active=False, ) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented(data) @mock.patch(UTILS_MODULE + '.CourseMode.mode_for_course') def test_ecommerce_disabled(self, mock_get_mode): """ Verify that the utility can operate when the ecommerce service is disabled. """ update_commerce_config(enabled=False, checkout_page=self.checkout_path) mock_mode = mock.Mock() mock_mode.sku = self.sku mock_get_mode.return_value = mock_mode CourseEnrollmentFactory(user=self.user, course_id=self.course.id, mode=MODES.audit) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented(data, is_enrolled=True, upgrade_url=None) @ddt.data( (1, 1, False), (1, -1, True), ) @ddt.unpack def test_course_run_enrollment_status(self, start_offset, end_offset, is_enrollment_open): """ Verify that course run enrollment status is reflected correctly. """ self.course.enrollment_start = datetime.datetime.now(utc) - datetime.timedelta(days=start_offset) self.course.enrollment_end = datetime.datetime.now(utc) - datetime.timedelta(days=end_offset) self.course = self.update_course(self.course, self.user.id) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented( data, is_enrollment_open=is_enrollment_open, enrollment_open_date=strftime_localized(self.course.enrollment_start, 'SHORT_DATE'), ) def test_no_enrollment_start_date(self): """ Verify that a closed course run with no explicit enrollment start date doesn't cause an error. Regression test for ECOM-4973. """ self.course.enrollment_end = datetime.datetime.now(utc) - datetime.timedelta(days=1) self.course = self.update_course(self.course, self.user.id) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented( data, is_enrollment_open=False, ) @ddt.data(True, False) @mock.patch(UTILS_MODULE + '.certificate_api.certificate_downloadable_status') @mock.patch(CERTIFICATES_API_MODULE + '.has_html_certificates_enabled') def test_certificate_url_retrieval(self, is_uuid_available, mock_html_certs_enabled, mock_get_cert_data): """ Verify that the student's run mode certificate is included, when available. """ test_uuid = uuid.uuid4().hex mock_get_cert_data.return_value = {'uuid': test_uuid} if is_uuid_available else {} mock_html_certs_enabled.return_value = True data = ProgramDataExtender(self.program, self.user).extend() expected_url = reverse( 'certificates:render_cert_by_uuid', kwargs={'certificate_uuid': test_uuid} ) if is_uuid_available else None self._assert_supplemented(data, certificate_url=expected_url)
class TestProgramDataExtender(ModuleStoreTestCase): """Tests of the program data extender utility class.""" maxDiff = None sku = 'abc123' checkout_path = '/basket' def setUp(self): super(TestProgramDataExtender, self).setUp() self.course = ModuleStoreCourseFactory() self.course.start = datetime.datetime.now(utc) - datetime.timedelta(days=1) self.course.end = datetime.datetime.now(utc) + datetime.timedelta(days=1) self.course = self.update_course(self.course, self.user.id) self.course_run = CourseRunFactory(key=unicode(self.course.id)) self.catalog_course = CourseFactory(course_runs=[self.course_run]) self.program = ProgramFactory(courses=[self.catalog_course]) def _assert_supplemented(self, actual, **kwargs): """DRY helper used to verify that program data is extended correctly.""" self.course_run.update( dict( { 'certificate_url': None, 'course_url': reverse('course_root', args=[self.course.id]), 'enrollment_open_date': strftime_localized(DEFAULT_ENROLLMENT_START_DATE, 'SHORT_DATE'), 'is_course_ended': self.course.end < datetime.datetime.now(utc), 'is_enrolled': False, 'is_enrollment_open': True, 'upgrade_url': None, 'advertised_start': None, }, **kwargs ) ) self.catalog_course['course_runs'] = [self.course_run] self.program['courses'] = [self.catalog_course] self.assertEqual(actual, self.program) @ddt.data(-1, 0, 1) def test_is_enrollment_open(self, days_offset): """ Verify that changes to the course run end date do not affect our assessment of the course run being open for enrollment. """ self.course.end = datetime.datetime.now(utc) + datetime.timedelta(days=days_offset) self.course = self.update_course(self.course, self.user.id) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented(data) @ddt.data( (False, None, False), (True, MODES.audit, True), (True, MODES.verified, False), ) @ddt.unpack @mock.patch(UTILS_MODULE + '.CourseMode.mode_for_course') def test_student_enrollment_status(self, is_enrolled, enrolled_mode, is_upgrade_required, mock_get_mode): """Verify that program data is supplemented with the student's enrollment status.""" expected_upgrade_url = '{root}/{path}?sku={sku}'.format( root=ECOMMERCE_URL_ROOT, path=self.checkout_path.strip('/'), sku=self.sku, ) update_commerce_config(enabled=True, checkout_page=self.checkout_path) mock_mode = mock.Mock() mock_mode.sku = self.sku mock_get_mode.return_value = mock_mode if is_enrolled: CourseEnrollmentFactory(user=self.user, course_id=self.course.id, mode=enrolled_mode) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented( data, is_enrolled=is_enrolled, upgrade_url=expected_upgrade_url if is_upgrade_required else None ) @ddt.data(MODES.audit, MODES.verified) def test_inactive_enrollment_no_upgrade(self, enrolled_mode): """ Verify that a student with an inactive enrollment isn't encouraged to upgrade. """ update_commerce_config(enabled=True, checkout_page=self.checkout_path) CourseEnrollmentFactory( user=self.user, course_id=self.course.id, mode=enrolled_mode, is_active=False, ) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented(data) @mock.patch(UTILS_MODULE + '.CourseMode.mode_for_course') def test_ecommerce_disabled(self, mock_get_mode): """ Verify that the utility can operate when the ecommerce service is disabled. """ update_commerce_config(enabled=False, checkout_page=self.checkout_path) mock_mode = mock.Mock() mock_mode.sku = self.sku mock_get_mode.return_value = mock_mode CourseEnrollmentFactory(user=self.user, course_id=self.course.id, mode=MODES.audit) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented(data, is_enrolled=True, upgrade_url=None) @ddt.data( (1, 1, False), (1, -1, True), ) @ddt.unpack def test_course_run_enrollment_status(self, start_offset, end_offset, is_enrollment_open): """ Verify that course run enrollment status is reflected correctly. """ self.course.enrollment_start = datetime.datetime.now(utc) - datetime.timedelta(days=start_offset) self.course.enrollment_end = datetime.datetime.now(utc) - datetime.timedelta(days=end_offset) self.course = self.update_course(self.course, self.user.id) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented( data, is_enrollment_open=is_enrollment_open, enrollment_open_date=strftime_localized(self.course.enrollment_start, 'SHORT_DATE'), ) def test_no_enrollment_start_date(self): """ Verify that a closed course run with no explicit enrollment start date doesn't cause an error. Regression test for ECOM-4973. """ self.course.enrollment_end = datetime.datetime.now(utc) - datetime.timedelta(days=1) self.course = self.update_course(self.course, self.user.id) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented( data, is_enrollment_open=False, ) @ddt.data(True, False) @mock.patch(UTILS_MODULE + '.certificate_api.certificate_downloadable_status') @mock.patch(CERTIFICATES_API_MODULE + '.has_html_certificates_enabled') def test_certificate_url_retrieval(self, is_uuid_available, mock_html_certs_enabled, mock_get_cert_data): """ Verify that the student's run mode certificate is included, when available. """ test_uuid = uuid.uuid4().hex mock_get_cert_data.return_value = {'uuid': test_uuid} if is_uuid_available else {} mock_html_certs_enabled.return_value = True data = ProgramDataExtender(self.program, self.user).extend() expected_url = reverse( 'certificates:render_cert_by_uuid', kwargs={'certificate_uuid': test_uuid} ) if is_uuid_available else None self._assert_supplemented(data, certificate_url=expected_url)