def _configure(self, mode, upgrade_deadline=None, verification_deadline=None): """Configure course modes and deadlines. """ course_mode = CourseModeFactory.create( mode_slug=mode, mode_display_name=mode, ) if upgrade_deadline is not None: course_mode.upgrade_deadline = upgrade_deadline course_mode.save() VerificationDeadline.set_deadline(self.course.id, verification_deadline) return CourseModeForm(instance=course_mode)
def test_remember_donation_for_course(self): # Create the course modes CourseModeFactory.create(mode_slug='honor', course_id=self.course.id) CourseModeFactory.create(mode_slug='verified', course_id=self.course.id, min_price=1) # Choose the mode (POST request) choose_track_url = reverse('course_modes_choose', args=[str(self.course.id)]) self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE['verified']) # Expect that the contribution amount is stored in the user's session assert 'donation_for_course' in self.client.session assert str( self.course.id) in self.client.session['donation_for_course'] actual_amount = self.client.session['donation_for_course'][str( self.course.id)] expected_amount = decimal.Decimal( self.POST_PARAMS_FOR_COURSE_MODE['verified']['contribution']) assert actual_amount == expected_amount
def test_no_id_redirect(self): # Create the course modes CourseModeFactory.create(mode_slug=CourseMode.NO_ID_PROFESSIONAL_MODE, course_id=self.course.id, min_price=100) # Enroll the user in the test course CourseEnrollmentFactory(is_active=False, mode=CourseMode.NO_ID_PROFESSIONAL_MODE, course_id=self.course.id, user=self.user) # Configure whether we're upgrading or not url = reverse('course_modes_choose', args=[six.text_type(self.course.id)]) response = self.client.get(url) # Check whether we were correctly redirected purchase_workflow = "?purchase_workflow=single" start_flow_url = reverse('verify_student_start_flow', args=[six.text_type(self.course.id) ]) + purchase_workflow with mock_payment_processors(): self.assertRedirects(response, start_flow_url)
def test_suggested_prices(self, price_list): # Create the course modes for mode in ('audit', 'honor'): CourseModeFactory.create(mode_slug=mode, course_id=self.course.id) CourseModeFactory.create(mode_slug='verified', course_id=self.course.id, suggested_prices=price_list) # Enroll the user in the test course to emulate # automatic enrollment CourseEnrollmentFactory(is_active=True, course_id=self.course.id, user=self.user) # Verify that the prices render correctly response = self.client.get( reverse('course_modes_choose', args=[str(self.course.id)]), follow=False, ) assert response.status_code == 200
def test_certificate_info_in_response(self): """ Test that certificate has been created and rendered properly with non-audit course mode. """ CourseModeFactory.create(course_id=self.course.id, mode_slug='verified') response = self.client.ajax_post( self._url(), data=CERTIFICATE_JSON_WITH_SIGNATORIES) self.assertEqual(response.status_code, 201) # in html response result = self.client.get_html(self._url()) self.assertContains(result, 'Test certificate') self.assertContains(result, 'Test description') # in JSON response response = self.client.get_json(self._url()) data = json.loads(response.content.decode('utf-8')) self.assertEqual(len(data), 1) self.assertEqual(data[0]['name'], 'Test certificate') self.assertEqual(data[0]['description'], 'Test description') self.assertEqual(data[0]['version'], CERTIFICATE_SCHEMA_VERSION)
def test_expiration_banner_with_expired_upgrade_deadline(self): """ Ensure that a user accessing a course with an expired upgrade deadline will still see the course expiration banner without the upgrade related text. """ past = datetime(2010, 1, 1, tzinfo=UTC) CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=past) course = CourseFactory.create(start=now() - timedelta(days=10)) CourseModeFactory.create(course_id=course.id, mode_slug=CourseMode.AUDIT) CourseModeFactory.create(course_id=course.id, mode_slug=CourseMode.VERIFIED, expiration_datetime=past) user = UserFactory(password=self.TEST_PASSWORD) self.client.login(username=user.username, password=self.TEST_PASSWORD) CourseEnrollment.enroll(user, course.id, mode=CourseMode.AUDIT) url = course_home_url(course) response = self.client.get(url) bannerText = get_expiration_banner_text(user, course) self.assertContains(response, bannerText, html=True) self.assertContains(response, TEST_BANNER_CLASS)
def test_access_denied_fragment_for_full_access_users(self): """ Test that Full Access users do not see the access_denied_fragment or access_denied_message """ mock_request = RequestFactory().get('/') mock_course = Mock(id=self.course_key, user_partitions={}) mock_block = Mock(scope_ids=Mock(usage_id=Mock(course_key=mock_course.id))) CourseModeFactory.create(course_id=mock_course.id, mode_slug='verified') global_staff = GlobalStaffFactory.create() ContentTypeGatingConfig.objects.create(enabled=False, studio_override_enabled=True) partition = create_content_gating_partition(mock_course) with patch( 'crum.get_current_request', return_value=mock_request ): fragment = partition.access_denied_fragment(mock_block, global_staff, FULL_ACCESS, 'test_allowed_group') assert fragment is None message = partition.access_denied_message(mock_block.scope_ids.usage_id, global_staff, FULL_ACCESS, 'test_allowed_group') assert message is None
def setUp(self): super().setUp() self.service = EnrollmentsService() self.course_modes = [ CourseMode.AUDIT, CourseMode.EXECUTIVE_EDUCATION, CourseMode.HONOR, CourseMode.MASTERS, CourseMode.PROFESSIONAL, CourseMode.VERIFIED ] self.course = CourseOverviewFactory.create(enable_proctored_exams=True) for index in range(len(self.course_modes)): course_mode = self.course_modes[index] course_id = self.course.id CourseModeFactory.create(mode_slug=course_mode, course_id=course_id) user = UserFactory( username=f'user{index}', email=f'LEARNER{index}@example.com' ) CourseEnrollment.enroll(user, course_id, mode=course_mode)
def test_generate_example_certs_with_verified_mode(self): # Create verified and honor modes for the course CourseModeFactory.create(course_id=self.COURSE_KEY, mode_slug='honor') CourseModeFactory.create(course_id=self.COURSE_KEY, mode_slug='verified') # Generate certificates for the course with self._mock_xqueue() as mock_queue: certs_api.generate_example_certificates(self.COURSE_KEY) # Verify that the appropriate certs were added to the queue self._assert_certs_in_queue(mock_queue, 2) # Verify that the certificate status is "started" self._assert_cert_status( { 'description': 'verified', 'status': 'started' }, { 'description': 'honor', 'status': 'started' } )
def test_verified_course(self): course = CourseFactory.create( start=now() - timedelta(days=30), run='test', display_name='test', ) CourseModeFactory.create(mode_slug=CourseMode.VERIFIED, course_id=course.id, min_price=10, sku=str(uuid4().hex)) response = self.client.get(self.url, {'course_id': str(course.id)}) assert response.status_code == 200 result = response.data assert 'basket_url' in result assert bool(result['basket_url']) expected = { 'show_upsell': True, 'price': '$10', 'basket_url': result['basket_url'], # Example basket_url: u'/verify_student/upgrade/org.0/course_0/test/' } assert result == expected
def _check_verification_status_off(self, mode, value): """ Check that the css class and the status message are not in the dashboard html. """ CourseModeFactory.create(mode_slug=mode, course_id=self.course.id) CourseEnrollment.enroll(self.user, self.course.location.course_key, mode=mode) if mode == 'verified': # Simulate a successful verification attempt attempt = self.create_and_submit_attempt_for_user(self.user) attempt.approve() response = self.client.get(reverse('dashboard')) if mode == 'audit': # Audit mode does not have a banner. Assert no banner element. self.assertEqual(pq(response.content)(".sts-enrollment").length, 0) else: self.assertNotContains(response, "class=\"course {0}\"".format(mode)) self.assertNotContains(response, value)
def _admin_form(self, mode, upgrade_deadline=None): """Load the course mode admin form. """ course_mode = CourseModeFactory.create( course_id=self.course.id, mode_slug=mode, ) return CourseModeForm({ "course": str(self.course.id), "mode_slug": mode, "mode_display_name": mode, "_expiration_datetime": upgrade_deadline, "currency": "usd", "min_price": 10, }, instance=course_mode)
def test_no_id_redirect(self): # Create the course modes CourseModeFactory.create(mode_slug=CourseMode.NO_ID_PROFESSIONAL_MODE, course_id=self.course.id, min_price=100) # Enroll the user in the test course CourseEnrollmentFactory(is_active=False, mode=CourseMode.NO_ID_PROFESSIONAL_MODE, course_id=self.course.id, user=self.user) # Configure whether we're upgrading or not url = reverse('course_modes_choose', args=[six.text_type(self.course.id)]) response = self.client.get(url) start_flow_url = IDVerificationService.get_verify_location( course_id=self.course.id) # Check whether we were correctly redirected self.assertRedirects(response, start_flow_url, fetch_redirect_response=False)
def test_course_upgrade_notification(self, ecommerce_enabled, has_verified_mode, has_entitlement, sku, should_display): """ Upgrade notification for a course should appear if: - Ecommerce service is enabled - The course has a paid/verified mode - The user doesn't have an entitlement for the course - The course has an associated SKU """ with patch.object(EcommerceService, 'is_enabled', return_value=ecommerce_enabled): course = CourseFactory.create() if has_verified_mode: CourseModeFactory.create( course_id=course.id, mode_slug='verified', mode_display_name='Verified', expiration_datetime=datetime.now(pytz.UTC) + timedelta(days=1), sku=sku) enrollment = CourseEnrollmentFactory(user=self.user, course_id=course.id) if has_entitlement: CourseEntitlementFactory(user=self.user, enrollment_course_run=enrollment) response = self.client.get(reverse('dashboard')) html_fragment = '<div class="message message-upsell has-actions is-shown">' if should_display: self.assertContains(response, html_fragment) else: self.assertNotContains(response, html_fragment)
def test_access_denied_fragment_for_null_request(self): """ Verifies the access denied fragment is visible when HTTP request is not available. Given the HTTP request instance is None Then set the mobile_app context variable to False And the fragment should be created successfully """ mock_request = None mock_course = Mock(id=self.course_key, user_partitions={}) mock_block = Mock(scope_ids=Mock(usage_id=Mock(course_key=mock_course.id))) CourseModeFactory.create(course_id=mock_course.id, mode_slug='audit') CourseModeFactory.create(course_id=mock_course.id, mode_slug='verified') global_staff = GlobalStaffFactory.create() ContentTypeGatingConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1)) partition = create_content_gating_partition(mock_course) with patch( 'crum.get_current_request', return_value=mock_request ): fragment = partition.access_denied_fragment(mock_block, global_staff, LIMITED_ACCESS, [FULL_ACCESS]) assert fragment is not None
def test_professional_enrollment(self, mode): # The only course mode is professional ed CourseModeFactory.create(mode_slug=mode, course_id=self.course.id, min_price=1) # Go to the "choose your track" page choose_track_url = reverse('course_modes_choose', args=[str(self.course.id)]) response = self.client.get(choose_track_url) # Since the only available track is professional ed, expect that # we're redirected immediately to the start of the payment flow. start_flow_url = IDVerificationService.get_verify_location(course_id=self.course.id) self.assertRedirects(response, start_flow_url, fetch_redirect_response=False) # Now enroll in the course CourseEnrollmentFactory( user=self.user, is_active=True, mode=mode, course_id=str(self.course.id), ) # Expect that this time we're redirected to the dashboard (since we're already registered) response = self.client.get(choose_track_url) self.assertRedirects(response, reverse('dashboard'))
def setUp(self): """ Setup components used by each refund test.""" super().setUp() self.user = UserFactory.create(password=self.USER_PASSWORD) self.verified_mode = CourseModeFactory.create( course_id=self.course.id, mode_slug='verified', mode_display_name='Verified', expiration_datetime=datetime.now(pytz.UTC) + timedelta(days=1) ) self.enrollment = CourseEnrollment.enroll(self.user, self.course.id, mode='verified') self.client = Client() cache.clear()
def test_get_visible_sessions_for_entitlement_expired_mode(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, 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) self.assertEqual(session_entitlements, [catalog_course_run])
def test_expired_verified_mode(self): course = CourseFactory.create( start=now() - timedelta(days=30), run='test', display_name='test', ) CourseModeFactory.create( mode_slug=CourseMode.VERIFIED, course_id=course.id, min_price=10, sku=str(uuid4().hex), expiration_datetime=now() - timedelta(days=30), ) response = self.client.get(self.url, {'course_id': str(course.id)}) assert response.status_code == 200 expected = { 'show_upsell': False, 'upsell_flag': True, 'experiment_bucket': 1, 'user_upsell': True, 'basket_url': None, # Expired verified mode means no basket link } assert response.data == expected
def test_certificate_header_data(self): """ Test that get_certificate_header_context from lms.djangoapps.certificates api returns data customized according to site branding. """ # Generate certificates for the course CourseModeFactory.create(course_id=self.COURSE_KEY, mode_slug=CourseMode.HONOR) data = certs_api.get_certificate_header_context(is_secure=True) # Make sure there are not unexpected keys in dict returned by 'get_certificate_header_context' six.assertCountEqual( self, list(data.keys()), ['logo_src', 'logo_url'] ) self.assertIn( self.configuration['logo_image_url'], data['logo_src'] ) self.assertIn( self.configuration['SITE_NAME'], data['logo_url'] )
def test_delete_course_mode_happy_path(self): new_mode = CourseModeFactory.create( course_id=self.course_key, mode_slug='bachelors', mode_display_name='Bachelors', min_price=1000, ) self.client.login(username=self.global_staff.username, password=self.user_password) url = self.get_url(mode_slug='bachelors') response = self.client.delete(url) assert status.HTTP_204_NO_CONTENT == response.status_code assert 0 == CourseMode.objects.filter(course_id=self.course_key, mode_slug='bachelors').count() self.addCleanup(lambda mode: mode.delete(), new_mode)
def test_course_mode_info(self): verified_mode = CourseModeFactory.create( course_id=self.course.id, mode_slug='verified', mode_display_name='Verified', expiration_datetime=datetime.now(pytz.UTC) + timedelta(days=1) ) enrollment = CourseEnrollment.enroll(self.user, self.course.id) course_mode_info = complete_course_mode_info(self.course.id, enrollment) assert course_mode_info['show_upsell'] assert course_mode_info['days_for_upsell'] == 1 verified_mode.expiration_datetime = datetime.now(pytz.UTC) + timedelta(days=-1) verified_mode.save() course_mode_info = complete_course_mode_info(self.course.id, enrollment) assert not course_mode_info['show_upsell'] assert course_mode_info['days_for_upsell'] is None
def test_display_after_discounted_price( self, discounted_price, is_enterprise_enabled, mock_get_course_final_price, mock_enterprise_customer_for_request): verified_mode = CourseModeFactory.create(mode_slug='verified', course_id=self.course.id, sku='dummy') CourseEnrollmentFactory(is_active=True, course_id=self.course.id, user=self.user) mock_enterprise_customer_for_request.return_value = { 'name': 'dummy' } if is_enterprise_enabled else {} mock_get_course_final_price.return_value = discounted_price url = reverse('course_modes_choose', args=[self.course.id]) response = self.client.get(url) if is_enterprise_enabled: self.assertContains(response, discounted_price) self.assertContains(response, verified_mode.min_price)
def setUp(self): super(BulkUnenrollTests, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments self.course = CourseFactory.create() self.audit_mode = CourseModeFactory.create( course_id=self.course.id, mode_slug='audit', mode_display_name='Audit', ) self.user_info = [('amy', '*****@*****.**', 'password'), ('rory', '*****@*****.**', 'password'), ('river', '*****@*****.**', 'password')] self.enrollments = [] self.users = [] for username, email, password in self.user_info: user = UserFactory.create(username=username, email=email, password=password) self.users.append(user) self.enrollments.append( CourseEnrollment.enroll(user, self.course.id, mode='audit'))
def test_all_modes_for_courses(self): now_dt = now() future = now_dt + timedelta(days=1) past = now_dt - timedelta(days=1) # Unexpired, no expiration date CourseModeFactory.create( course_id=self.course_key, mode_display_name="Honor No Expiration", mode_slug="honor_no_expiration", expiration_datetime=None ) # Unexpired, expiration date in future CourseModeFactory.create( course_id=self.course_key, mode_display_name="Honor Not Expired", mode_slug="honor_not_expired", expiration_datetime=future ) # Expired CourseModeFactory.create( course_id=self.course_key, mode_display_name="Verified Expired", mode_slug="verified_expired", expiration_datetime=past ) # We should get all of these back when querying for *all* course modes, # including ones that have expired. other_course_key = CourseLocator(org="not", course="a", run="course") all_modes = CourseMode.all_modes_for_courses([self.course_key, other_course_key]) assert len(all_modes[self.course_key]) == 3 assert all_modes[self.course_key][0].name == 'Honor No Expiration' assert all_modes[self.course_key][1].name == 'Honor Not Expired' assert all_modes[self.course_key][2].name == 'Verified Expired' # Check that we get a default mode for when no course mode is available assert len(all_modes[other_course_key]) == 1 assert all_modes[other_course_key][0] == CourseMode.DEFAULT_MODE
def setUp(self): self.course_overview = CourseOverviewFactory.create() CourseModeFactory.create(course_id=self.course_overview.id, mode_slug='audit') CourseModeFactory.create(course_id=self.course_overview.id, mode_slug='verified') self.user = UserFactory.create() super(TestContentTypeGatingConfig, self).setUp()
def test_linked_in_add_to_profile_btn_with_certificate(self): # If user has a certificate with valid linked-in config then Add Certificate to LinkedIn button # should be visible. and it has URL value with valid parameters. self.client.login(username="******", password="******") linkedin_config = LinkedInAddToProfileConfiguration.objects.create( company_identifier='1337', enabled=True) CourseModeFactory.create(course_id=self.course.id, mode_slug='verified', mode_display_name='verified', expiration_datetime=datetime.now(pytz.UTC) - timedelta(days=1)) CourseEnrollment.enroll(self.user, self.course.id, mode='honor') self.course.certificate_available_date = datetime.now( pytz.UTC) - timedelta(days=1) self.course.start = datetime.now(pytz.UTC) - timedelta(days=2) self.course.end = datetime.now(pytz.UTC) - timedelta(days=1) self.course.display_name = 'Omega' self.course = self.update_course(self.course, self.user.id) cert = GeneratedCertificateFactory.create( user=self.user, course_id=self.course.id, status=CertificateStatuses.downloadable, mode='honor', grade='67', download_url='https://www.edx.org') response = self.client.get(reverse('dashboard')) assert response.status_code == 200 self.assertContains(response, 'Add Certificate to LinkedIn') # We can switch to this and the commented out assertContains once edx-platform reaches Python 3.8 # expected_url = ( # 'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME&' # 'name={platform}+Honor+Code+Certificate+for+Omega&certUrl={cert_url}&' # 'organizationId={company_identifier}' # ).format( # platform=quote(settings.PLATFORM_NAME.encode('utf-8')), # cert_url=quote(cert.download_url, safe=''), # company_identifier=linkedin_config.company_identifier, # ) # self.assertContains(response, escape(expected_url)) # These can be removed (in favor of the above) once we are on Python 3.8. Fails in 3.5 because of dict ordering self.assertContains( response, escape( 'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME' )) self.assertContains( response, escape('&name={platform}+Honor+Code+Certificate+for+Omega'.format( platform=quote(settings.PLATFORM_NAME.encode('utf-8'))))) self.assertContains( response, escape('&certUrl={cert_url}'.format( cert_url=quote(cert.download_url, safe='')))) self.assertContains( response, escape('&organizationId={company_identifier}'.format( company_identifier=linkedin_config.company_identifier)))
def _create_course(cls, run, display_name, modes, component_types, expired_upgrade_deadline=False): """ Helper method to create a course Arguments: run (str): name of course run display_name (str): display name of course modes (list of str): list of modes/tracks this course should have component_types (list of str): list of problem types this course should have Returns: (dict): { 'course': (CourseBlockWithMixins): course definition 'blocks': (dict) { 'block_category_1': XBlock representing that block, 'block_category_2': XBlock representing that block, .... } """ start_date = timezone.now() - timedelta(weeks=1) course = CourseFactory.create(run=run, display_name=display_name, start=start_date) for mode in modes: if expired_upgrade_deadline and mode == 'verified': CourseModeFactory.create( course_id=course.id, mode_slug=mode, expiration_datetime=start_date + timedelta(days=365), ) else: CourseModeFactory.create(course_id=course.id, mode_slug=mode) with cls.store.bulk_operations(course.id): blocks_dict = {} chapter = ItemFactory.create( parent=course, display_name='Overview', ) blocks_dict['chapter'] = ItemFactory.create( parent=course, category='chapter', display_name='Week 1', ) blocks_dict['sequential'] = ItemFactory.create( parent=chapter, category='sequential', display_name='Lesson 1', ) blocks_dict['vertical'] = ItemFactory.create( parent=blocks_dict['sequential'], category='vertical', display_name='Lesson 1 Vertical - Unit 1', ) for component_type in component_types: block = ItemFactory.create(parent=blocks_dict['vertical'], category=component_type, graded=True, metadata={} if (component_type == 'html' or len(modes) == 1) else METADATA) blocks_dict[component_type] = block # Intersperse HTML so that the content-gating renders in all blocks ItemFactory.create( parent=blocks_dict['vertical'], category='html', graded=False, ) return { 'course': course, 'blocks': blocks_dict, }
def setUp(self): self.course_overview = CourseOverviewFactory.create() CourseModeFactory.create(course_id=self.course_overview.id, mode_slug='audit') CourseModeFactory.create(course_id=self.course_overview.id, mode_slug='verified') self.user = UserFactory.create() super().setUp()