def test_course_access_endpoint_with_non_staff_user(self):
        user = UserFactory(is_staff=False)
        self.client.login(username=user.username, password=UserFactory._DEFAULT_PASSWORD)  # lint-amnesty, pylint: disable=protected-access

        response = self.client.get(self.url, data=self.request_data)
        assert response.status_code == 403
Example #2
0
 def setUp(self):
     super().setUp()
     self.catalog_integration = self.create_catalog_integration(cache_ttl=1)
     self.user = UserFactory(username=self.catalog_integration.service_username)
 def setUp(self):
     super(TestCreateDotApplication, self).setUp()
     self.user = UserFactory.create()
Example #4
0
 def setUp(self):
     super().setUp()
     self.user = UserFactory()
     self.client.login(username=self.user.username, password=TEST_PASSWORD)
Example #5
0
class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin,
                            CompletionWaffleTestMixin):
    """
    Tests for the student dashboard.
    """

    EMAIL_SETTINGS_ELEMENT_ID = "#actions-item-email-settings-0"
    ENABLED_SIGNALS = ['course_published']
    TOMORROW = now() + timedelta(days=1)
    THREE_YEARS_FROM_NOW = now() + timedelta(days=(365 * 3))
    THREE_YEARS_AGO = now() - timedelta(days=(365 * 3))
    MOCK_SETTINGS = {
        'FEATURES': {
            'DISABLE_START_DATES': False,
            'ENABLE_MKTG_SITE': True,
            'DISABLE_SET_JWT_COOKIES_FOR_TESTS': True,
        },
        'SOCIAL_SHARING_SETTINGS': {
            'CUSTOM_COURSE_URLS': True,
            'DASHBOARD_FACEBOOK': True,
            'DASHBOARD_TWITTER': True,
        },
    }
    MOCK_SETTINGS_HIDE_COURSES = {
        'FEATURES': {
            'HIDE_DASHBOARD_COURSES_UNTIL_ACTIVATED': True,
            'DISABLE_SET_JWT_COOKIES_FOR_TESTS': True,
        }
    }

    def setUp(self):
        """
        Create a course and user, then log in.
        """
        super(StudentDashboardTests, self).setUp()  # lint-amnesty, pylint: disable=super-with-arguments
        self.user = UserFactory()
        self.client.login(username=self.user.username, password=PASSWORD)
        self.path = reverse('dashboard')

    def set_course_sharing_urls(self, set_marketing, set_social_sharing):
        """
        Set course sharing urls (i.e. social_sharing_url, marketing_url)
        """
        course_overview = self.course_enrollment.course_overview
        if set_marketing:
            course_overview.marketing_url = 'http://www.testurl.com/marketing/url/'

        if set_social_sharing:
            course_overview.social_sharing_url = 'http://www.testurl.com/social/url/'

        course_overview.save()

    def test_redirect_account_settings(self):
        """
        Verify if user does not have profile he/she is redirected to account_settings.
        """
        UserProfile.objects.get(user=self.user).delete()
        response = self.client.get(self.path)
        self.assertRedirects(response, reverse('account_settings'))

    def test_grade_appears_before_course_end_date(self):
        """
        Verify that learners are not able to see their final grade before the end
        of course in the learner dashboard
        """
        self.course_key = CourseKey.from_string(
            'course-v1:edX+DemoX+Demo_Course')  # lint-amnesty, pylint: disable=attribute-defined-outside-init
        self.course = CourseOverviewFactory.create(
            id=self.course_key,
            end_date=self.TOMORROW,  # lint-amnesty, pylint: disable=attribute-defined-outside-init
            certificate_available_date=self.THREE_YEARS_AGO,
            lowest_passing_grade=0.3)
        self.course_enrollment = CourseEnrollmentFactory(
            course_id=self.course.id, user=self.user)  # lint-amnesty, pylint: disable=attribute-defined-outside-init
        GeneratedCertificateFactory(status='notpassing',
                                    course_id=self.course.id,
                                    user=self.user,
                                    grade=0.45)

        response = self.client.get(reverse('dashboard'))
        # The final grade does not appear before the course has ended
        self.assertContains(response, 'Your final grade:')
        self.assertContains(response, '<span class="grade-value">45%</span>')

    def test_grade_not_appears_before_cert_available_date(self):
        """
        Verify that learners are able to see their final grade of the course in
        the learner dashboard after the course had ended
        """
        self.course_key = CourseKey.from_string(
            'course-v1:edX+DemoX+Demo_Course')  # lint-amnesty, pylint: disable=attribute-defined-outside-init
        self.course = CourseOverviewFactory.create(
            id=self.course_key,
            end_date=self.THREE_YEARS_AGO,  # lint-amnesty, pylint: disable=attribute-defined-outside-init
            certificate_available_date=self.TOMORROW,
            lowest_passing_grade=0.3)
        self.course_enrollment = CourseEnrollmentFactory(
            course_id=self.course.id, user=self.user)  # lint-amnesty, pylint: disable=attribute-defined-outside-init
        GeneratedCertificateFactory(status='notpassing',
                                    course_id=self.course.id,
                                    user=self.user,
                                    grade=0.45)

        response = self.client.get(reverse('dashboard'))
        self.assertNotContains(response, 'Your final grade:')
        self.assertNotContains(response,
                               '<span class="grade-value">45%</span>')

    @patch.multiple('django.conf.settings', **MOCK_SETTINGS)
    @ddt.data(*itertools.product(
        [True, False],
        [True, False],
        [ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split],
    ))
    @ddt.unpack
    def test_sharing_icons_for_future_course(self, set_marketing,
                                             set_social_sharing,
                                             modulestore_type):
        """
        Verify that the course sharing icons show up if course is starting in future and
        any of marketing or social sharing urls are set.
        """
        self.course = CourseFactory.create(start=self.TOMORROW,
                                           emit_signals=True,
                                           default_store=modulestore_type)  # lint-amnesty, pylint: disable=attribute-defined-outside-init
        self.course_enrollment = CourseEnrollmentFactory(
            course_id=self.course.id, user=self.user)  # lint-amnesty, pylint: disable=attribute-defined-outside-init
        self.set_course_sharing_urls(set_marketing, set_social_sharing)

        # Assert course sharing icons
        response = self.client.get(reverse('dashboard'))
        assert ('Share on Twitter'
                in response.content.decode('utf-8')) == (set_marketing
                                                         or set_social_sharing)
        assert ('Share on Facebook'
                in response.content.decode('utf-8')) == (set_marketing
                                                         or set_social_sharing)

    @patch.dict("django.conf.settings.FEATURES",
                {'ENABLE_PREREQUISITE_COURSES': True})
    def test_pre_requisites_appear_on_dashboard(self):
        """
        When a course has a prerequisite, the dashboard should display the prerequisite.
        If we remove the prerequisite and access the dashboard again, the prerequisite
        should not appear.
        """
        self.pre_requisite_course = CourseFactory.create(
            org='edx', number='999', display_name='Pre requisite Course')  # lint-amnesty, pylint: disable=attribute-defined-outside-init
        self.course = CourseFactory.create(  # lint-amnesty, pylint: disable=attribute-defined-outside-init
            org='edx',
            number='998',
            display_name='Test Course',
            pre_requisite_courses=[
                six.text_type(self.pre_requisite_course.id)
            ])
        self.course_enrollment = CourseEnrollmentFactory(
            course_id=self.course.id, user=self.user)  # lint-amnesty, pylint: disable=attribute-defined-outside-init

        set_prerequisite_courses(self.course.id,
                                 [six.text_type(self.pre_requisite_course.id)])
        response = self.client.get(reverse('dashboard'))
        self.assertContains(response, '<div class="prerequisites">')

        remove_prerequisite_course(self.course.id,
                                   get_course_milestones(self.course.id)[0])
        response = self.client.get(reverse('dashboard'))
        self.assertNotContains(response, '<div class="prerequisites">')

    @patch('openedx.core.djangoapps.programs.utils.get_programs')
    @patch(
        'common.djangoapps.student.views.dashboard.get_visible_sessions_for_entitlement'
    )
    @patch(
        'common.djangoapps.student.views.dashboard.get_pseudo_session_for_entitlement'
    )
    @patch.object(CourseOverview, 'get_from_id')
    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=mock_course_overview.return_value,
            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.')

    @patch(
        'common.djangoapps.student.views.dashboard.get_visible_sessions_for_entitlement'
    )
    @patch.object(CourseOverview, 'get_from_id')
    def test_unfulfilled_expired_entitlement(self, mock_course_overview,
                                             mock_course_runs):
        """
        When a learner has an unfulfilled, expired entitlement, a card should NOT appear on the dashboard.
        This use case represents either an entitlement that the user waited too long to fulfill, or an entitlement
        for which they received a refund.
        """
        CourseEntitlementFactory(user=self.user,
                                 created=self.THREE_YEARS_AGO,
                                 expired_at=now())
        mock_course_overview.return_value = CourseOverviewFactory(
            start=self.TOMORROW)
        mock_course_runs.return_value = [{
            'key': 'course-v1:FAKE+FA1-MA1.X+3T2017',
            'enrollment_end': str(self.TOMORROW),
            'pacing_type': 'instructor_paced',
            'type': 'verified',
            'status': 'published'
        }]
        response = self.client.get(self.path)
        self.assertNotContains(response, '<li class="course-item">')

    @patch(
        'common.djangoapps.entitlements.rest_api.v1.views.get_course_runs_for_course'
    )
    @patch.object(CourseOverview, 'get_from_id')
    def test_sessions_for_entitlement_course_runs(self, mock_course_overview,
                                                  mock_course_runs):
        """
        When a learner has a fulfilled entitlement for a course run in the past, there should be no availableSession
        data passed to the JS view. When a learner has a fulfilled entitlement for a course run enrollment ending in the
        future, there should not be an empty availableSession variable. When a learner has a fulfilled entitlement
        for a course that doesn't have an enrollment ending, there should not be an empty availableSession variable.

        NOTE: We commented out the assertions to move this to the catalog utils test suite.
        """
        # noAvailableSessions = "availableSessions: '[]'"

        # Test an enrollment end in the past
        mocked_course_overview = CourseOverviewFactory.create(
            start=self.TOMORROW,
            end=self.THREE_YEARS_FROM_NOW,
            self_paced=True,
            enrollment_end=self.THREE_YEARS_AGO)
        mock_course_overview.return_value = mocked_course_overview
        course_enrollment = CourseEnrollmentFactory(
            user=self.user, course_id=six.text_type(mocked_course_overview.id))
        mock_course_runs.return_value = [{
            'key':
            str(mocked_course_overview.id),
            'enrollment_end':
            str(mocked_course_overview.enrollment_end),
            'pacing_type':
            'self_paced',
            'type':
            'verified',
            'status':
            'published'
        }]
        CourseEntitlementFactory(user=self.user,
                                 enrollment_course_run=course_enrollment)
        # response = self.client.get(self.path)
        # self.assertIn(noAvailableSessions, response.content)

        # Test an enrollment end in the future sets an availableSession
        mocked_course_overview.enrollment_end = self.TOMORROW
        mocked_course_overview.save()

        mock_course_overview.return_value = mocked_course_overview
        mock_course_runs.return_value = [{
            'key':
            str(mocked_course_overview.id),
            'enrollment_end':
            str(mocked_course_overview.enrollment_end),
            'pacing_type':
            'self_paced',
            'type':
            'verified',
            'status':
            'published'
        }]
        # response = self.client.get(self.path)
        # self.assertNotIn(noAvailableSessions, response.content)

        # Test an enrollment end that doesn't exist sets an availableSession
        mocked_course_overview.enrollment_end = None
        mocked_course_overview.save()

        mock_course_overview.return_value = mocked_course_overview
        mock_course_runs.return_value = [{
            'key': str(mocked_course_overview.id),
            'enrollment_end': None,
            'pacing_type': 'self_paced',
            'type': 'verified',
            'status': 'published'
        }]
        # response = self.client.get(self.path)
        # self.assertNotIn(noAvailableSessions, response.content)

    @patch('openedx.core.djangoapps.programs.utils.get_programs')
    @patch(
        'common.djangoapps.student.views.dashboard.get_visible_sessions_for_entitlement'
    )
    @patch.object(CourseOverview, 'get_from_id')
    def test_fulfilled_entitlement(self, mock_course_overview,
                                   mock_course_runs, mock_get_programs):
        """
        When a learner has a fulfilled entitlement, their course dashboard should have:
            - exactly one course item, meaning it:
                - has an entitlement card
                - does NOT have a course card referencing the selected session
            - an unhidden Change or Leave Session button
            - a related programs message
        """
        mocked_course_overview = CourseOverviewFactory(
            start=self.TOMORROW, self_paced=True, enrollment_end=self.TOMORROW)
        mock_course_overview.return_value = mocked_course_overview
        course_enrollment = CourseEnrollmentFactory(
            user=self.user, course_id=six.text_type(mocked_course_overview.id))
        mock_course_runs.return_value = [{
            'key':
            str(mocked_course_overview.id),
            'enrollment_end':
            str(mocked_course_overview.enrollment_end),
            'pacing_type':
            'self_paced',
            'type':
            'verified',
            'status':
            'published'
        }]
        entitlement = CourseEntitlementFactory(
            user=self.user, enrollment_course_run=course_enrollment)
        program = ProgramFactory()
        program['courses'][0]['course_runs'] = [{
            'key':
            six.text_type(mocked_course_overview.id)
        }]
        program['courses'][0]['uuid'] = entitlement.course_uuid
        mock_get_programs.return_value = [program]
        response = self.client.get(self.path)
        self.assertContains(response, '<li class="course-item">', count=1)
        self.assertContains(response,
                            '<button class="change-session btn-link "')
        self.assertContains(response, 'Related Programs:')

    @patch('openedx.core.djangoapps.programs.utils.get_programs')
    @patch(
        'common.djangoapps.student.views.dashboard.get_visible_sessions_for_entitlement'
    )
    @patch.object(CourseOverview, 'get_from_id')
    def test_fulfilled_expired_entitlement(self, mock_course_overview,
                                           mock_course_runs,
                                           mock_get_programs):
        """
        When a learner has a fulfilled entitlement that is expired, their course dashboard should have:
            - exactly one course item, meaning it:
                - has an entitlement card
            - Message that the learner can no longer change sessions
            - a related programs message
        """
        mocked_course_overview = CourseOverviewFactory(
            start=self.TOMORROW, self_paced=True, enrollment_end=self.TOMORROW)
        mock_course_overview.return_value = mocked_course_overview
        course_enrollment = CourseEnrollmentFactory(user=self.user, course_id=six.text_type(mocked_course_overview.id), created=self.THREE_YEARS_AGO)  # lint-amnesty, pylint: disable=line-too-long
        mock_course_runs.return_value = [{
            'key':
            str(mocked_course_overview.id),
            'enrollment_end':
            str(mocked_course_overview.enrollment_end),
            'pacing_type':
            'self_paced',
            'type':
            'verified',
            'status':
            'published'
        }]
        entitlement = CourseEntitlementFactory(user=self.user, enrollment_course_run=course_enrollment, created=self.THREE_YEARS_AGO)  # lint-amnesty, pylint: disable=line-too-long
        program = ProgramFactory()
        program['courses'][0]['course_runs'] = [{
            'key':
            six.text_type(mocked_course_overview.id)
        }]
        program['courses'][0]['uuid'] = entitlement.course_uuid
        mock_get_programs.return_value = [program]
        response = self.client.get(self.path)
        self.assertContains(response, '<li class="course-item">', count=1)
        self.assertContains(response, 'You can no longer change sessions.')
        self.assertContains(response, 'Related Programs:')

    @patch('openedx.core.djangoapps.catalog.utils.get_course_runs_for_course')
    @patch(
        'common.djangoapps.student.views.dashboard.is_bulk_email_feature_enabled'
    )
    def test_email_settings_fulfilled_entitlement(self, mock_email_feature,
                                                  mock_get_course_runs):
        """
        Assert that the Email Settings action is shown when the user has a fulfilled entitlement.
        """
        mock_email_feature.return_value = True
        course_overview = CourseOverviewFactory(start=self.TOMORROW,
                                                self_paced=True,
                                                enrollment_end=self.TOMORROW)
        course_enrollment = CourseEnrollmentFactory(
            user=self.user, course_id=course_overview.id)
        entitlement = CourseEntitlementFactory(
            user=self.user, enrollment_course_run=course_enrollment)
        course_runs = [{
            'key': six.text_type(course_overview.id),
            'uuid': entitlement.course_uuid
        }]
        mock_get_course_runs.return_value = course_runs

        response = self.client.get(self.path)
        assert pq(response.content)(self.EMAIL_SETTINGS_ELEMENT_ID).length == 1

    @patch.object(CourseOverview, 'get_from_id')
    @patch(
        'common.djangoapps.student.views.dashboard.is_bulk_email_feature_enabled'
    )
    def test_email_settings_unfulfilled_entitlement(self, mock_email_feature,
                                                    mock_course_overview):
        """
        Assert that the Email Settings action is not shown when the entitlement is not fulfilled.
        """
        mock_email_feature.return_value = True
        mock_course_overview.return_value = CourseOverviewFactory(
            start=self.TOMORROW)
        CourseEntitlementFactory(user=self.user)
        response = self.client.get(self.path)
        assert pq(response.content)(self.EMAIL_SETTINGS_ELEMENT_ID).length == 0

    @patch.multiple('django.conf.settings', **MOCK_SETTINGS_HIDE_COURSES)
    def test_hide_dashboard_courses_until_activated(self):
        """
        Verify that when the HIDE_DASHBOARD_COURSES_UNTIL_ACTIVATED feature is enabled,
        inactive users don't see the Courses list, but active users still do.
        """
        # Ensure active users see the course list
        assert self.user.is_active
        response = self.client.get(reverse('dashboard'))
        self.assertContains(response,
                            'You are not enrolled in any courses yet.')

        # Ensure inactive users don't see the course list
        self.user.is_active = False
        self.user.save()
        response = self.client.get(reverse('dashboard'))
        self.assertNotContains(response,
                               'You are not enrolled in any courses yet.')

    def test_show_empty_dashboard_message(self):
        """
        Verify that when the EMPTY_DASHBOARD_MESSAGE feature is set,
        its text is displayed in an empty courses list.
        """
        empty_dashboard_message = "Check out our lovely <i>free</i> courses!"
        response = self.client.get(reverse('dashboard'))
        self.assertContains(response,
                            'You are not enrolled in any courses yet.')
        self.assertNotContains(response, empty_dashboard_message)

        with with_site_configuration_context(
                configuration={
                    "EMPTY_DASHBOARD_MESSAGE": empty_dashboard_message,
                }):
            response = self.client.get(reverse('dashboard'))
            self.assertContains(response,
                                'You are not enrolled in any courses yet.')
            self.assertContains(response, empty_dashboard_message)

    @patch('django.conf.settings.DASHBOARD_COURSE_LIMIT', 1)
    def test_course_limit_on_dashboard(self):
        course = CourseFactory.create()
        CourseEnrollmentFactory(user=self.user, course_id=course.id)

        course_v1 = CourseFactory.create()
        CourseEnrollmentFactory(user=self.user, course_id=course_v1.id)

        response = self.client.get(reverse('dashboard'))
        self.assertContains(response, '1 results successfully populated')

    @staticmethod
    def _remove_whitespace_from_html_string(html):
        return ''.join(html.split())

    @staticmethod
    def _remove_whitespace_from_response(response):
        return ''.join(response.content.decode('utf-8').split())

    @staticmethod
    def _pull_course_run_from_course_key(course_key_string):  # lint-amnesty, pylint: disable=missing-function-docstring
        search_results = re.search(r'Run_[0-9]+$', course_key_string)
        assert search_results
        course_run_string = search_results.group(0).replace('_', ' ')
        return course_run_string

    @staticmethod
    def _get_html_for_view_course_button(course_key_string, course_run_string):
        return '''
            <a href="/courses/{course_key}/course/"
               class="course-target-link enter-course"
               data-course-key="{course_key}">
              View Course
              <span class="sr">
                &nbsp;{course_run}
              </span>
            </a>
        '''.format(course_key=course_key_string, course_run=course_run_string)

    @staticmethod
    def _get_html_for_resume_course_button(course_key_string,
                                           resume_block_key_string,
                                           course_run_string):
        return '''
            <a href="/courses/{course_key}/jump_to/{url_to_block}"
               class="course-target-link enter-course"
               data-course-key="{course_key}">
              Resume Course
              <span class="sr">
                &nbsp;{course_run}
              </span>
            </a>
        '''.format(course_key=course_key_string,
                   url_to_block=resume_block_key_string,
                   course_run=course_run_string)

    @staticmethod
    def _get_html_for_entitlement_button(course_key_string):
        return '''
            <div class="course-info">
            <span class="info-university">{org} - </span>
            <span class="info-course-id">{course}</span>
            <span class="info-date-block-container">
            <button class="change-session btn-link ">Change or Leave Session</button>
            </span>
            </div>
        '''.format(org=course_key_string.split('/')[0],
                   course=course_key_string.split('/')[1])

    def test_view_course_appears_on_dashboard(self):
        """
        When a course doesn't have completion data, its course card should
        display a "View Course" button.
        """
        self.override_waffle_switch(True)

        course = CourseFactory.create()
        CourseEnrollmentFactory.create(user=self.user, course_id=course.id)

        response = self.client.get(reverse('dashboard'))

        course_key_string = str(course.id)
        # No completion data means there's no block from which to resume.
        resume_block_key_string = ''
        course_run_string = self._pull_course_run_from_course_key(
            course_key_string)

        view_button_html = self._get_html_for_view_course_button(
            course_key_string, course_run_string)
        resume_button_html = self._get_html_for_resume_course_button(
            course_key_string, resume_block_key_string, course_run_string)

        view_button_html = self._remove_whitespace_from_html_string(
            view_button_html)
        resume_button_html = self._remove_whitespace_from_html_string(
            resume_button_html)
        dashboard_html = self._remove_whitespace_from_response(response)

        assert view_button_html in dashboard_html
        assert resume_button_html not in dashboard_html

    def test_resume_course_appears_on_dashboard(self):
        """
        When a course has completion data, its course card should display a
        "Resume Course" button.
        """
        self.override_waffle_switch(True)

        course = CourseFactory.create()
        CourseEnrollmentFactory.create(user=self.user, course_id=course.id)

        course_key = course.id
        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)
        ]

        submit_completions_for_testing(self.user, block_keys)

        response = self.client.get(reverse('dashboard'))

        course_key_string = str(course_key)
        resume_block_key_string = str(block_keys[-1])
        course_run_string = self._pull_course_run_from_course_key(
            course_key_string)

        view_button_html = self._get_html_for_view_course_button(
            course_key_string, course_run_string)
        resume_button_html = self._get_html_for_resume_course_button(
            course_key_string, resume_block_key_string, course_run_string)

        view_button_html = self._remove_whitespace_from_html_string(
            view_button_html)
        resume_button_html = self._remove_whitespace_from_html_string(
            resume_button_html)
        dashboard_html = self._remove_whitespace_from_response(response)

        assert resume_button_html in dashboard_html
        assert view_button_html not in dashboard_html

    def test_content_gating_course_card_changes(self):
        """
        When a course is expired, the links on the course card should be removed.
        Links will be removed from the course title, course image and button (View Course/Resume Course).
        The course card should have an access expired message.
        """
        CourseDurationLimitConfig.objects.create(
            enabled=True,
            enabled_as_of=self.THREE_YEARS_AGO - timedelta(days=30))
        self.override_waffle_switch(True)

        course = CourseFactory.create(start=self.THREE_YEARS_AGO)
        add_course_mode(course, mode_slug=CourseMode.AUDIT)
        add_course_mode(course)
        enrollment = CourseEnrollmentFactory.create(user=self.user,
                                                    course_id=course.id)
        enrollment.created = self.THREE_YEARS_AGO + timedelta(days=1)
        enrollment.save()

        response = self.client.get(reverse('dashboard'))
        dashboard_html = self._remove_whitespace_from_response(response)
        access_expired_substring = 'Accessexpired'
        course_link_class = 'course-target-link'

        assert course_link_class not in dashboard_html

        assert access_expired_substring in dashboard_html

    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]

            assert expected_button in dashboard_html
            assert unexpected_button not in dashboard_html
 def setUpClass(cls):
     super(EnrollmentTrackPartitionSchemeTest, cls).setUpClass()
     cls.course = CourseFactory.create()
     cls.student = UserFactory()
Example #7
0
 def test_duplicate_email(self):
     UserFactory.create(email=self.pending_change_request.new_email)
     self.check_confirm_email_change('email_exists.html', {})
     self.assertFailedBeforeEmailing()
Example #8
0
 def setUp(self):
     super().setUp()
     self.org = 'testX'
     self.course = CourseFactory.create(org=self.org)
     self.users = UserFactory.create_batch(5)
     CourseOverview.load_from_module_store(self.course.id)
 def _setup_user(self):
     self.user = UserFactory.create()
     CourseEnrollment.enroll(self.user, self.course_key)
Example #10
0
 def setUp(self):
     super().setUp()
     self.service_user = UserFactory(username=self.SERVICE_USERNAME)
     self.url = reverse("username_replacement")
Example #11
0
 def create_user(self):
     """
     Creates a normal student user.
     """
     return UserFactory()
Example #12
0
 def setUp(self):
     super().setUp()
     self.client = APIClient()
     self.user = UserFactory.create(password=TEST_PASSWORD)
     self.url = reverse("accounts_api", kwargs={'username': self.user.username})
Example #13
0
    def test_cohort_membership_changed(self, mock_tracker):
        cohort_list = [CohortFactory() for _ in range(2)]
        non_cohort = CourseUserGroup.objects.create(
            name="dummy",
            course_id=self.course_key,
            group_type="dummy"
        )
        user_list = [UserFactory() for _ in range(2)]
        mock_tracker.reset_mock()

        def assert_events(event_name_suffix, user_list, cohort_list):
            """
            Confirms the presence of the specifed event for each user in the specified list of cohorts
            """
            expected_calls = [
                call(
                    "edx.cohort.user_" + event_name_suffix,
                    {
                        "user_id": user.id,
                        "cohort_id": cohort.id,
                        "cohort_name": cohort.name,
                    }
                )
                for user in user_list for cohort in cohort_list
            ]
            mock_tracker.emit.assert_has_calls(expected_calls, any_order=True)

        # Add users to cohort
        cohort_list[0].users.add(*user_list)
        assert_events("added", user_list, cohort_list[:1])
        mock_tracker.reset_mock()

        # Remove users from cohort
        cohort_list[0].users.remove(*user_list)
        assert_events("removed", user_list, cohort_list[:1])
        mock_tracker.reset_mock()

        # Clear users from cohort
        cohort_list[0].users.add(*user_list)
        cohort_list[0].users.clear()
        assert_events("removed", user_list, cohort_list[:1])
        mock_tracker.reset_mock()

        # Clear users from non-cohort group
        non_cohort.users.add(*user_list)
        non_cohort.users.clear()
        self.assertFalse(mock_tracker.emit.called)

        # Add cohorts to user
        user_list[0].course_groups.add(*cohort_list)
        assert_events("added", user_list[:1], cohort_list)
        mock_tracker.reset_mock()

        # Remove cohorts from user
        user_list[0].course_groups.remove(*cohort_list)
        assert_events("removed", user_list[:1], cohort_list)
        mock_tracker.reset_mock()

        # Clear cohorts from user
        user_list[0].course_groups.add(*cohort_list)
        user_list[0].course_groups.clear()
        assert_events("removed", user_list[:1], cohort_list)
        mock_tracker.reset_mock()

        # Clear non-cohort groups from user
        user_list[0].course_groups.add(non_cohort)
        user_list[0].course_groups.clear()
        self.assertFalse(mock_tracker.emit.called)
Example #14
0
    def test_add_user_to_cohort(self, mock_signal, mock_tracker):
        """
        Make sure cohorts.add_user_to_cohort() properly adds a user to a cohort and
        handles errors.
        """
        course_user = UserFactory(username="******", email="*****@*****.**")
        UserFactory(username="******", email="*****@*****.**")
        course = modulestore().get_course(self.toy_course_key)
        CourseEnrollment.enroll(course_user, self.toy_course_key)
        first_cohort = CohortFactory(course_id=course.id, name="FirstCohort")
        second_cohort = CohortFactory(course_id=course.id, name="SecondCohort")

        def check_and_reset_signal():
            mock_signal.send.assert_called_with(sender=None, user=course_user, course_key=self.toy_course_key)
            mock_signal.reset_mock()

        # Success cases
        # We shouldn't get back a previous cohort, since the user wasn't in one
        self.assertEqual(
            cohorts.add_user_to_cohort(first_cohort, "Username"),
            (course_user, None, False)
        )
        mock_tracker.emit.assert_any_call(
            "edx.cohort.user_add_requested",
            {
                "user_id": course_user.id,
                "cohort_id": first_cohort.id,
                "cohort_name": first_cohort.name,
                "previous_cohort_id": None,
                "previous_cohort_name": None,
            }
        )
        check_and_reset_signal()

        # Should get (user, previous_cohort_name) when moved from one cohort to
        # another
        self.assertEqual(
            cohorts.add_user_to_cohort(second_cohort, "Username"),
            (course_user, "FirstCohort", False)
        )
        mock_tracker.emit.assert_any_call(
            "edx.cohort.user_add_requested",
            {
                "user_id": course_user.id,
                "cohort_id": second_cohort.id,
                "cohort_name": second_cohort.name,
                "previous_cohort_id": first_cohort.id,
                "previous_cohort_name": first_cohort.name,
            }
        )
        check_and_reset_signal()

        # Should preregister email address for a cohort if an email address
        # not associated with a user is added
        (user, previous_cohort, prereg) = cohorts.add_user_to_cohort(first_cohort, "*****@*****.**")
        self.assertEqual(
            (user, previous_cohort, prereg),
            (None, None, True)
        )
        mock_tracker.emit.assert_any_call(
            "edx.cohort.email_address_preassigned",
            {
                "user_email": "*****@*****.**",
                "cohort_id": first_cohort.id,
                "cohort_name": first_cohort.name,
            }
        )

        # Error cases
        # Should get ValueError if user already in cohort
        self.assertRaises(
            ValueError,
            lambda: cohorts.add_user_to_cohort(second_cohort, "Username")
        )
        # UserDoesNotExist if user truly does not exist
        self.assertRaises(
            User.DoesNotExist,
            lambda: cohorts.add_user_to_cohort(first_cohort, "non_existent_username")
        )
Example #15
0
 def test_success_no_enrollment(self):
     """
     Basic success path for users who have no enrollments, should simply not error
     """
     user = UserFactory()
     _listen_for_lms_retire(sender=self.__class__, user=user)
Example #16
0
 def setUpTestData(cls):
     """Set up and enroll our fake user in the course."""
     cls.user = UserFactory(password=TEST_PASSWORD)
     CourseEnrollment.enroll(cls.user, cls.course.id)
Example #17
0
 def setUp(self):
     super(UserAssertionTestCase, self).setUp()
     self.course = CourseFactory.create()
     self.user = UserFactory.create()
     # Password defined by factory.
     self.client.login(username=self.user.username, password='******')
 def setUp(self):
     super(TestAccountAPITransactions, self).setUp()  # lint-amnesty, pylint: disable=super-with-arguments
     self.client = APIClient()
     self.user = UserFactory.create(password=TEST_PASSWORD)
     self.url = reverse("accounts_api", kwargs={'username': self.user.username})
Example #19
0
 def setUp(self):
     super().setUp()
     self.course = None
     self.user = UserFactory()
 def setUp(self):
     super(UsernameReplacementViewTests, self).setUp()  # lint-amnesty, pylint: disable=super-with-arguments
     self.service_user = UserFactory(username=self.SERVICE_USERNAME)
     self.url = reverse("username_replacement")
Example #21
0
 def _create_user(self, is_superuser=False, is_staff=False):
     return UserFactory(username='******',
                        is_superuser=is_superuser,
                        is_staff=is_staff)
Example #22
0
 def setUp(self):
     super(UserProfilePropertiesTest, self).setUp()  # lint-amnesty, pylint: disable=super-with-arguments
     self.user = UserFactory.create(password=self.password)
     self.profile = self.user.profile
 def setUpClass(cls):
     super().setUpClass()
     cls.command = 'create_api_access_request'
     cls.user = UserFactory()
Example #24
0
 def setUpTestData(cls):
     cls.user = UserFactory.create(password='******')
     super().setUpTestData()
Example #25
0
    def setUp(self):
        super().setUp()

        self.catalog_integration = self.create_catalog_integration(cache_ttl=1)
        self.user = UserFactory(username=self.catalog_integration.service_username)
        self.tomorrow = now() + timedelta(days=1)
Example #26
0
 def setUpTestData(cls):  # lint-amnesty, pylint: disable=super-method-not-called
     """Set up and enroll our fake user in the course."""
     cls.user = UserFactory(password=TEST_PASSWORD)
     CourseEnrollment.enroll(cls.user, cls.course.id)
     cls.site = Site.objects.get_current()
class PermissionTests(ModuleStoreTestCase):
    """
    Tests for permissions defined in courseware.rules
    """
    def setUp(self):
        super().setUp()
        self.user = UserFactory()
        self.course = CourseFactory(enable_proctored_exams=True)

        self.course_id = self.course.id  # pylint: disable=no-member
        CourseModeFactory(mode_slug='verified', course_id=self.course_id)
        CourseModeFactory(mode_slug='masters', course_id=self.course_id)
        CourseModeFactory(mode_slug='professional', course_id=self.course_id)
        CourseEnrollment.unenroll(self.user, self.course_id)

    def tearDown(self):
        super().tearDown()
        self.user.delete()

    @ddt.data(
        ('audit', False),
        ('verified', True),
        ('masters', True),
        ('professional', True),
        ('no-id-professional', False),
    )
    @ddt.unpack
    def test_proctoring_perm(self, mode, should_have_perm):
        """
        Test that the user has the edx_proctoring.can_take_proctored_exam permission
        """
        if mode is not None:
            CourseEnrollment.enroll(self.user, self.course_id, mode=mode)
        has_perm = self.user.has_perm('edx_proctoring.can_take_proctored_exam',
                                      {'course_id': str(self.course_id)})
        assert has_perm == should_have_perm

    def test_proctoring_perm_no_enrollment(self):
        """
        Test that the user does not have the edx_proctoring.can_take_proctored_exam permission if they
        are not enrolled in the course
        """
        has_perm = self.user.has_perm('edx_proctoring.can_take_proctored_exam',
                                      {'course_id': str(self.course_id)})
        assert not has_perm

    @patch.dict(
        'django.conf.settings.PROCTORING_BACKENDS',
        {'mock_proctoring_allow_honor_mode': {
            'allow_honor_mode': True
        }})
    def test_proctoring_perm_with_honor_mode_permission(self):
        """
        Test that the user has the edx_proctoring.can_take_proctored_exam permission in honor enrollment mode.

        If proctoring backend configuration allows exam in honor mode {`allow_honor_mode`: True} the user is
        granted proctored exam permission.
        """
        course_allow_honor = CourseFactory(
            enable_proctored_exams=True,
            proctoring_provider='mock_proctoring_allow_honor_mode')
        CourseEnrollment.enroll(self.user, course_allow_honor.id, mode='honor')
        assert self.user.has_perm(
            'edx_proctoring.can_take_proctored_exam', {
                'course_id': str(course_allow_honor.id),
                'backend': 'mock_proctoring_allow_honor_mode',
                'is_proctored': True
            })
Example #28
0
def super_user_object():
    """
    Fixture which returns a superuser.
    """
    return UserFactory(is_superuser=True, is_staff=True)
Example #29
0
 def setUp(self):
     """
     Fixtures
     """
     super(TestRequireStudentIdentifier, self).setUp()
     self.student = UserFactory.create()
Example #30
0
    def test_oauth2_provider_edit_icon_image(self):
        """
        Test that we can update an OAuth provider's icon image from the admin
        form.

        OAuth providers are updated using KeyedConfigurationModelAdmin, which
        updates models by adding a new instance that replaces the old one,
        instead of editing the old instance directly.

        Updating the icon image is tricky here because
        KeyedConfigurationModelAdmin copies data over from the previous
        version by injecting its attributes into request.GET, but the icon
        ends up in request.FILES. We need to ensure that the value is
        prepopulated correctly, and that we can clear and update the image.
        """
        # Login as a super user
        user = UserFactory.create(is_staff=True, is_superuser=True)
        user.save()
        self.client.login(username=user.username, password='******')

        # Get baseline provider count
        providers = OAuth2ProviderConfig.objects.all()
        pcount = len(providers)

        # Create a provider
        provider1 = self.configure_dummy_provider(
            enabled=True,
            icon_class='',
            icon_image=SimpleUploadedFile(
                'icon.svg', b'<svg><rect width="50" height="100"/></svg>'),
        )

        # Get the provider instance with active flag
        providers = OAuth2ProviderConfig.objects.all()
        self.assertEqual(len(providers), 1)
        self.assertEqual(providers[pcount].id, provider1.id)

        # Edit the provider via the admin edit link
        admin = OAuth2ProviderConfigAdmin(provider1, AdminSite())
        update_url = reverse('admin:{}_{}_add'.format(
            admin.model._meta.app_label, admin.model._meta.model_name))
        update_url += "?source={}".format(provider1.pk)

        # Remove the icon_image from the POST data, to simulate unchanged icon_image
        post_data = models.model_to_dict(provider1)
        del post_data['icon_image']
        # Remove max_session_length and organization. A default null value must be POSTed
        # back as an absent value, rather than as a "null-like" included value.
        del post_data['max_session_length']
        del post_data['organization']

        # Change the name, to verify POST
        post_data['name'] = 'Another name'

        # Post the edit form: expecting redirect
        response = self.client.post(update_url, post_data)
        self.assertEqual(response.status_code, 302)

        # Editing the existing provider creates a new provider instance
        providers = OAuth2ProviderConfig.objects.all()
        self.assertEqual(len(providers), pcount + 2)
        self.assertEqual(providers[pcount].id, provider1.id)
        provider2 = providers[pcount + 1]

        # Ensure the icon_image was preserved on the new provider instance
        self.assertEqual(provider2.icon_image, provider1.icon_image)
        self.assertEqual(provider2.name, post_data['name'])