Exemple #1
0
 def _create_and_purchase_verified(self, student, course_id):
     """
     Creates a verified mode for the course and purchases it for the student.
     """
     course_mode = CourseMode(course_id=course_id,
                              mode_slug='verified',
                              mode_display_name='verified cert',
                              min_price=50)
     course_mode.save()
Exemple #2
0
 def _create_and_purchase_verified(self, student, course_id):  # lint-amnesty, pylint: disable=unused-argument
     """
     Creates a verified mode for the course and purchases it for the student.
     """
     course_mode = CourseMode(course_id=course_id,
                              mode_slug='verified',
                              mode_display_name='verified cert',
                              min_price=50)
     course_mode.save()
Exemple #3
0
class TestInstructorDashboardPerformance(ModuleStoreTestCase,
                                         LoginEnrollmentTestCase,
                                         XssTestMixin):
    """
    Tests for the instructor dashboard from the performance point of view.
    """
    MODULESTORE = TEST_DATA_SPLIT_MODULESTORE

    def setUp(self):
        """
        Set up tests
        """
        super().setUp()
        self.course = CourseFactory.create(
            grading_policy={
                "GRADE_CUTOFFS": {
                    "A": 0.75,
                    "B": 0.63,
                    "C": 0.57,
                    "D": 0.5
                }
            },
            display_name='<script>alert("XSS")</script>',
            default_store=ModuleStoreEnum.Type.split)

        self.course_mode = CourseMode(
            course_id=self.course.id,
            mode_slug=CourseMode.DEFAULT_MODE_SLUG,
            mode_display_name=CourseMode.DEFAULT_MODE.name,
            min_price=40)
        self.course_mode.save()
        # Create instructor account
        self.instructor = AdminFactory.create()
        self.client.login(username=self.instructor.username, password="******")

    def test_spoc_gradebook_mongo_calls(self):
        """
        Test that the MongoDB cache is used in API to return grades
        """
        # prepare course structure
        course = ItemFactory.create(
            parent_location=self.course.location,
            category="course",
            display_name="Test course",
        )

        students = []
        for i in range(20):
            username = "******" % i
            student = UserFactory.create(username=username)
            CourseEnrollmentFactory.create(user=student,
                                           course_id=self.course.id)
            students.append(student)

        chapter = ItemFactory.create(
            parent=course,
            category='chapter',
            display_name="Chapter",
            publish_item=True,
            start=datetime.datetime(2015, 3, 1, tzinfo=UTC),
        )
        sequential = ItemFactory.create(
            parent=chapter,
            category='sequential',
            display_name="Lesson",
            publish_item=True,
            start=datetime.datetime(2015, 3, 1, tzinfo=UTC),
            metadata={
                'graded': True,
                'format': 'Homework'
            },
        )
        vertical = ItemFactory.create(
            parent=sequential,
            category='vertical',
            display_name='Subsection',
            publish_item=True,
            start=datetime.datetime(2015, 4, 1, tzinfo=UTC),
        )
        for i in range(10):
            problem = ItemFactory.create(
                category="problem",
                parent=vertical,
                display_name="A Problem Block %d" % i,
                weight=1,
                publish_item=False,
                metadata={'rerandomize': 'always'},
            )
            for j in students:
                grade = i % 2
                StudentModuleFactory.create(grade=grade,
                                            max_grade=1,
                                            student=j,
                                            course_id=self.course.id,
                                            module_state_key=problem.location)

        # check MongoDB calls count
        url = reverse('spoc_gradebook', kwargs={'course_id': self.course.id})
        with check_mongo_calls(7):
            response = self.client.get(url)
            assert response.status_code == 200
Exemple #4
0
class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase,
                              XssTestMixin):
    """
    Tests for the instructor dashboard (not legacy).
    """
    def setUp(self):
        """
        Set up tests
        """
        super().setUp()
        self.course = CourseFactory.create(
            grading_policy={
                "GRADE_CUTOFFS": {
                    "A": 0.75,
                    "B": 0.63,
                    "C": 0.57,
                    "D": 0.5
                }
            },
            display_name='<script>alert("XSS")</script>')

        self.course_mode = CourseMode(
            course_id=self.course.id,
            mode_slug=CourseMode.DEFAULT_MODE_SLUG,
            mode_display_name=CourseMode.DEFAULT_MODE.name,
            min_price=40)
        self.course_info = CourseFactory.create(
            org="ACME",
            number="001",
            run="2017",
            name="How to defeat the Road Runner")
        self.course_mode.save()
        # Create instructor account
        self.instructor = AdminFactory.create()
        self.client.login(username=self.instructor.username, password="******")

        # URL for instructor dash
        self.url = reverse('instructor_dashboard',
                           kwargs={'course_id': str(self.course.id)})

    def get_dashboard_enrollment_message(self):
        """
        Returns expected dashboard enrollment message with link to Insights.
        """
        return 'Enrollment data is now available in <a href="http://example.com/courses/{}" ' \
               'rel="noopener" target="_blank">Example</a>.'.format(str(self.course.id))

    def get_dashboard_analytics_message(self):
        """
        Returns expected dashboard demographic message with link to Insights.
        """
        return 'For analytics about your course, go to <a href="http://example.com/courses/{}" ' \
               'rel="noopener" target="_blank">Example</a>.'.format(str(self.course.id))

    def test_instructor_tab(self):
        """
        Verify that the instructor tab appears for staff only.
        """
        def has_instructor_tab(user, course):
            """Returns true if the "Instructor" tab is shown."""
            tabs = get_course_tab_list(user, course)
            return len([tab for tab in tabs if tab.name == 'Instructor']) == 1

        assert has_instructor_tab(self.instructor, self.course)

        staff = StaffFactory(course_key=self.course.id)
        assert has_instructor_tab(staff, self.course)

        student = UserFactory.create()
        assert not has_instructor_tab(student, self.course)

        researcher = UserFactory.create()
        CourseAccessRoleFactory(course_id=self.course.id,
                                user=researcher,
                                role='data_researcher',
                                org=self.course.id.org)
        assert has_instructor_tab(researcher, self.course)

        org_researcher = UserFactory.create()
        CourseAccessRoleFactory(course_id=None,
                                user=org_researcher,
                                role='data_researcher',
                                org=self.course.id.org)
        assert has_instructor_tab(org_researcher, self.course)

    @ddt.data(
        ('staff', False, False, True), ('staff', True, False, False),
        ('staff', True, True, True), ('staff', False, True, True),
        ('instructor', False, False, True), ('instructor', True, False, False),
        ('instructor', True, True, True), ('instructor', False, True, True))
    @ddt.unpack
    def test_discussion_tab_for_course_staff_role(
            self, access_role, is_pages_and_resources_enabled,
            is_legacy_discussion_setting_enabled, is_discussion_tab_available):
        """
        Verify that the Discussion tab is available for course for course staff role.
        """
        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()
                CourseAccessRoleFactory(course_id=self.course.id,
                                        user=user,
                                        role=access_role,
                                        org=self.course.id.org)
                set_course_cohorted(self.course.id, True)
                self.client.login(username=self.user.username, password='******')
                response = self.client.get(self.url).content.decode('utf-8')
                self.assertEqual(discussion_section in response,
                                 is_discussion_tab_available)

    @ddt.data(
        (False, False, True),
        (True, False, False),
        (True, True, True),
        (False, True, True),
    )
    @ddt.unpack
    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)

    @ddt.data(
        ('staff', False, False),
        ('instructor', False, False),
        ('data_researcher', True, False),
        ('global_staff', True, False),
        ('staff', False, True),
        ('instructor', False, True),
        ('data_researcher', True, True),
        ('global_staff', True, True),
    )
    @ddt.unpack
    def test_data_download(self, access_role, can_access, waffle_status):
        """
        Verify that the Data Download tab only shows up for certain roles
        """
        with override_waffle_flag(DATA_DOWNLOAD_V2, waffle_status):
            download_section = '<li class="nav-item"><button type="button" class="btn-link data_download" ' \
                               'data-section="data_download">Data Download</button></li>'
            if waffle_status:
                download_section = '<li class="nav-item"><button type="button" class="btn-link data_download_2" ' \
                                   'data-section="data_download_2">Data Download</button></li>'
            user = UserFactory.create(is_staff=access_role == 'global_staff')
            CourseAccessRoleFactory(course_id=self.course.id,
                                    user=user,
                                    role=access_role,
                                    org=self.course.id.org)
            self.client.login(username=user.username, password="******")
            response = self.client.get(self.url)
            if can_access:
                self.assertContains(response, download_section)
            else:
                self.assertNotContains(response, download_section)

    @override_settings(ANALYTICS_DASHBOARD_URL='http://example.com')
    @override_settings(ANALYTICS_DASHBOARD_NAME='Example')
    def test_data_download_only(self):
        """
        Verify that only the data download tab is visible for data researchers.
        """
        user = UserFactory.create()
        CourseAccessRoleFactory(course_id=self.course.id,
                                user=user,
                                role='data_researcher',
                                org=self.course.id.org)
        self.client.login(username=user.username, password="******")
        response = self.client.get(self.url)
        matches = re.findall(
            rb'<li class="nav-item"><button type="button" class="btn-link .*" data-section=".*">.*',
            response.content)
        assert len(matches) == 1

    @ddt.data(
        ("How to defeat the Road Runner", "2017", "001", "ACME"), )
    @ddt.unpack
    def test_instructor_course_info(self, display_name, run, number, org):
        """
        Verify that it shows the correct course information
        """
        url = reverse('instructor_dashboard',
                      kwargs={'course_id': str(self.course_info.id)})

        response = self.client.get(url)
        content = pq(response.content)

        assert display_name == content(
            '#field-course-display-name b').contents()[0].strip()

        assert run == content('#field-course-name b').contents()[0].strip()

        assert number == content(
            '#field-course-number b').contents()[0].strip()

        assert org == content(
            '#field-course-organization b').contents()[0].strip()

    @ddt.data(True, False)
    def test_membership_reason_field_visibility(self, enbale_reason_field):
        """
        Verify that reason field is enabled by site configuration flag 'ENABLE_MANUAL_ENROLLMENT_REASON_FIELD'
        """

        configuration_values = {
            "ENABLE_MANUAL_ENROLLMENT_REASON_FIELD": enbale_reason_field
        }
        site = Site.objects.first()
        SiteConfiguration.objects.create(site=site,
                                         site_values=configuration_values,
                                         enabled=True)

        url = reverse('instructor_dashboard',
                      kwargs={'course_id': str(self.course_info.id)})
        response = self.client.get(url)
        reason_field = '<textarea rows="2" id="reason-field-id" name="reason-field" ' \
                       'placeholder="Reason" spellcheck="false"></textarea>'
        if enbale_reason_field:
            self.assertContains(response, reason_field)
        else:
            self.assertNotContains(response, reason_field)

    def test_student_admin_staff_instructor(self):
        """
        Verify that staff users are not able to see course-wide options, while still
        seeing individual learner options.
        """
        # Original (instructor) user can see both specific grades, and course-wide grade adjustment tools
        response = self.client.get(self.url)
        self.assertContains(
            response, '<h4 class="hd hd-4">Adjust all enrolled learners')
        self.assertContains(
            response,
            '<h4 class="hd hd-4">View a specific learner&#39;s grades and progress'
        )

        # But staff user can only see specific grades
        staff = StaffFactory(course_key=self.course.id)
        self.client.login(username=staff.username, password="******")
        response = self.client.get(self.url)
        self.assertNotContains(
            response, '<h4 class="hd hd-4">Adjust all enrolled learners')
        self.assertContains(
            response,
            '<h4 class="hd hd-4">View a specific learner&#39;s grades and progress'
        )

    @patch(
        'lms.djangoapps.instructor.views.instructor_dashboard.settings.WRITABLE_GRADEBOOK_URL',
        'http://gradebook.local.edx.org')
    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')

    GRADEBOOK_LEARNER_COUNT_MESSAGE = (
        'Note: This feature is available only to courses with a small number '
        + 'of enrolled learners.')

    @patch(
        'lms.djangoapps.instructor.views.instructor_dashboard.settings.WRITABLE_GRADEBOOK_URL',
        settings.LMS_ROOT_URL + '/gradebook')
    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')

    GRADEBOOK_LEARNER_COUNT_MESSAGE = (
        'Note: This feature is available only to courses with a small number '
        + 'of enrolled learners.')

    def test_gradebook_learner_count_message(self):
        """
        Test that, when the writable gradebook featue is NOT enabled, there IS
        a message that the feature is only available for courses with small
        numbers of learners.
        """
        response = self.client.get(self.url)
        self.assertContains(
            response,
            self.GRADEBOOK_LEARNER_COUNT_MESSAGE,
        )
        self.assertContains(response, 'View Gradebook')

    @patch(
        'lms.djangoapps.instructor.views.instructor_dashboard.settings.WRITABLE_GRADEBOOK_URL',
        'http://gradebook.local.edx.org')
    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')

    def test_course_name_xss(self):
        """Test that the instructor dashboard correctly escapes course names
        with script tags.
        """
        response = self.client.get(self.url)
        self.assert_no_xss(response, '<script>alert("XSS")</script>')

    @patch.dict(settings.FEATURES, {'DISPLAY_ANALYTICS_ENROLLMENTS': False})
    @override_settings(ANALYTICS_DASHBOARD_URL='')
    def test_no_enrollments(self):
        """
        Test enrollment section is hidden.
        """
        response = self.client.get(self.url)
        # no enrollment information should be visible
        self.assertNotContains(
            response, '<h3 class="hd hd-3">Enrollment Information</h3>')

    @patch.dict(settings.FEATURES, {'DISPLAY_ANALYTICS_ENROLLMENTS': True})
    @override_settings(ANALYTICS_DASHBOARD_URL='')
    def test_show_enrollments_data(self):
        """
        Test enrollment data is shown.
        """
        response = self.client.get(self.url)

        # enrollment information visible
        self.assertContains(response,
                            '<h4 class="hd hd-4">Enrollment Information</h4>')
        self.assertContains(response, '<th scope="row">Verified</th>')
        self.assertContains(response, '<th scope="row">Audit</th>')
        self.assertContains(response, '<th scope="row">Honor</th>')
        self.assertContains(response, '<th scope="row">Professional</th>')

        # dashboard link hidden
        self.assertNotContains(response,
                               self.get_dashboard_enrollment_message())

    @patch.dict(settings.FEATURES, {'DISPLAY_ANALYTICS_ENROLLMENTS': True})
    @override_settings(ANALYTICS_DASHBOARD_URL='')
    def test_show_enrollment_data_for_prof_ed(self):
        # Create both "professional" (meaning professional + verification)
        # and "no-id-professional" (meaning professional without verification)
        # These should be aggregated for display purposes.
        users = [UserFactory() for _ in range(2)]
        CourseEnrollment.enroll(users[0], self.course.id, mode="professional")
        CourseEnrollment.enroll(users[1],
                                self.course.id,
                                mode="no-id-professional")
        response = self.client.get(self.url)

        # Check that the number of professional enrollments is two
        self.assertContains(response,
                            '<th scope="row">Professional</th><td>2</td>')

    @patch.dict(settings.FEATURES, {'DISPLAY_ANALYTICS_ENROLLMENTS': False})
    @override_settings(ANALYTICS_DASHBOARD_URL='http://example.com')
    @override_settings(ANALYTICS_DASHBOARD_NAME='Example')
    def test_show_dashboard_enrollment_message(self):
        """
        Test enrollment dashboard message is shown and data is hidden.
        """
        response = self.client.get(self.url)

        # enrollment information hidden
        self.assertNotContains(response, '<th scope="row">Verified</th>')
        self.assertNotContains(response, '<th scope="row">Audit</th>')
        self.assertNotContains(response, '<th scope="row">Honor</th>')
        self.assertNotContains(response, '<th scope="row">Professional</th>')

        # link to dashboard shown
        expected_message = self.get_dashboard_enrollment_message()
        assert expected_message in response.content.decode(response.charset)

    @override_settings(ANALYTICS_DASHBOARD_URL='')
    @override_settings(ANALYTICS_DASHBOARD_NAME='')
    def test_dashboard_analytics_tab_not_shown(self):
        """
        Test dashboard analytics tab isn't shown if insights isn't configured.
        """
        response = self.client.get(self.url)
        analytics_section = '<li class="nav-item"><a href="" data-section="instructor_analytics">Analytics</a></li>'
        self.assertNotContains(response, analytics_section)

    @override_settings(ANALYTICS_DASHBOARD_URL='http://example.com')
    @override_settings(ANALYTICS_DASHBOARD_NAME='Example')
    def test_dashboard_analytics_points_at_insights(self):
        """
        Test analytics dashboard message is shown
        """
        response = self.client.get(self.url)
        analytics_section = '<li class="nav-item"><button type="button" class="btn-link instructor_analytics"' \
                            ' data-section="instructor_analytics">Analytics</button></li>'
        self.assertContains(response, analytics_section)

        # link to dashboard shown
        expected_message = self.get_dashboard_analytics_message()
        assert expected_message in response.content.decode(response.charset)

    @ddt.data(
        (True, True, True),
        (True, False, False),
        (False, True, False),
        (False, False, False),
    )
    @ddt.unpack
    def test_ccx_coaches_option_on_admin_list_management_instructor(
            self, ccx_feature_flag, enable_ccx, expected_result):
        """
        Test whether the "CCX Coaches" option is visible or hidden depending on the value of course.enable_ccx.
        """
        with patch.dict(settings.FEATURES,
                        {'CUSTOM_COURSES_EDX': ccx_feature_flag}):
            self.course.enable_ccx = enable_ccx
            self.store.update_item(self.course, self.instructor.id)

            response = self.client.get(self.url)

            assert expected_result == (
                'CCX Coaches are able to create their own Custom Courses based on this course'
                in response.content.decode('utf-8'))

    def test_grade_cutoffs(self):
        """
        Verify that grade cutoffs are displayed in the correct order.
        """
        response = self.client.get(self.url)
        self.assertContains(response, 'D: 0.5, C: 0.57, B: 0.63, A: 0.75')

    @patch(
        'lms.djangoapps.instructor.views.gradebook_api.MAX_STUDENTS_PER_PAGE_GRADE_BOOK',
        2)
    def test_calculate_page_info(self):
        page = calculate_page_info(offset=0, total_students=2)
        assert page['offset'] == 0
        assert page['page_num'] == 1
        assert page['next_offset'] is None
        assert page['previous_offset'] is None
        assert page['total_pages'] == 1

    @patch('lms.djangoapps.instructor.views.gradebook_api.render_to_response',
           intercept_renderer)
    @patch(
        'lms.djangoapps.instructor.views.gradebook_api.MAX_STUDENTS_PER_PAGE_GRADE_BOOK',
        1)
    def test_spoc_gradebook_pages(self):
        for i in range(2):
            username = "******" % i
            student = UserFactory.create(username=username)
            CourseEnrollmentFactory.create(user=student,
                                           course_id=self.course.id)
        url = reverse('spoc_gradebook', kwargs={'course_id': self.course.id})
        response = self.client.get(url)
        assert response.status_code == 200
        # Max number of student per page is one.  Patched setting MAX_STUDENTS_PER_PAGE_GRADE_BOOK = 1
        assert len(response.mako_context['students']) == 1

    def test_open_response_assessment_page(self):
        """
        Test that Open Responses is available only if course contains at least one ORA block
        """
        ora_section = (
            '<li class="nav-item">'
            '<button type="button" class="btn-link open_response_assessment" data-section="open_response_assessment">'
            'Open Responses'
            '</button>'
            '</li>')

        response = self.client.get(self.url)
        self.assertNotContains(response, ora_section)

        ItemFactory.create(parent_location=self.course.location,
                           category="openassessment")
        response = self.client.get(self.url)
        self.assertContains(response, ora_section)

    def test_open_response_assessment_page_orphan(self):
        """
        Tests that the open responses tab loads if the course contains an
        orphaned openassessment block
        """
        # create non-orphaned openassessment block
        ItemFactory.create(
            parent_location=self.course.location,
            category="openassessment",
        )
        # create orphan
        self.store.create_item(self.user.id, self.course.id, 'openassessment',
                               "orphan")
        response = self.client.get(self.url)
        # assert we don't get a 500 error
        assert 200 == response.status_code

    @patch(
        "lms.djangoapps.instructor.views.instructor_dashboard.get_plugins_view_context"
    )
    def test_external_plugin_integration(self, mock_get_plugins_view_context):
        """
        Tests that whether context from plugins is being reflected/added in instructor dashboard.
        """
        test_studio_url = get_studio_url(self.course, 'course')

        context = {'studio_url': test_studio_url}
        mock_get_plugins_view_context.return_value = context

        response = self.client.get(self.url)
        self.assertContains(response, test_studio_url)
Exemple #5
0
class AboutTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase,
                    EventTrackingTestCase, MilestonesTestCaseMixin):
    """
    Tests about xblock.
    """
    @classmethod
    def setUpClass(cls):
        super(AboutTestCase, cls).setUpClass()
        cls.course = CourseFactory.create()
        cls.course_without_about = CourseFactory.create(
            catalog_visibility=CATALOG_VISIBILITY_NONE)
        cls.course_with_about = CourseFactory.create(
            catalog_visibility=CATALOG_VISIBILITY_ABOUT)
        cls.purchase_course = CourseFactory.create(
            org='MITx', number='buyme', display_name='Course To Buy')
        cls.about = ItemFactory.create(category="about",
                                       parent_location=cls.course.location,
                                       data="OOGIE BLOOGIE",
                                       display_name="overview")
        cls.about = ItemFactory.create(
            category="about",
            parent_location=cls.course_without_about.location,
            data="WITHOUT ABOUT",
            display_name="overview")
        cls.about = ItemFactory.create(
            category="about",
            parent_location=cls.course_with_about.location,
            data="WITH ABOUT",
            display_name="overview")

    def setUp(self):
        super(AboutTestCase, self).setUp()  # lint-amnesty, pylint: disable=super-with-arguments

        self.course_mode = CourseMode(
            course_id=self.purchase_course.id,
            mode_slug=CourseMode.DEFAULT_MODE_SLUG,
            mode_display_name=CourseMode.DEFAULT_MODE_SLUG,
            min_price=10)
        self.course_mode.save()

    def test_anonymous_user(self):
        """
        This test asserts that a non-logged in user can visit the course about page
        """
        url = reverse('about_course', args=[text_type(self.course.id)])
        resp = self.client.get(url)
        self.assertContains(resp, "OOGIE BLOOGIE")

        # Check that registration button is present
        self.assertContains(resp, REG_STR)

    def test_logged_in(self):
        """
        This test asserts that a logged-in user can visit the course about page
        """
        self.setup_user()
        url = reverse('about_course', args=[text_type(self.course.id)])
        resp = self.client.get(url)
        self.assertContains(resp, "OOGIE BLOOGIE")

    def test_already_enrolled(self):
        """
        Asserts that the end user sees the appropriate messaging
        when he/she visits the course about page, but is already enrolled
        """
        self.setup_user()
        self.enroll(self.course, True)
        url = reverse('about_course', args=[text_type(self.course.id)])
        resp = self.client.get(url)
        self.assertContains(resp, "You are enrolled in this course")
        self.assertContains(resp, "View Course")

    @override_settings(COURSE_ABOUT_VISIBILITY_PERMISSION="see_about_page")
    def test_visible_about_page_settings(self):
        """
        Verify that the About Page honors the permission settings in the course module
        """
        url = reverse('about_course',
                      args=[text_type(self.course_with_about.id)])
        resp = self.client.get(url)
        self.assertContains(resp, "WITH ABOUT")

        url = reverse('about_course',
                      args=[text_type(self.course_without_about.id)])
        resp = self.client.get(url)
        assert resp.status_code == 404

    @patch.dict(settings.FEATURES, {'ENABLE_MKTG_SITE': True})
    def test_logged_in_marketing(self):
        self.setup_user()
        url = reverse('about_course', args=[text_type(self.course.id)])
        resp = self.client.get(url)
        # should be redirected
        assert resp.status_code == 302
        # follow this time, and check we're redirected to the course home page
        resp = self.client.get(url, follow=True)
        target_url = resp.redirect_chain[-1][0]
        course_home_url = reverse('openedx.course_experience.course_home',
                                  args=[text_type(self.course.id)])
        assert target_url.endswith(course_home_url)

    @patch.dict(settings.FEATURES, {'ENABLE_COURSE_HOME_REDIRECT': False})
    @patch.dict(settings.FEATURES, {'ENABLE_MKTG_SITE': True})
    def test_logged_in_marketing_without_course_home_redirect(self):
        """
        Verify user is not redirected to course home page when
        ENABLE_COURSE_HOME_REDIRECT is set to False
        """
        self.setup_user()
        url = reverse('about_course', args=[text_type(self.course.id)])
        resp = self.client.get(url)
        # should not be redirected
        self.assertContains(resp, "OOGIE BLOOGIE")

    @patch.dict(settings.FEATURES, {'ENABLE_COURSE_HOME_REDIRECT': True})
    @patch.dict(settings.FEATURES, {'ENABLE_MKTG_SITE': False})
    def test_logged_in_marketing_without_mktg_site(self):
        """
        Verify user is not redirected to course home page when
        ENABLE_MKTG_SITE is set to False
        """
        self.setup_user()
        url = reverse('about_course', args=[text_type(self.course.id)])
        resp = self.client.get(url)
        # should not be redirected
        self.assertContains(resp, "OOGIE BLOOGIE")

    @patch.dict(settings.FEATURES, {'ENABLE_PREREQUISITE_COURSES': True})
    def test_pre_requisite_course(self):
        pre_requisite_course = CourseFactory.create(
            org='edX', course='900', display_name='pre requisite course')
        course = CourseFactory.create(
            pre_requisite_courses=[text_type(pre_requisite_course.id)])
        self.setup_user()
        url = reverse('about_course', args=[text_type(course.id)])
        resp = self.client.get(url)
        assert resp.status_code == 200
        pre_requisite_courses = get_prerequisite_courses_display(course)
        pre_requisite_course_about_url = reverse(
            'about_course', args=[text_type(pre_requisite_courses[0]['key'])])
        assert u'<span class="important-dates-item-text pre-requisite"><a href="{}">{}</a></span>'.format(pre_requisite_course_about_url, pre_requisite_courses[0]['display']) in resp.content.decode(resp.charset).strip('\n')  # pylint: disable=line-too-long

    @patch.dict(settings.FEATURES, {'ENABLE_PREREQUISITE_COURSES': True})
    def test_about_page_unfulfilled_prereqs(self):
        pre_requisite_course = CourseFactory.create(
            org='edX',
            course='901',
            display_name='pre requisite course',
        )

        pre_requisite_courses = [text_type(pre_requisite_course.id)]

        # for this failure to occur, the enrollment window needs to be in the past
        course = CourseFactory.create(
            org='edX',
            course='1000',
            # closed enrollment
            enrollment_start=datetime.datetime(2013, 1, 1),
            enrollment_end=datetime.datetime(2014, 1, 1),
            start=datetime.datetime(2013, 1, 1),
            end=datetime.datetime(2030, 1, 1),
            pre_requisite_courses=pre_requisite_courses,
        )
        set_prerequisite_courses(course.id, pre_requisite_courses)

        self.setup_user()
        self.enroll(self.course, True)
        self.enroll(pre_requisite_course, True)

        url = reverse('about_course', args=[text_type(course.id)])
        resp = self.client.get(url)
        assert resp.status_code == 200
        pre_requisite_courses = get_prerequisite_courses_display(course)
        pre_requisite_course_about_url = reverse(
            'about_course', args=[text_type(pre_requisite_courses[0]['key'])])
        assert u'<span class="important-dates-item-text pre-requisite"><a href="{}">{}</a></span>'.format(pre_requisite_course_about_url, pre_requisite_courses[0]['display']) in resp.content.decode(resp.charset).strip('\n')  # pylint: disable=line-too-long

        url = reverse('about_course',
                      args=[six.text_type(pre_requisite_course.id)])
        resp = self.client.get(url)
        assert resp.status_code == 200

    @ddt.data(
        [COURSE_VISIBILITY_PRIVATE],
        [COURSE_VISIBILITY_PUBLIC_OUTLINE],
        [COURSE_VISIBILITY_PUBLIC],
    )
    @ddt.unpack
    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.CourseDescriptor.course_visibility',
                course_visibility):
            with override_waffle_flag(COURSE_ENABLE_UNENROLLED_ACCESS_FLAG,
                                      active=True):
                url = reverse('about_course', args=[text_type(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")