def test_is_entitlement_regainable(self): """ Test that the entitlement is not expired when created now, and is expired when created20 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 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 # 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_unfulfilled_entitlement(self, mock_course_overview, mock_pseudo_session, mock_course_runs, mock_get_programs): """ When a learner has an unfulfilled entitlement, their course dashboard should have: - a hidden 'View Course' button - the text 'In order to view the course you must select a session:' - an unhidden course-entitlement-selection-container - a related programs message """ program = ProgramFactory() CourseEntitlementFactory.create(user=self.user, course_uuid=program['courses'][0]['uuid']) mock_get_programs.return_value = [program] course_key = CourseKey.from_string('course-v1:FAKE+FA1-MA1.X+3T2017') mock_course_overview.return_value = CourseOverviewFactory.create(start=self.TOMORROW, id=course_key) mock_course_runs.return_value = [ { 'key': six.text_type(course_key), 'enrollment_end': str(self.TOMORROW), 'pacing_type': 'instructor_paced', 'type': 'verified', 'status': 'published' } ] mock_pseudo_session.return_value = { 'key': six.text_type(course_key), 'type': 'verified' } response = self.client.get(self.path) self.assertIn('class="course-target-link enter-course hidden"', response.content) self.assertIn('You must select a session to access the course.', response.content) self.assertIn('<div class="course-entitlement-selection-container ">', response.content) self.assertIn('Related Programs:', response.content) # If an entitlement has already been redeemed by the user for a course run, do not let the run be selectable enrollment = CourseEnrollmentFactory( user=self.user, course_id=six.text_type(mock_course_overview.return_value.id), mode=CourseMode.VERIFIED ) CourseEntitlementFactory.create( user=self.user, course_uuid=program['courses'][0]['uuid'], enrollment_course_run=enrollment ) mock_course_runs.return_value = [ { 'key': 'course-v1:edX+toy+2012_Fall', 'enrollment_end': str(self.TOMORROW), 'pacing_type': 'instructor_paced', 'type': 'verified', 'status': 'published' } ] response = self.client.get(self.path) # There should be two entitlements on the course page, one prompting for a mandatory session, but no # select option for the courses as there is only the single course run which has already been redeemed self.assertEqual(response.content.count('<li class="course-item">'), 2) self.assertIn('You must select a session to access the course.', response.content) self.assertNotIn('To access the course, select a session.', response.content)
def test_no_tasks_if_no_work(self, mock_task): """ Verify that we never try to spin off a task if there are no database rows. """ call_command('expire_old_entitlements', commit=True) self.assertEqual(mock_task.call_count, 0) # Now confirm that the above test wasn't a fluke and we will create a task if there is work CourseEntitlementFactory.create() call_command('expire_old_entitlements', commit=True) self.assertEqual(mock_task.call_count, 1)
def test_no_commit(self, mock_task): """ Verify that relevant tasks are only enqueued when the commit option is passed. """ CourseEntitlementFactory.create() call_command('expire_old_entitlements') self.assertEqual(mock_task.call_count, 0) call_command('expire_old_entitlements', commit=True) self.assertEqual(mock_task.call_count, 1)
def test_get_entitlements(self, search_string_type): CourseEntitlementFactory.create(mode=CourseMode.VERIFIED, user=self.student, course_uuid=self.course_uuid) url = self.url + getattr(self.student, search_string_type) response = self.client.get(url) self.assertEqual(response.status_code, 200) data = json.loads(response.content) self.assertEqual(len(data), 1) self.assertDictContainsSubset({ 'user': self.student.username, 'course_uuid': unicode(self.course_uuid), 'enrollment_course_run': None, 'mode': CourseMode.VERIFIED, 'support_details': [] }, data[0])
def test_get_user_entitlements(self): user2 = UserFactory() CourseEntitlementFactory.create() entitlement_user2 = CourseEntitlementFactory.create(user=user2) url = reverse('entitlements_api:v1:entitlements-list') url += '?user={username}'.format(username=user2.username) response = self.client.get( url, content_type='application/json', ) assert response.status_code == 200 results = response.data.get('results', []) assert results == CourseEntitlementSerializer([entitlement_user2], many=True).data
def test_pagination(self, mock_task): """ Verify that we chunk up our requests to celery. """ for _ in range(5): CourseEntitlementFactory.create() call_command('expire_old_entitlements', commit=True, batch_size=2) args_list = mock_task.call_args_list self.assertEqual(len(args_list), 3) self.assertEqual(args_list[0][0], (1, 3)) self.assertEqual(args_list[1][0], (3, 5)) self.assertEqual(args_list[2][0], (5, 6))
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_reinstate_entitlement(self): enrollment = CourseEnrollmentFactory(user=self.user, is_active=True) expired_entitlement = CourseEntitlementFactory.create( user=self.user, enrollment_course_run=enrollment, expired_at=datetime.now() ) url = reverse(self.ENTITLEMENTS_DETAILS_PATH, args=[str(expired_entitlement.uuid)]) update_data = { 'expired_at': None, 'enrollment_course_run': None, 'support_details': [ { 'unenrolled_run': str(enrollment.course.id), 'action': 'REISSUE', 'comments': 'Severe illness.' } ] } response = self.client.patch( url, data=json.dumps(update_data), content_type='application/json' ) assert response.status_code == 200 results = response.data reinstated_entitlement = CourseEntitlement.objects.get( uuid=expired_entitlement.uuid ) assert results == CourseEntitlementSerializer(reinstated_entitlement).data
def test_user_already_enrolled_in_unpaid_mode(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)] ) CourseEnrollment.enroll(self.user, self.course.id, mode=CourseMode.AUDIT) 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) (enrolled_mode, is_active) = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id) assert is_active and (enrolled_mode == course_entitlement.mode) assert course_entitlement.enrollment_course_run is not None
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_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 test_reinstate_unrefundable_entitlement(self): """ Verify that a no longer refundable entitlement does not become refundable when support reinstates it. """ enrollment = CourseEnrollmentFactory(user=self.user, is_active=True) expired_entitlement = CourseEntitlementFactory.create( user=self.user, enrollment_course_run=enrollment, expired_at=datetime.now() ) assert expired_entitlement.is_entitlement_refundable() is False url = reverse(self.ENTITLEMENTS_DETAILS_PATH, args=[str(expired_entitlement.uuid)]) update_data = { 'expired_at': None, 'enrollment_course_run': None, 'support_details': [ { 'unenrolled_run': str(enrollment.course.id), 'action': CourseEntitlementSupportDetail.REISSUE, 'comments': 'Severe illness.' } ] } response = self.client.patch( url, data=json.dumps(update_data), content_type='application/json' ) assert response.status_code == 200 reinstated_entitlement = CourseEntitlement.objects.get( uuid=expired_entitlement.uuid ) assert reinstated_entitlement.refund_locked is True assert reinstated_entitlement.is_entitlement_refundable() is False
def test_user_can_unenroll(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) response = self.client.delete( url, content_type='application/json', ) assert response.status_code == 204 course_entitlement.refresh_from_db() assert not CourseEnrollment.is_enrolled(self.user, self.course.id) assert course_entitlement.enrollment_course_run is None
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_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_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_course_run_not_fullfillable_enroll_period_ended(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) assert not is_course_run_entitlement_fulfillable(course_overview.id, entitlement)
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_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_staff_get_only_staff_entitlements(self): CourseEntitlementFactory.create_batch(2) entitlement = CourseEntitlementFactory.create(user=self.user) response = self.client.get( self.entitlements_list_url, content_type='application/json', ) assert response.status_code == 200 results = response.data.get('results', []) assert results == CourseEntitlementSerializer([entitlement], many=True).data
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_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_non_staff_get_select_entitlements(self): not_staff_user = UserFactory() self.client.login(username=not_staff_user.username, password=TEST_PASSWORD) CourseEntitlementFactory.create_batch(2) entitlement = CourseEntitlementFactory.create(user=not_staff_user) response = self.client.get( self.entitlements_list_url, content_type='application/json', ) assert response.status_code == 200 results = response.data.get('results', []) # pylint: disable=no-member assert results == CourseEntitlementSerializer([entitlement], many=True).data
def test_course_run_fullfillable_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_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_create_entitlement(self): CourseEntitlementFactory.create( mode=CourseMode.VERIFIED, user=self.student, course_uuid=self.course_uuid, expired_at=datetime.now() ) url = self.url + self.student.username response = self.client.post( url, data=json.dumps({ 'course_uuid': unicode(self.course_uuid), 'reason': CourseEntitlementSupportDetail.LEARNER_REQUEST_NEW, 'mode': CourseMode.VERIFIED }), content_type='application/json', ) self.assertEqual(response.status_code, 201) data = json.loads(response.content) self.assertEqual(len(data['support_details']), 1) self.assertDictContainsSubset({ 'support_user': self.user.username, 'reason': CourseEntitlementSupportDetail.LEARNER_REQUEST_NEW, 'comments': None, 'unenrolled_run': None }, data['support_details'][0])
def test_get_entitlement_by_uuid(self): entitlement = CourseEntitlementFactory.create() CourseEntitlementFactory.create_batch(2) 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 == CourseEntitlementSerializer(entitlement).data and results.get('expired_at') is None
def test_revoke_unenroll_entitlement(self, mock_course_uuid): enrollment = CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id, is_active=True) course_entitlement = CourseEntitlementFactory.create(user=self.user, enrollment_course_run=enrollment) mock_course_uuid.return_value = course_entitlement.course_uuid url = reverse(self.ENTITLEMENTS_DETAILS_PATH, args=[str(course_entitlement.uuid)]) assert course_entitlement.enrollment_course_run is not None 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 assert course_entitlement.enrollment_course_run is None
def test_ecommerce_refund_send_notification_failed(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' ) mock_send_notification.side_effect = NotImplementedError 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 not refund_success
def test_revoke_unenroll_entitlement(self): course_entitlement = CourseEntitlementFactory.create() url = reverse(self.ENTITLEMENTS_DETAILS_PATH, args=[str(course_entitlement.uuid)]) enrollment = CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id) course_entitlement.refresh_from_db() course_entitlement.enrollment_course_run = enrollment course_entitlement.save() assert course_entitlement.enrollment_course_run is not None 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 assert course_entitlement.enrollment_course_run is None
def test_user_can_revoke_and_refund(self, mock_get_course_runs, mock_refund_entitlement): 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) # Unenroll with Revoke for refund revoke_url = url + '?is_refund=true' response = self.client.delete( revoke_url, content_type='application/json', ) assert response.status_code == 204 course_entitlement.refresh_from_db() assert mock_refund_entitlement.is_called assert (CourseEntitlementSerializer( mock_refund_entitlement.call_args[1]['course_entitlement']).data == CourseEntitlementSerializer(course_entitlement).data) assert not CourseEnrollment.is_enrolled(self.user, self.course.id) assert course_entitlement.enrollment_course_run is None assert course_entitlement.expired_at is not None
def test_user_cannot_enroll_in_unknown_course_run_id(self, mock_get_course_runs): fake_course_str = str(self.course.id) + 'fake' fake_course_key = CourseKey.from_string(fake_course_str) 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)] ) data = { 'course_run_id': str(fake_course_key) } response = self.client.post( url, data=json.dumps(data), content_type='application/json', ) expected_message = 'The Course Run ID is not a match for this Course Entitlement.' assert response.status_code == 400 assert response.data['message'] == expected_message # pylint: disable=no-member assert not CourseEnrollment.is_enrolled(self.user, fake_course_key)
def test_user_can_switch(self, mock_get_course_runs): mock_get_course_runs.return_value = self.return_values course_entitlement = CourseEntitlementFactory.create(user=self.user, mode=CourseMode.VERIFIED) 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) data = { 'course_run_id': str(self.course2.id) } response = self.client.post( url, data=json.dumps(data), content_type='application/json', ) assert response.status_code == 201 course_entitlement.refresh_from_db() assert CourseEnrollment.is_enrolled(self.user, self.course2.id) assert course_entitlement.enrollment_course_run is not None
def test_enrollment_closed_upgrade_open(self, mock_get_course_runs): """ Test that user can still select a session while course enrollment is closed and upgrade deadline is in future. """ course_entitlement = CourseEntitlementFactory.create(user=self.user, mode=CourseMode.VERIFIED) mock_get_course_runs.return_value = self.return_values # Setup enrollment period to be in the past utc_now = datetime.now(UTC) self.course.enrollment_start = utc_now - timedelta(days=15) self.course.enrollment_end = utc_now - timedelta(days=1) self.course = self.update_course(self.course, self.user.id) CourseOverview.update_select_courses([self.course.id], force_update=True) assert CourseEnrollment.is_enrollment_closed(self.user, self.course) assert not CourseEnrollment.is_enrolled(self.user, self.course.id) url = reverse( self.ENTITLEMENTS_ENROLLMENT_NAMESPACE, args=[str(course_entitlement.uuid)] ) 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_dashboard_with_resume_buttons_and_view_buttons(self): ''' The Test creates a four-course-card dashboard. The user completes course blocks in the even-numbered course cards. The test checks that courses with completion data have course cards with "Resume Course" buttons; those without have "View Course" buttons. ''' self.override_waffle_switch(True) isEven = lambda n: n % 2 == 0 num_course_cards = 4 html_for_view_buttons = [] html_for_resume_buttons = [] html_for_entitlement = [] for i in range(num_course_cards): course = CourseFactory.create() course_enrollment = CourseEnrollmentFactory( user=self.user, course_id=course.id ) course_key = course_enrollment.course_id course_key_string = str(course_key) if i == 1: CourseEntitlementFactory.create(user=self.user, enrollment_course_run=course_enrollment) else: last_completed_block_string = '' course_run_string = self._pull_course_run_from_course_key( course_key_string) # Submit completed course blocks in even-numbered courses. if isEven(i): block_keys = [ ItemFactory.create( category='video', parent_location=course.location, display_name='Video {0}'.format(six.text_type(number)) ).location for number in range(5) ] last_completed_block_string = str(block_keys[-1]) submit_completions_for_testing(self.user, block_keys) html_for_view_buttons.append( self._get_html_for_view_course_button( course_key_string, course_run_string ) ) html_for_resume_buttons.append( self._get_html_for_resume_course_button( course_key_string, last_completed_block_string, course_run_string ) ) html_for_entitlement.append( self._get_html_for_entitlement_button( course_key_string ) ) response = self.client.get(reverse('dashboard')) html_for_view_buttons = [ self._remove_whitespace_from_html_string(button) for button in html_for_view_buttons ] html_for_resume_buttons = [ self._remove_whitespace_from_html_string(button) for button in html_for_resume_buttons ] html_for_entitlement = [ self._remove_whitespace_from_html_string(button) for button in html_for_entitlement ] dashboard_html = self._remove_whitespace_from_response(response) for i in range(num_course_cards): expected_button = None unexpected_button = None if i == 1: expected_button = html_for_entitlement[i] unexpected_button = html_for_view_buttons[i] + html_for_resume_buttons[i] elif isEven(i): expected_button = html_for_resume_buttons[i] unexpected_button = html_for_view_buttons[i] + html_for_entitlement[i] else: expected_button = html_for_view_buttons[i] unexpected_button = html_for_resume_buttons[i] + html_for_entitlement[i] self.assertIn( expected_button, dashboard_html ) self.assertNotIn( unexpected_button, dashboard_html )
def test_ecommerce_service_not_configured(self, mock_commerce_configured): course_entitlement = CourseEntitlementFactory.create( mode=CourseMode.VERIFIED) refund_success = refund_entitlement(course_entitlement) assert mock_commerce_configured.is_called assert not refund_success
def test_expired_at_datetime(self): """ Tests that using the getter method properly updates the expired_at field for an entitlement """ # Verify a brand new entitlement isn't expired and the db row isn't updated entitlement = CourseEntitlementFactory.create() expired_at_datetime = entitlement.expired_at_datetime assert expired_at_datetime is None assert entitlement.expired_at is None # Verify an entitlement from two years ago is expired and the db row is updated past_datetime = datetime.utcnow().replace(tzinfo=pytz.UTC) - timedelta( days=365 * 2) entitlement.created = past_datetime entitlement.save() expired_at_datetime = entitlement.expired_at_datetime assert expired_at_datetime assert entitlement.expired_at # Verify that a brand new entitlement that has been redeemed is not expired entitlement = CourseEntitlementFactory.create( enrollment_course_run=self.enrollment) assert entitlement.enrollment_course_run expired_at_datetime = entitlement.expired_at_datetime assert expired_at_datetime is None assert entitlement.expired_at is None # Verify that an entitlement that has been redeemed but not within 14 days # and the course started more than two weeks ago is expired past_datetime = datetime.utcnow().replace(tzinfo=pytz.UTC) - 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.enrollment_course_run expired_at_datetime = entitlement.expired_at_datetime assert expired_at_datetime assert entitlement.expired_at # Verify that an entitlement that has just been created, but the user has been enrolled in the course for # greater than 14 days, and the course started more than 14 days ago is not expired entitlement = CourseEntitlementFactory.create( enrollment_course_run=self.enrollment) past_datetime = datetime.utcnow().replace(tzinfo=pytz.UTC) - timedelta( days=20) entitlement.created = datetime.utcnow().replace(tzinfo=pytz.UTC) self.enrollment.created = past_datetime self.course.start = past_datetime entitlement.save() self.enrollment.save() self.course.save() assert entitlement.enrollment_course_run expired_at_datetime = entitlement.expired_at_datetime assert expired_at_datetime is None assert entitlement.expired_at is None # Verify a date 451 days in the past (1 days after the policy expiration) # That is enrolled and started in within the regain period is still expired entitlement = CourseEntitlementFactory.create( enrollment_course_run=self.enrollment) expired_datetime = datetime.utcnow().replace( tzinfo=pytz.UTC) - timedelta(days=451) entitlement.created = expired_datetime now = datetime.now(tz=pytz.UTC) self.enrollment.created = now self.course.start = now entitlement.save() self.course.save() self.enrollment.save() assert entitlement.enrollment_course_run expired_at_datetime = entitlement.expired_at_datetime assert expired_at_datetime assert entitlement.expired_at
def test_unfulfilled_entitlement(self, mock_course_overview, mock_pseudo_session, mock_course_runs, mock_get_programs): """ When a learner has an unfulfilled entitlement, their course dashboard should have: - a hidden 'View Course' button - the text 'In order to view the course you must select a session:' - an unhidden course-entitlement-selection-container - a related programs message """ program = ProgramFactory() CourseEntitlementFactory.create( user=self.user, course_uuid=program['courses'][0]['uuid']) mock_get_programs.return_value = [program] course_key = CourseKey.from_string('course-v1:FAKE+FA1-MA1.X+3T2017') mock_course_overview.return_value = CourseOverviewFactory.create( start=self.TOMORROW, id=course_key) mock_course_runs.return_value = [{ 'key': six.text_type(course_key), 'enrollment_end': str(self.TOMORROW), 'pacing_type': 'instructor_paced', 'type': 'verified', 'status': 'published' }] mock_pseudo_session.return_value = { 'key': six.text_type(course_key), 'type': 'verified' } response = self.client.get(self.path) self.assertContains(response, 'class="course-target-link enter-course hidden"') self.assertContains(response, 'You must select a session to access the course.') self.assertContains( response, '<div class="course-entitlement-selection-container ">') self.assertContains(response, 'Related Programs:') # If an entitlement has already been redeemed by the user for a course run, do not let the run be selectable enrollment = CourseEnrollmentFactory( user=self.user, course_id=six.text_type(mock_course_overview.return_value.id), mode=CourseMode.VERIFIED) CourseEntitlementFactory.create( user=self.user, course_uuid=program['courses'][0]['uuid'], enrollment_course_run=enrollment) mock_course_runs.return_value = [{ 'key': 'course-v1:edX+toy+2012_Fall', 'enrollment_end': str(self.TOMORROW), 'pacing_type': 'instructor_paced', 'type': 'verified', 'status': 'published' }] response = self.client.get(self.path) # There should be two entitlements on the course page, one prompting for a mandatory session, but no # select option for the courses as there is only the single course run which has already been redeemed self.assertContains(response, '<li class="course-item">', count=2) self.assertContains(response, 'You must select a session to access the course.') self.assertNotContains(response, 'To access the course, select a session.')
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 make_entitlement(expired=False): age = CourseEntitlementPolicy.DEFAULT_EXPIRATION_PERIOD_DAYS past_datetime = datetime.now(tz=pytz.UTC) - timedelta(days=age) expired_at = past_datetime if expired else None return CourseEntitlementFactory.create(created=past_datetime, expired_at=expired_at)