예제 #1
0
    def test_interlocked_overrides(self):
        waffle_flag1 = self.waffle_flag
        waffle_flag2 = WaffleFlag(waffle_flag1.name + "2", __name__)
        waffle_flag2.cached_flags()[waffle_flag2.name] = True

        self.assertFalse(waffle_flag1.is_enabled())
        self.assertTrue(waffle_flag2.is_enabled())

        with override_waffle_flag(waffle_flag1, True):
            with override_waffle_flag(waffle_flag2, False):
                self.assertTrue(waffle_flag1.is_enabled())
                self.assertFalse(waffle_flag2.is_enabled())

        self.assertFalse(waffle_flag1.is_enabled())
        self.assertTrue(waffle_flag2.is_enabled())
예제 #2
0
 def test_course_waffle_flag(self, waffle_enabled, course_override, result):
     """
     Tests various combinations of a flag being set in waffle and overridden for a course.
     """
     with override_waffle_flag(self.TEST_COURSE_FLAG,
                               active=waffle_enabled):
         with patch.object(WaffleFlagCourseOverrideModel,
                           'override_value',
                           return_value=course_override):
             # check twice to test that the result is properly cached
             assert self.TEST_COURSE_FLAG.is_enabled(
                 self.TEST_COURSE_KEY) == result
             assert self.TEST_COURSE_FLAG.is_enabled(
                 self.TEST_COURSE_KEY) == result
             # result is cached, so override check should happen only once
             # pylint: disable=no-member
             WaffleFlagCourseOverrideModel.override_value.assert_called_once_with(
                 self.NAMESPACED_FLAG_NAME, self.TEST_COURSE_KEY)
         # Check flag for a second course.
         # This should be the same cached value as for overriden flag.
         assert self.TEST_COURSE_FLAG.is_enabled(
             self.TEST_COURSE_2_KEY) == waffle_enabled
     # Check the default value for the second course after the cache was restored
     assert self.TEST_COURSE_FLAG.is_enabled(
         self.TEST_COURSE_2_KEY) is False
예제 #3
0
    def test_verified_mode_only(self):
        # Create only the verified mode and enroll the user
        CourseModeFactory.create(
            mode_slug='verified',
            course_id=self.course_that_started.id,
            min_price=149,
        )
        CourseEnrollmentFactory(is_active=True,
                                course_id=self.course_that_started.id,
                                user=self.user)

        # Value Prop TODO (REV-2378): remove waffle flag from tests once the new Track Selection template is rolled out.
        with override_waffle_flag(VALUE_PROP_TRACK_SELECTION_FLAG,
                                  active=True):
            with patch(GATING_METHOD_NAME, return_value=True):
                with patch(CDL_METHOD_NAME, return_value=True):
                    url = reverse('course_modes_choose',
                                  args=[str(self.course_that_started.id)])
                    response = self.client.get(url)
                    # Check that only the verified option is rendered
                    self.assertNotContains(response,
                                           "Choose a path for your course in")
                    self.assertContains(response, "Earn a certificate")
                    self.assertNotContains(response, "Access this course")
                    self.assertContains(response, '<div class="grid-single">')
                    self.assertNotContains(response,
                                           '<div class="grid-options">')
예제 #4
0
    def test_errors(self, has_perm, post_params, error_msg, status_code, mock_has_perm):
        """
        Test the error template is rendered on different types of errors.
        When the chosen CourseMode is 'honor' or 'audit' via POST,
        it redirects to dashboard, but if there's an error in the process,
        it shows the error template.
        If the user does not have permission to enroll, GET is called with error message,
        but it also redirects to dashboard.
        """
        # Create course modes
        for mode in ('audit', 'honor', 'verified'):
            CourseModeFactory.create(mode_slug=mode, course_id=self.course.id)

        # Value Prop TODO (REV-2378): remove waffle flag from tests once flag is removed.
        with override_waffle_flag(VALUE_PROP_TRACK_SELECTION_FLAG, active=True):
            mock_has_perm.return_value = has_perm
            url = reverse('course_modes_choose', args=[str(self.course.id)])

            # Choose mode (POST request)
            response = self.client.post(url, post_params)
            self.assertEqual(response.status_code, status_code)

            if has_perm:
                self.assertContains(response, error_msg)
                self.assertContains(response, 'Sorry, we were unable to enroll you')

                # Check for CTA button on error page
                marketing_root = settings.MKTG_URLS.get('ROOT')
                search_courses_url = urljoin(marketing_root, '/search?tab=course')
                self.assertContains(response, search_courses_url)
                self.assertContains(response, '<span>Explore all courses</span>')
            else:
                self.assertTrue(CourseEnrollment.is_enrollment_closed(self.user, self.course))
예제 #5
0
 def test_api_returns_live_iframe(self):
     request = RequestFactory().get(self.url)
     request.user = self.user
     live_config = CourseLiveConfiguration.objects.create(
         course_key=self.course.id,
         enabled=True,
         provider_type="zoom",
     )
     live_config.lti_configuration = LtiConfiguration.objects.create(
         config_store=LtiConfiguration.CONFIG_ON_DB,
         lti_config={
             "pii_share_username": '******',
             "pii_share_email": 'true',
             "additional_parameters": {
                 "custom_instructor_email": "*****@*****.**"
             }
         },
         lti_1p1_launch_url='http://test.url',
         lti_1p1_client_key='test_client_key',
         lti_1p1_client_secret='test_client_secret',
     )
     live_config.save()
     with override_waffle_flag(ENABLE_COURSE_LIVE, True):
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 200)
         self.assertIsInstance(response.data['iframe'], Markup)
         self.assertIn('iframe', str(response.data['iframe']))
예제 #6
0
    def test_redirect_view(self):
        old_url_path = reverse('account_settings')
        with override_waffle_flag(REDIRECT_TO_ACCOUNT_MICROFRONTEND, active=True):
            # Test with waffle flag active and none site setting, redirects to microfrontend
            response = self.client.get(path=old_url_path)
            self.assertRedirects(response, settings.ACCOUNT_MICROFRONTEND_URL, fetch_redirect_response=False)

        # Test with waffle flag disabled and site setting disabled, does not redirect
        response = self.client.get(path=old_url_path)
        for attribute in self.FIELDS:
            self.assertContains(response, attribute)

        # Test with site setting disabled, does not redirect
        site_domain = 'othersite.example.com'
        site = self.set_up_site(site_domain, {
            'SITE_NAME': site_domain,
            'ENABLE_ACCOUNT_MICROFRONTEND': False
        })
        self.client.login(username=self.USERNAME, password=self.PASSWORD)
        response = self.client.get(path=old_url_path)
        for attribute in self.FIELDS:
            self.assertContains(response, attribute)

        # Test with site setting enabled, redirects to microfrontend
        site.configuration.site_values['ENABLE_ACCOUNT_MICROFRONTEND'] = True
        site.configuration.save()
        site.__class__.objects.clear_cache()
        response = self.client.get(path=old_url_path)
        self.assertRedirects(response, settings.ACCOUNT_MICROFRONTEND_URL, fetch_redirect_response=False)
예제 #7
0
    def test_matching_org_override_waffle_flag(self, waffle_enabled,
                                               org_override_choice,
                                               is_enabled):
        """
        Tests various combinations of a flag being set in waffle and overridden for an org
        which is the org which authored/owns the course.
        Since the org-level override has the same org as the course being checked, the org-level
        override's on/off/unset state determines whether is CourseWaffleFlag is active or not.

        on    = active (enabled)
        off   = inactive (disabled)
        unset = mirror the base waffle flag's activity
        """
        WaffleFlagOrgOverrideModel.objects.create(
            waffle_flag=self.NAMESPACED_FLAG_NAME,
            org=self.TEST_ORG,
            override_choice=org_override_choice,
            note='',
            enabled=True)
        # Both course keys should match the org-level override.
        with override_waffle_flag(self.TEST_COURSE_FLAG,
                                  active=waffle_enabled):
            assert self.TEST_COURSE_FLAG.is_enabled(
                self.TEST_COURSE_KEY) == is_enabled
            assert self.TEST_COURSE_FLAG.is_enabled(
                self.TEST_COURSE_2_KEY) == is_enabled
예제 #8
0
    def test_hide_learning_sequences(self):
        """
        Check that Learning Sequences filters out sequences.
        """
        CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED)

        # Normal behavior: the sequence exists
        response = self.client.get(self.url)
        assert response.status_code == 200
        blocks = response.data['course_blocks']['blocks']
        seq_block_id = next(
            block_id
            for block_id, block in blocks.items()
            if block['type'] == 'sequential'
        )

        # With Learning Sequences active and a course outline loaded, the same
        # sequence is removed.
        with override_waffle_flag(USE_FOR_OUTLINES, active=True):
            new_learning_seq_outline = CourseOutlineData(
                course_key=self.course.id,
                title="Test Course Outline!",
                published_at=datetime(2021, 6, 14, tzinfo=timezone.utc),
                published_version="5ebece4b69dd593d82fe2022",
                entrance_exam_id=None,
                days_early_for_beta=None,
                sections=[],
                self_paced=False,
                course_visibility=CourseVisibility.PRIVATE  # pylint: disable=protected-access
            )
            replace_course_outline(new_learning_seq_outline)
            response = self.client.get(self.url)
            blocks = response.data['course_blocks']['blocks']
            assert seq_block_id not in blocks
예제 #9
0
    def test_override_waffle_flag_as_context_manager(self):
        self.assertFalse(self.waffle_flag.is_enabled())

        with override_waffle_flag(self.waffle_flag, True):
            self.assertTrue(self.waffle_flag.is_enabled())

        self.assertFalse(self.waffle_flag.is_enabled())
예제 #10
0
    def test_upgrade_message_discount(self):
        # pylint: disable=no-member
        CourseEnrollment.enroll(self.user, self.course.id, CourseMode.AUDIT)

        with override_waffle_flag(SHOW_UPGRADE_MSG_ON_COURSE_HOME, True):
            response = self.client.get(self.url)

        self.assertContains(response, "<span>DISCOUNT_PRICE</span>")
예제 #11
0
    def test_interlocked_overrides(self):
        waffle_flag1 = self.waffle_flag
        waffle_flag2 = WaffleFlag(  # lint-amnesty, pylint: disable=toggle-missing-annotation
            waffle_flag1.name + "2", __name__
        )
        waffle_flag2.cached_flags()[waffle_flag2.name] = True

        self.assertFalse(waffle_flag1.is_enabled())
        self.assertTrue(waffle_flag2.is_enabled())

        with override_waffle_flag(waffle_flag1, True):
            with override_waffle_flag(waffle_flag2, False):
                self.assertTrue(waffle_flag1.is_enabled())
                self.assertFalse(waffle_flag2.is_enabled())

        self.assertFalse(waffle_flag1.is_enabled())
        self.assertTrue(waffle_flag2.is_enabled())
예제 #12
0
 def test_pages_and_resources_toggle(self, toggle_active, assert_count):
     """Basic check that the Pages page responds correctly"""
     with override_waffle_flag(ENABLE_PAGES_AND_RESOURCES_MICROFRONTEND,
                               active=toggle_active):
         resp = self.client.get_html(self.url)
         self.assertContains(resp,
                             'View new Pages and Resources Experience',
                             count=assert_count)
 def test_feature_enabled_for_user(self):
     goal = self.make_valid_goal()
     with override_waffle_flag(ENABLE_COURSE_GOALS, active=None):
         # We want to ensure that when we set up a fake request
         # it works correctly if the flag is only enabled for specific users
         flag = get_waffle_flag_model().get(ENABLE_COURSE_GOALS.name)
         flag.users.add(goal.user)
         self.call_command(expect_sent=True)
예제 #14
0
 def test_special_exams_enabled_for_course(self, is_globaly_enabled, is_waffle_enabled):
     """ Ensure that special exams flag present in courseware meta data with expected value """
     with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_SPECIAL_EXAMS': is_globaly_enabled}):
         with override_waffle_flag(COURSEWARE_MICROFRONTEND_SPECIAL_EXAMS, active=is_waffle_enabled):
             response = self.client.get(self.url)
             assert response.status_code == 200
             courseware_data = response.json()
             assert 'is_mfe_special_exams_enabled' in courseware_data
             assert courseware_data['is_mfe_special_exams_enabled'] == (is_globaly_enabled and is_waffle_enabled)
예제 #15
0
 def test_streak_segment_suppressed_for_unverified(self):
     """ Test that metadata endpoint does not return a discount and signal is not sent if flag is not set """
     CourseEnrollment.enroll(self.user, self.course.id, 'audit')
     with override_waffle_flag(COURSEWARE_MFE_MILESTONES_STREAK_DISCOUNT, active=False):
         with mock.patch('common.djangoapps.student.models.UserCelebration.perform_streak_updates', return_value=3):
             response = self.client.get(self.url, content_type='application/json')
             celebrations = response.json()['celebrations']
             assert celebrations['streak_length_to_celebrate'] == 3
             assert celebrations['streak_discount_enabled'] is False
예제 #16
0
 def test_streak_data_in_response(self):
     """ Test that metadata endpoint returns data for the streak celebration """
     CourseEnrollment.enroll(self.user, self.course.id, 'audit')
     with override_waffle_flag(COURSEWARE_MFE_MILESTONES_STREAK_DISCOUNT, active=True):
         with mock.patch('common.djangoapps.student.models.UserCelebration.perform_streak_updates', return_value=3):
             response = self.client.get(self.url, content_type='application/json')
             celebrations = response.json()['celebrations']
             assert celebrations['streak_length_to_celebrate'] == 3
             assert celebrations['streak_discount_enabled'] is True
예제 #17
0
 def test_waffle_flag_settings(self, mock_olxcleaner_validate):
     """
     Tests olx validation in case of waffle flag is off.
     """
     with override_waffle_flag(self.waffle_flg, active=False):
         self.assertTrue(
             validate_course_olx(self.course.id, self.toy_course_path,
                                 self.status))
         self.assertFalse(mock_olxcleaner_validate.called)
예제 #18
0
def override_experiment_waffle_flag(flag, active=True, bucket=1):
    """
    Override both the base waffle flag and the experiment bucket value.
    """
    if not active:
        bucket = 0
    with override_waffle_flag(flag, active):
        with patch.object(flag, "get_bucket", return_value=bucket):
            yield
예제 #19
0
    def test_without_request_and_everyone_active_waffle(self):
        """
        Test the flag behavior when outside a request context and waffle active for everyone.
        """
        crum.set_current_request(None)

        test_course_flag = CourseWaffleFlag(self.NAMESPACED_FLAG_NAME,
                                            __name__)
        with override_waffle_flag(self.TEST_COURSE_FLAG, active=True):
            assert test_course_flag.is_enabled(self.TEST_COURSE_KEY) is True
예제 #20
0
    def test_admin_login_redirect(self):
        with override_waffle_switch(ENABLE_LOGIN_USING_THIRDPARTY_AUTH_ONLY,
                                    True):
            response = self.client.get(reverse('admin:login'))
            assert response.url == '/login?next=/admin'
            assert response.status_code == 302

        with override_waffle_flag(ADMIN_AUTH_REDIRECT_TO_LMS, True):
            response = self.client.get(reverse('admin:login'))
            assert response.url == '/login?next=/admin'
            assert response.status_code == 302

        with override_waffle_switch(ENABLE_LOGIN_USING_THIRDPARTY_AUTH_ONLY,
                                    False):
            response = self.client.get(reverse('admin:login'))
            assert response.template_name == ['admin/login.html']

        with override_waffle_flag(ADMIN_AUTH_REDIRECT_TO_LMS, False):
            response = self.client.get(reverse('admin:login'))
            assert response.template_name == ['admin/login.html']
예제 #21
0
    def test_offer(self):
        CourseEnrollment.enroll(self.user, self.course.id)

        response = self.client.get(self.url)
        self.assertIsNone(response.data['offer'])

        with override_waffle_flag(DISCOUNT_APPLICABILITY_FLAG, active=True):
            response = self.client.get(self.url)

            # Just a quick spot check that the dictionary looks like what we expect
            self.assertEqual(response.data['offer']['code'], 'EDXWELCOME')
예제 #22
0
    def test_discussion_tab_for_global_user(
            self, is_pages_and_resources_enabled,
            is_legacy_discussion_setting_enabled, is_discussion_tab_available):
        """
        Verify that the Discussion tab is available for course for global user.
        """
        discussion_section = (
            '<li class="nav-item"><button type="button" class="btn-link discussions_management" '
            'data-section="discussions_management">Discussions</button></li>')

        with override_waffle_flag(ENABLE_PAGES_AND_RESOURCES_MICROFRONTEND,
                                  is_pages_and_resources_enabled):
            with override_waffle_flag(OVERRIDE_DISCUSSION_LEGACY_SETTINGS_FLAG,
                                      is_legacy_discussion_setting_enabled):
                user = UserFactory.create(is_staff=True)
                set_course_cohorted(self.course.id, True)
                self.client.login(username=user.username, password='******')
                response = self.client.get(self.url).content.decode('utf-8')
                self.assertEqual(discussion_section in response,
                                 is_discussion_tab_available)
예제 #23
0
 def test_is_learning_sequences_api_enabled(self, enable_new_api):
     """
     Test that the Courseware API exposes the Learning Sequences API flag.
     """
     with override_waffle_flag(COURSEWARE_USE_LEARNING_SEQUENCES_API,
                               active=enable_new_api):
         response = self.client.get(self.url)
         assert response.status_code == 200
         courseware_data = response.json()
         assert courseware_data[
             'is_learning_sequences_api_enabled'] is enable_new_api
예제 #24
0
 def test_no_gradebook_learner_count_message(self):
     """
     Test that, when the writable gradebook featue IS enabled, there is NOT
     a message that the feature is only available for courses with small
     numbers of learners.
     """
     waffle_flag = waffle_flags()[WRITABLE_GRADEBOOK]
     with override_waffle_flag(waffle_flag, active=True):
         response = self.client.get(self.url)
     assert TestInstructorDashboard.GRADEBOOK_LEARNER_COUNT_MESSAGE not in response.content.decode('utf-8')
     self.assertContains(response, 'View Gradebook')
예제 #25
0
    def test_staff_can_see_writable_gradebook_as_subdirectory(self):
        """
        Test that, when the writable gradebook feature is enabled and
        deployed in a subdirectory, a staff member can see it.
        """
        waffle_flag = waffle_flags()[WRITABLE_GRADEBOOK]
        with override_waffle_flag(waffle_flag, active=True):
            response = self.client.get(self.url)

        expected_gradebook_url = f'{settings.WRITABLE_GRADEBOOK_URL}/{self.course.id}'
        self.assertContains(response, expected_gradebook_url)
        self.assertContains(response, 'View Gradebook')
예제 #26
0
    def test_staff_can_see_writable_gradebook(self):
        """
        Test that, when the writable gradebook feature is enabled and
        deployed in another domain, a staff member can see it.
        """
        waffle_flag = waffle_flags()[WRITABLE_GRADEBOOK]
        with override_waffle_flag(waffle_flag, active=True):
            response = self.client.get(self.url)

        expected_gradebook_url = f'http://gradebook.local.edx.org/{self.course.id}'
        self.assertContains(response, expected_gradebook_url)
        self.assertContains(response, 'View Gradebook')
 def test_about_page_public_view(self, course_visibility):
     """
     Assert that anonymous or unenrolled users see View Course option
     when unenrolled access flag is set
     """
     with mock.patch('xmodule.course_module.CourseBlock.course_visibility', course_visibility):
         with override_waffle_flag(COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, active=True):
             url = reverse('about_course', args=[str(self.course.id)])
             resp = self.client.get(url)
     if course_visibility == COURSE_VISIBILITY_PUBLIC or course_visibility == COURSE_VISIBILITY_PUBLIC_OUTLINE:  # lint-amnesty, pylint: disable=consider-using-in
         self.assertContains(resp, "View Course")
     else:
         self.assertContains(resp, "Enroll Now")
예제 #28
0
    def test_timed_exam_gating_waffle_flag(self, mocked_user):
        """
        Verify the code inside the waffle flag is not executed with the flag off
        Verify the code inside the waffle flag is executed with the flag on
        """
        # the order of the overrides is important since the `assert_not_called` does
        # not appear to be limited to just the override_waffle_flag = False scope
        with override_waffle_flag(TIMED_EXAM_GATING_WAFFLE_FLAG, active=False):
            self._get_rendered_view(
                self.sequence_5_1,
                extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'),
                view=STUDENT_VIEW
            )
            mocked_user.assert_not_called()

        with override_waffle_flag(TIMED_EXAM_GATING_WAFFLE_FLAG, active=True):
            self._get_rendered_view(
                self.sequence_5_1,
                extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'),
                view=STUDENT_VIEW
            )
            mocked_user.assert_called_once()
예제 #29
0
 def test_user_can_access_course_live_tab(self, course_live_config_enabled):
     """
     Test if tab is accessible to users with different roles
     """
     self.course_live_config.enabled = course_live_config_enabled
     self.course_live_config.save()
     tab = self.check_course_live_tab()
     with override_waffle_flag(ENABLE_COURSE_LIVE, True):
         self.check_can_display_results(
             tab,
             for_staff_only=True,
             for_enrolled_users_only=True,
             expected_value=course_live_config_enabled,
         )
예제 #30
0
    def test_logistration_mfe_redirects(self, url_name, path):
        """
        Test that if Logistration MFE is enabled, then we redirect to
        the correct URL.
        """

        # Test with setting enabled and waffle flag in-active, does not redirect
        response = self.client.get(reverse(url_name))
        self.assertContains(response, 'Sign in or Register')

        # Test with setting enabled and waffle flag active, redirects to microfrontend
        with override_waffle_flag(REDIRECT_TO_AUTHN_MICROFRONTEND, active=True):
            response = self.client.get(reverse(url_name))
            self.assertRedirects(response, settings.AUTHN_MICROFRONTEND_URL + path, fetch_redirect_response=False)