def test_unenroll_entitlement_with_audit_course_enrollment(self, mock_refund, mock_get_course_uuid): """ Test that entitlement is not refunded if un-enroll is called on audit course un-enroll. """ self.enrollment.mode = CourseMode.AUDIT self.enrollment.user = self.user self.enrollment.save() entitlement = CourseEntitlementFactory.create(user=self.user) mock_get_course_uuid.return_value = entitlement.course_uuid CourseEnrollment.unenroll(self.user, self.course.id) assert not mock_refund.called entitlement.refresh_from_db() assert entitlement.expired_at is None self.enrollment.mode = CourseMode.VERIFIED self.enrollment.is_active = True self.enrollment.save() entitlement.enrollment_course_run = self.enrollment entitlement.save() CourseEnrollment.unenroll(self.user, self.course.id) assert mock_refund.called entitlement.refresh_from_db() assert entitlement.expired_at < now()
def test_check_for_existing_entitlement_and_enroll(self, mock_get_course_uuid): course = CourseFactory() CourseModeFactory( course_id=course.id, mode_slug=CourseMode.VERIFIED, # This must be in the future to ensure it is returned by downstream code. expiration_datetime=now() + timedelta(days=1) ) entitlement = CourseEntitlementFactory.create( mode=CourseMode.VERIFIED, user=self.user, ) mock_get_course_uuid.return_value = entitlement.course_uuid assert not CourseEnrollment.is_enrolled(user=self.user, course_key=course.id) CourseEntitlement.check_for_existing_entitlement_and_enroll( user=self.user, course_run_key=course.id, ) assert CourseEnrollment.is_enrolled(user=self.user, course_key=course.id) entitlement.refresh_from_db() assert entitlement.enrollment_course_run
def test_unpublished_sessions_for_entitlement_when_enrolled(self, mock_get_edx_api_data): """ Test unpublished course runs are part of visible session entitlements when the user is enrolled. """ catalog_course_run = CourseRunFactory.create(status=COURSE_UNPUBLISHED) catalog_course = CourseFactory(course_runs=[catalog_course_run]) mock_get_edx_api_data.return_value = catalog_course course_key = CourseKey.from_string(catalog_course_run.get('key')) course_overview = CourseOverviewFactory.create(id=course_key, start=self.tomorrow) CourseModeFactory.create( mode_slug=CourseMode.VERIFIED, min_price=100, course_id=course_overview.id, expiration_datetime=now() - timedelta(days=1) ) course_enrollment = CourseEnrollmentFactory( user=self.user, course=course_overview, mode=CourseMode.VERIFIED ) entitlement = CourseEntitlementFactory( user=self.user, enrollment_course_run=course_enrollment, mode=CourseMode.VERIFIED ) session_entitlements = get_visible_sessions_for_entitlement(entitlement) assert session_entitlements == [catalog_course_run]
def test_course_run_missing_overview_not_fulfillable(self): entitlement = CourseEntitlementFactory.create(mode=CourseMode.VERIFIED) assert not is_course_run_entitlement_fulfillable( CourseKey.from_string('course-v1:edx+FakeCourse+3T2017'), entitlement )
def test_change_enrollment_mode_fullfills_entitlement( self, search_string_type, mock_get_course_uuid): """ Assert that changing student's enrollment fulfills it's respective entitlement if it exists. """ assert ManualEnrollmentAudit.get_manual_enrollment_by_email( self.student.email) is None enrollment = CourseEnrollment.get_enrollment(self.student, self.course.id) entitlement = CourseEntitlementFactory.create( user=self.user, mode=CourseMode.VERIFIED, enrollment_course_run=enrollment) mock_get_course_uuid.return_value = entitlement.course_uuid url = reverse('support:enrollment_list', kwargs={ 'username_or_email': getattr(self.student, search_string_type) }) response = self.client.post(url, data={ 'course_id': str(self.course.id), 'old_mode': CourseMode.AUDIT, 'new_mode': CourseMode.VERIFIED, 'reason': 'Financial Assistance' }) entitlement.refresh_from_db() assert response.status_code == 200 assert ManualEnrollmentAudit.get_manual_enrollment_by_email( self.student.email) is not None assert entitlement.enrollment_course_run is not None assert entitlement.is_entitlement_redeemable() is False self.assert_enrollment(CourseMode.VERIFIED)
def test_ecommerce_refund_not_verified_notification_for_entitlement(self, mock_send_notification): """ Note that we are currently notifying Support whenever a refund require approval for entitlements as Entitlements are only available in paid modes. This test should be updated if this logic changes in the future. PROFESSIONAL mode is used here although we never auto approve PROFESSIONAL refunds right now """ httpretty.register_uri( httpretty.POST, settings.ECOMMERCE_API_URL + 'refunds/', status=201, body='[1]', content_type='application/json' ) httpretty.register_uri( httpretty.PUT, settings.ECOMMERCE_API_URL + 'refunds/1/process/', status=400, body='{}', content_type='application/json' ) course_entitlement = CourseEntitlementFactory.create(mode=CourseMode.PROFESSIONAL) refund_success = refund_entitlement(course_entitlement) assert mock_send_notification.is_called call_args = list(mock_send_notification.call_args) assert call_args[0] == (course_entitlement.user, [1]) assert refund_success
def test_ecommerce_successful_refund(self): httpretty.register_uri( httpretty.POST, settings.ECOMMERCE_API_URL + 'refunds/', status=201, body='[1]', content_type='application/json' ) httpretty.register_uri( httpretty.PUT, settings.ECOMMERCE_API_URL + 'refunds/1/process/', status=200, body=json.dumps({ "id": 9, "created": "2017-12-21T18:23:49.468298Z", "modified": "2017-12-21T18:24:02.741426Z", "total_credit_excl_tax": "100.00", "currency": "USD", "status": "Complete", "order": 15, "user": 5 }), content_type='application/json' ) course_entitlement = CourseEntitlementFactory.create(mode=CourseMode.VERIFIED) refund_success = refund_entitlement(course_entitlement) assert refund_success
def make_entitlement(expired=False): # lint-amnesty, pylint: disable=missing-function-docstring age = CourseEntitlementPolicy.DEFAULT_EXPIRATION_PERIOD_DAYS past_datetime = datetime.now(tz=pytz.UTC) - timedelta(days=age) expired_at = past_datetime if expired else None entitlement = CourseEntitlementFactory.create(created=past_datetime, expired_at=expired_at) return entitlement
def test_get_expired_entitlement_by_uuid(self): past_datetime = now() - timedelta(days=365 * 2) entitlement = CourseEntitlementFactory(created=past_datetime) CourseEntitlementFactory.create_batch(2) CourseEntitlementFactory() url = reverse(self.ENTITLEMENTS_DETAILS_PATH, args=[str(entitlement.uuid)]) response = self.client.get( url, content_type='application/json', ) assert response.status_code == 200 results = response.data assert results.get('expired_at')
def test_email_settings_unfulfilled_entitlement(self, mock_email_feature, mock_course_overview): """ Assert that the Email Settings action is not shown when the entitlement is not fulfilled. """ mock_email_feature.return_value = True mock_course_overview.return_value = CourseOverviewFactory(start=self.TOMORROW) CourseEntitlementFactory(user=self.user) response = self.client.get(self.path) self.assertEqual(pq(response.content)(self.EMAIL_SETTINGS_ELEMENT_ID).length, 0)
def test_get_days_until_expiration(self): """ Test that the expiration period is always less than or equal to the policy expiration """ entitlement = CourseEntitlementFactory.create(enrollment_course_run=self.enrollment) # This will always either be 1 less than the expiration_period_days because the get_days_until_expiration # method will have had at least some time pass between object creation in setUp and this method execution, # or the exact same as the original expiration_period_days if somehow no time has passed assert entitlement.get_days_until_expiration() <= entitlement.policy.expiration_period.days
def test_course_run_not_fulfillable_enrollment_start_in_future(self): course_overview = self.create_course(start_from_now=-3, end_from_now=2, enrollment_start_from_now=2, enrollment_end_from_now=4) entitlement = CourseEntitlementFactory.create(mode=CourseMode.VERIFIED) assert not is_course_run_entitlement_fulfillable( course_overview.id, entitlement)
def test_no_ecommerce_connection_and_failure(self): httpretty.register_uri(httpretty.POST, settings.ECOMMERCE_API_URL + 'refunds/', status=404, body='{}', content_type='application/json') course_entitlement = CourseEntitlementFactory.create( mode=CourseMode.VERIFIED) refund_success = refund_entitlement(course_entitlement) assert not refund_success
def test_delete_and_revoke_entitlement(self): course_entitlement = CourseEntitlementFactory.create() url = reverse(self.ENTITLEMENTS_DETAILS_PATH, args=[str(course_entitlement.uuid)]) response = self.client.delete( url, content_type='application/json', ) assert response.status_code == 204 course_entitlement.refresh_from_db() assert course_entitlement.expired_at is not None
def test_is_entitlement_redeemable(self): """ Test that the entitlement is not expired when created now, and is expired when created 2 years ago with a policy that sets the expiration period to 450 days """ entitlement = CourseEntitlementFactory.create() assert entitlement.is_entitlement_redeemable() is True # Create a date 2 years in the past (greater than the policy expire period of 450 days) past_datetime = now() - timedelta(days=365 * 2) entitlement.created = past_datetime entitlement.save() assert entitlement.is_entitlement_redeemable() is False entitlement = CourseEntitlementFactory.create(expired_at=now()) assert entitlement.is_entitlement_refundable() is False
def test_is_entitlement_refundable(self): """ Test that the entitlement is refundable when created now, and is not refundable when created 70 days ago with a policy that sets the expiration period to 60 days. Also test that if the entitlement is spent and greater than 14 days it is no longer refundable. """ entitlement = CourseEntitlementFactory.create() assert entitlement.is_entitlement_refundable() is True # If there is no order_number make sure the entitlement is not refundable entitlement.order_number = None assert entitlement.is_entitlement_refundable() is False # Create a date 70 days in the past (greater than the policy refund expire period of 60 days) past_datetime = now() - timedelta(days=70) entitlement = CourseEntitlementFactory.create(created=past_datetime) assert entitlement.is_entitlement_refundable() is False entitlement = CourseEntitlementFactory.create( enrollment_course_run=self.enrollment) # Create a date 20 days in the past (less than the policy refund expire period of 60 days) # but more than the policy regain period of 14 days and also the course start past_datetime = now() - timedelta(days=20) entitlement.created = past_datetime self.enrollment.created = past_datetime self.course.start = past_datetime entitlement.save() self.course.save() self.enrollment.save() assert entitlement.is_entitlement_refundable() is False # Removing the entitlement being redeemed, make sure that the entitlement is refundable entitlement.enrollment_course_run = None assert entitlement.is_entitlement_refundable() is True entitlement = CourseEntitlementFactory.create(expired_at=now()) assert entitlement.is_entitlement_refundable() is False
def test_staff_user_required_for_delete(self): not_staff_user = UserFactory() self.client.login(username=not_staff_user.username, password=TEST_PASSWORD) course_entitlement = CourseEntitlementFactory.create() url = reverse(self.ENTITLEMENTS_DETAILS_PATH, args=[str(course_entitlement.uuid)]) response = self.client.delete( url, content_type='application/json', ) assert response.status_code == 403
def test_course_run_not_fulfillable_no_start_date(self): course_overview = self.create_course(start_from_now=-2, end_from_now=2, enrollment_start_from_now=-1, enrollment_end_from_now=1) course_overview.start = None course_overview.save() entitlement = CourseEntitlementFactory.create(mode=CourseMode.VERIFIED) assert not is_course_run_entitlement_fulfillable( course_overview.id, entitlement)
def test_course_run_fulfillable_already_enrolled_course_ended(self): course_overview = self.create_course( start_from_now=-3, end_from_now=-1, enrollment_start_from_now=-2, enrollment_end_from_now=-1, ) entitlement = CourseEntitlementFactory.create(mode=CourseMode.VERIFIED) CourseEnrollmentFactory.create(user=entitlement.user, course_id=course_overview.id) assert is_course_run_entitlement_fulfillable(course_overview.id, entitlement)
def test_is_entitlement_regainable(self): """ Test that the entitlement is not expired when created now, and is expired when created 20 days ago with a policy that sets the expiration period to 14 days """ entitlement = CourseEntitlementFactory.create( enrollment_course_run=self.enrollment) assert entitlement.is_entitlement_regainable() is True # Create and associate a GeneratedCertificate for a user and course and make sure it isn't regainable certificate = GeneratedCertificateFactory( user=entitlement.user, course_id=entitlement.enrollment_course_run.course_id, mode=MODES.verified, status=CertificateStatuses.downloadable, ) assert entitlement.is_entitlement_regainable() is False certificate.status = CertificateStatuses.notpassing certificate.save() assert entitlement.is_entitlement_regainable() is True # Create a date 20 days in the past (greater than the policy expire period of 14 days) # and apply it to both the entitlement and the course past_datetime = now() - timedelta(days=20) entitlement = CourseEntitlementFactory.create( enrollment_course_run=self.enrollment, created=past_datetime) self.enrollment.created = past_datetime self.course.start = past_datetime self.course.save() self.enrollment.save() assert entitlement.is_entitlement_regainable() is False entitlement = CourseEntitlementFactory.create(expired_at=now()) assert entitlement.is_entitlement_regainable
def test_course_run_fulfillable_user_enrolled(self): course_overview = self.create_course( start_from_now=-3, end_from_now=2, enrollment_start_from_now=-2, enrollment_end_from_now=1 ) entitlement = CourseEntitlementFactory.create(mode=CourseMode.VERIFIED) # Enroll User in the Course, but do not update the entitlement CourseEnrollmentFactory.create(user=entitlement.user, course_id=course_overview.id) assert is_course_run_entitlement_fulfillable(course_overview.id, entitlement)
def test_staff_get_expired_entitlements(self): past_datetime = now() - timedelta(days=365 * 2) entitlements = CourseEntitlementFactory.create_batch(2, created=past_datetime, user=self.user) # Set the first entitlement to be at a time that it isn't expired entitlements[0].created = now() entitlements[0].save() response = self.client.get( self.entitlements_list_url, content_type='application/json', ) assert response.status_code == 200 results = response.data.get('results', []) # Make sure that the first result isn't expired, and the second one is also not for staff users assert results[0].get('expired_at') is None and results[1].get('expired_at') is None
def test_check_for_no_entitlement_and_do_not_enroll( self, mock_get_course_uuid): course = CourseFactory() CourseModeFactory( course_id=course.id, mode_slug=CourseMode.VERIFIED, # This must be in the future to ensure it is returned by downstream code. expiration_datetime=now() + timedelta(days=1)) entitlement = CourseEntitlementFactory.create( mode=CourseMode.VERIFIED, user=self.user, ) mock_get_course_uuid.return_value = None assert not CourseEnrollment.is_enrolled(user=self.user, course_key=course.id) CourseEntitlement.check_for_existing_entitlement_and_enroll( user=self.user, course_run_key=course.id, ) assert not CourseEnrollment.is_enrolled(user=self.user, course_key=course.id) entitlement.refresh_from_db() assert entitlement.enrollment_course_run is None new_course = CourseFactory() CourseModeFactory( course_id=new_course.id, # lint-amnesty, pylint: disable=no-member mode_slug=CourseMode.VERIFIED, # This must be in the future to ensure it is returned by downstream code. expiration_datetime=now() + timedelta(days=1)) # Return invalid uuid so that no entitlement returned for this new course mock_get_course_uuid.return_value = uuid4().hex try: CourseEntitlement.check_for_existing_entitlement_and_enroll( user=self.user, course_run_key=new_course.id, ) assert not CourseEnrollment.is_enrolled(user=self.user, course_key=new_course.id) except AttributeError as error: self.fail(error.message) # lint-amnesty, pylint: disable=no-member
def test_email_settings_fulfilled_entitlement(self, mock_email_feature, mock_get_course_runs): """ Assert that the Email Settings action is shown when the user has a fulfilled entitlement. """ mock_email_feature.return_value = True course_overview = CourseOverviewFactory( start=self.TOMORROW, self_paced=True, enrollment_end=self.TOMORROW ) course_enrollment = CourseEnrollmentFactory(user=self.user, course_id=course_overview.id) entitlement = CourseEntitlementFactory(user=self.user, enrollment_course_run=course_enrollment) course_runs = [{ 'key': six.text_type(course_overview.id), 'uuid': entitlement.course_uuid }] mock_get_course_runs.return_value = course_runs response = self.client.get(self.path) self.assertEqual(pq(response.content)(self.EMAIL_SETTINGS_ELEMENT_ID).length, 1)
def test_get_visible_sessions_for_entitlement(self, mock_get_edx_api_data): """ Test retrieval of visible session entitlements. """ catalog_course_run = CourseRunFactory.create() catalog_course = CourseFactory(course_runs=[catalog_course_run]) mock_get_edx_api_data.return_value = catalog_course course_key = CourseKey.from_string(catalog_course_run.get('key')) course_overview = CourseOverviewFactory.create(id=course_key, start=self.tomorrow) CourseModeFactory.create(mode_slug=CourseMode.VERIFIED, min_price=100, course_id=course_overview.id) course_enrollment = CourseEnrollmentFactory( user=self.user, course=course_overview, mode=CourseMode.VERIFIED ) entitlement = CourseEntitlementFactory( user=self.user, enrollment_course_run=course_enrollment, mode=CourseMode.VERIFIED ) session_entitlements = get_visible_sessions_for_entitlement(entitlement) self.assertEqual(session_entitlements, [catalog_course_run])
def test_user_can_enroll(self, mock_get_course_runs): course_entitlement = CourseEntitlementFactory.create( user=self.user, mode=CourseMode.VERIFIED) mock_get_course_runs.return_value = self.return_values url = reverse(self.ENTITLEMENTS_ENROLLMENT_NAMESPACE, args=[str(course_entitlement.uuid)]) assert course_entitlement.enrollment_course_run is None data = {'course_run_id': str(self.course.id)} response = self.client.post( url, data=json.dumps(data), content_type='application/json', ) course_entitlement.refresh_from_db() assert response.status_code == 201 assert CourseEnrollment.is_enrolled(self.user, self.course.id) assert course_entitlement.enrollment_course_run is not None
def test_fulfilled_entitlement(self, mock_course_overview, mock_course_runs, mock_get_programs): """ When a learner has a fulfilled entitlement, their course dashboard should have: - exactly one course item, meaning it: - has an entitlement card - does NOT have a course card referencing the selected session - an unhidden Change or Leave Session button - a related programs message """ mocked_course_overview = CourseOverviewFactory(start=TOMORROW, self_paced=True, enrollment_end=TOMORROW) mock_course_overview.return_value = mocked_course_overview course_enrollment = CourseEnrollmentFactory( user=self.user, course_id=str(mocked_course_overview.id)) mock_course_runs.return_value = [{ 'key': str(mocked_course_overview.id), 'enrollment_end': str(mocked_course_overview.enrollment_end), 'pacing_type': 'self_paced', 'type': 'verified', 'status': 'published' }] entitlement = CourseEntitlementFactory( user=self.user, enrollment_course_run=course_enrollment) program = ProgramFactory() program['courses'][0]['course_runs'] = [{ 'key': str(mocked_course_overview.id) }] program['courses'][0]['uuid'] = entitlement.course_uuid mock_get_programs.return_value = [program] response = self.client.get(self.path) self.assertContains(response, '<li class="course-item">', count=1) self.assertContains(response, '<button class="change-session btn-link "') self.assertContains(response, 'Related Programs:')
def test_data(self): entitlement = CourseEntitlementFactory() request = RequestFactory().get('') serializer = CourseEntitlementSerializer(entitlement, context={'request': request}) expected = { 'user': entitlement.user.username, 'uuid': str(entitlement.uuid), 'expired_at': entitlement.expired_at, 'course_uuid': str(entitlement.course_uuid), 'mode': entitlement.mode, 'refund_locked': False, 'order_number': entitlement.order_number, 'created': entitlement.created.strftime('%Y-%m-%dT%H:%M:%S.%fZ'), 'modified': entitlement.modified.strftime('%Y-%m-%dT%H:%M:%S.%fZ'), 'support_details': [], } assert serializer.data == expected
def test_ecommerce_refund_failed_process_notification_sent( self, mock_send_notification): httpretty.register_uri(httpretty.POST, settings.ECOMMERCE_API_URL + 'refunds/', status=201, body='[1]', content_type='application/json') httpretty.register_uri(httpretty.PUT, settings.ECOMMERCE_API_URL + 'refunds/1/process/', status=400, body='{}', content_type='application/json') course_entitlement = CourseEntitlementFactory.create( mode=CourseMode.VERIFIED) refund_success = refund_entitlement(course_entitlement) assert mock_send_notification.is_called call_args = list(mock_send_notification.call_args) assert call_args[0] == (course_entitlement.user, [1]) assert refund_success
def test_get_user_expired_entitlements(self): past_datetime = now() - timedelta(days=365 * 2) not_staff_user = UserFactory() self.client.login(username=not_staff_user.username, password=TEST_PASSWORD) entitlement_user2 = CourseEntitlementFactory.create_batch(2, user=not_staff_user, created=past_datetime) url = reverse('entitlements_api:v1:entitlements-list') url += '?user={username}'.format(username=not_staff_user.username) # Set the first entitlement to be at a time that it isn't expired entitlement_user2[0].created = now() entitlement_user2[0].save() response = self.client.get( url, content_type='application/json', ) assert response.status_code == 200 results = response.data.get('results', []) assert results[0].get('expired_at') is None and results[1].get('expired_at')