Esempio n. 1
0
 def user_is_embargoed(self, user, is_embargoed):
     """
     Set a users emabargo state.
     """
     user_profile = UserFactory(username=user.username, email=user.email).profile
     user_profile.allow_certificate = not is_embargoed
     user_profile.save()
Esempio n. 2
0
class EcommerceApiClientTest(TestCase):
    """ Tests to ensure the client is initialized properly. """

    TEST_USER_EMAIL = '*****@*****.**'
    TEST_CLIENT_ID = 'test-client-id'

    def setUp(self):
        super(EcommerceApiClientTest, self).setUp()
        self.user = UserFactory()
        self.user.email = self.TEST_USER_EMAIL
        self.user.save()  # pylint: disable=no-member

    @httpretty.activate
    def test_tracking_context(self):
        """
        Ensure the tracking context is set up in the api client correctly and
        automatically.
        """
        # fake an ecommerce api request.
        httpretty.register_uri(
            httpretty.POST,
            '{}/baskets/1/'.format(TEST_API_URL),
            status=200, body='{}',
            adding_headers={'Content-Type': 'application/json'}
        )
        mock_tracker = mock.Mock()
        mock_tracker.resolve_context = mock.Mock(return_value={'client_id': self.TEST_CLIENT_ID})
        with mock.patch('commerce.tracker.get_tracker', return_value=mock_tracker):
            ecommerce_api_client(self.user).baskets(1).post()

        # make sure the request's JWT token payload included correct tracking context values.
        actual_header = httpretty.last_request().headers['Authorization']
        expected_payload = {
            'username': self.user.username,
            'email': self.user.email,
            'tracking_context': {
                'lms_user_id': self.user.id,  # pylint: disable=no-member
                'lms_client_id': self.TEST_CLIENT_ID,
            },
        }
        expected_header = 'JWT {}'.format(jwt.encode(expected_payload, TEST_API_SIGNING_KEY))
        self.assertEqual(actual_header, expected_header)

    @httpretty.activate
    def test_client_unicode(self):
        """
        The client should handle json responses properly when they contain
        unicode character data.

        Regression test for ECOM-1606.
        """
        expected_content = '{"result": "Préparatoire"}'
        httpretty.register_uri(
            httpretty.GET,
            '{}/baskets/1/order/'.format(TEST_API_URL),
            status=200, body=expected_content,
            adding_headers={'Content-Type': 'application/json'},
        )
        actual_object = ecommerce_api_client(self.user).baskets(1).order.get()
        self.assertEqual(actual_object, {u"result": u"Préparatoire"})
Esempio n. 3
0
    def test_create_permissions(self):
        """ Users should only be allowed to create data for themselves. """
        url = reverse('api_experiments:v0:data-list')

        # Authentication is required
        response = self.client.post(url, {})
        self.assertEqual(response.status_code, 401)

        user = UserFactory()
        data = {
            'experiment_id': 1,
            'key': 'foo',
            'value': 'bar',
        }
        self.client.login(username=user.username, password=UserFactory._DEFAULT_PASSWORD)

        # Users can create data for themselves
        response = self.client.post(url, data)
        self.assertEqual(response.status_code, 201)
        ExperimentData.objects.get(user=user)

        # A non-staff user cannot create data for another user
        other_user = UserFactory()
        data['user'] = other_user.username
        response = self.client.post(url, data)
        self.assertEqual(response.status_code, 403)
        self.assertFalse(ExperimentData.objects.filter(user=other_user).exists())

        # A staff user can create data for other users
        user.is_staff = True
        user.save()
        response = self.client.post(url, data)
        self.assertEqual(response.status_code, 201)
        ExperimentData.objects.get(user=other_user)
Esempio n. 4
0
class IssueProgramCertificatesViewTests(TestCase, ProgramsApiConfigMixin):
    password = '******'

    def setUp(self):
        super(IssueProgramCertificatesViewTests, self).setUp()

        self.create_programs_config()

        self.path = reverse('support:programs-certify')
        self.user = UserFactory(password=self.password, is_staff=True)
        self.data = {'username': self.user.username}
        self.headers = {}

        self.client.login(username=self.user.username, password=self.password)

    def _verify_response(self, status_code):
        """Verify that the endpoint returns the provided status code and enqueues the task if appropriate."""
        with mock.patch('lms.djangoapps.support.views.programs.award_program_certificates.delay') as mock_task:
            response = self.client.post(self.path, self.data, **self.headers)

        self.assertEqual(response.status_code, status_code)
        self.assertEqual(status_code == 200, mock_task.called)

    def test_authentication_required(self):
        """Verify that the endpoint requires authentication."""
        self.client.logout()

        self._verify_response(403)

    def test_session_auth(self):
        """Verify that the endpoint supports session auth."""
        self._verify_response(200)

    def test_oauth(self):
        """Verify that the endpoint supports OAuth 2.0."""
        access_token = AccessTokenFactory(user=self.user, client=ClientFactory()).token  # pylint: disable=no-member
        self.headers['HTTP_AUTHORIZATION'] = 'Bearer ' + access_token

        self.client.logout()

        self._verify_response(200)

    def test_staff_permissions_required(self):
        """Verify that staff permissions are required to access the endpoint."""
        self.user.is_staff = False
        self.user.save()  # pylint: disable=no-member

        self._verify_response(403)

    def test_certification_disabled(self):
        """Verify that the endpoint returns a 400 when program certification is disabled."""
        self.create_programs_config(enable_certification=False)

        self._verify_response(400)

    def test_username_required(self):
        """Verify that the endpoint returns a 400 when a username isn't provided."""
        self.data.pop('username')

        self._verify_response(400)
Esempio n. 5
0
    def test_process_postpay_accepted(self):
        """
        Tests the ACCEPTED path of process_postpay
        """
        student1 = UserFactory()
        student1.save()

        order1 = Order.get_cart_for_user(student1)
        params = {
            'card_accountNumber': '1234',
            'card_cardType': '001',
            'billTo_firstName': student1.first_name,
            'orderNumber': str(order1.id),
            'orderCurrency': 'usd',
            'decision': 'ACCEPT',
            'ccAuthReply_amount': '0.00'
        }
        result = process_postpay_callback(params)
        self.assertTrue(result['success'])
        self.assertEqual(result['order'], order1)
        order1 = Order.objects.get(
            id=order1.id
        )  # reload from DB to capture side-effect of process_postpay_callback
        self.assertEqual(order1.status, 'purchased')
        self.assertFalse(result['error_html'])
Esempio n. 6
0
    def test_record_purchase(self):
        """
        Tests record_purchase with good and without returned CCNum
        """
        student1 = UserFactory()
        student1.save()
        student2 = UserFactory()
        student2.save()
        params_cc = {
            'card_accountNumber': '1234',
            'card_cardType': '001',
            'billTo_firstName': student1.first_name
        }
        params_nocc = {
            'card_accountNumber': '',
            'card_cardType': '002',
            'billTo_firstName': student2.first_name
        }
        order1 = Order.get_cart_for_user(student1)
        order2 = Order.get_cart_for_user(student2)
        record_purchase(params_cc, order1)
        record_purchase(params_nocc, order2)
        self.assertEqual(order1.bill_to_first, student1.first_name)
        self.assertEqual(order1.status, 'purchased')

        order2 = Order.objects.get(user=student2)
        self.assertEqual(order2.bill_to_first, student2.first_name)
        self.assertEqual(order2.status, 'purchased')
    def test_transfer_students(self):
        student = UserFactory()
        student.set_password(self.PASSWORD)  # pylint: disable=E1101
        student.save()   # pylint: disable=E1101

        # Original Course
        original_course_location = locator.CourseLocator('Org0', 'Course0', 'Run0')
        course = self._create_course(original_course_location)
        # Enroll the student in 'verified'
        CourseEnrollment.enroll(student, course.id, mode="verified")

        # New Course 1
        course_location_one = locator.CourseLocator('Org1', 'Course1', 'Run1')
        new_course_one = self._create_course(course_location_one)

        # New Course 2
        course_location_two = locator.CourseLocator('Org2', 'Course2', 'Run2')
        new_course_two = self._create_course(course_location_two)
        original_key = unicode(course.id)
        new_key_one = unicode(new_course_one.id)
        new_key_two = unicode(new_course_two.id)

        # Run the actual management command
        transfer_students.Command().handle(
            source_course=original_key, dest_course_list=new_key_one + "," + new_key_two
        )

        # Confirm the enrollment mode is verified on the new courses, and enrollment is enabled as appropriate.
        self.assertEquals(('verified', False), CourseEnrollment.enrollment_mode_for_user(student, course.id))
        self.assertEquals(('verified', True), CourseEnrollment.enrollment_mode_for_user(student, new_course_one.id))
        self.assertEquals(('verified', True), CourseEnrollment.enrollment_mode_for_user(student, new_course_two.id))
Esempio n. 8
0
    def test_create_permissions(self):
        """ Users should only be allowed to create data for themselves. """
        url = reverse('api_experiments:v0:data-list')

        # Authentication is required
        response = self.client.post(url, {})
        self.assertEqual(response.status_code, 401)

        user = UserFactory()
        data = {
            'experiment_id': 1,
            'key': 'foo',
            'value': 'bar',
        }
        self.client.login(username=user.username, password=UserFactory._DEFAULT_PASSWORD)

        # Users can create data for themselves
        response = self.client.post(url, data)
        self.assertEqual(response.status_code, 201)
        ExperimentData.objects.get(user=user)

        # A non-staff user cannot create data for another user
        other_user = UserFactory()
        data['user'] = other_user.username
        response = self.client.post(url, data)
        self.assertEqual(response.status_code, 403)
        self.assertFalse(ExperimentData.objects.filter(user=other_user).exists())

        # A staff user can create data for other users
        user.is_staff = True
        user.save()
        response = self.client.post(url, data)
        self.assertEqual(response.status_code, 201)
        ExperimentData.objects.get(user=other_user)
Esempio n. 9
0
    def test_tracking_context(self):
        """ Ensure the tracking context is set up in the api client correctly
        and automatically. """
        user = UserFactory()
        user.email = self.TEST_USER_EMAIL
        user.save()  # pylint: disable=no-member

        # fake an ecommerce api request.
        httpretty.register_uri(
            httpretty.POST,
            '{}/baskets/1/'.format(TEST_API_URL),
            status=200,
            body='{}',
            adding_headers={'Content-Type': 'application/json'})
        mock_tracker = mock.Mock()
        mock_tracker.resolve_context = mock.Mock(
            return_value={'client_id': self.TEST_CLIENT_ID})
        with mock.patch('commerce.tracker.get_tracker',
                        return_value=mock_tracker):
            ecommerce_api_client(user).baskets(1).post()

        # make sure the request's JWT token payload included correct tracking context values.
        actual_header = httpretty.last_request().headers['Authorization']
        expected_payload = {
            'username': user.username,
            'email': user.email,
            'tracking_context': {
                'lms_user_id': user.id,  # pylint: disable=no-member
                'lms_client_id': self.TEST_CLIENT_ID,
            },
        }
        expected_header = 'JWT {}'.format(
            jwt.encode(expected_payload, TEST_API_SIGNING_KEY))
        self.assertEqual(actual_header, expected_header)
Esempio n. 10
0
 def test_authenticated_user(self):
     req = self.make_successful_sneakpeek_login_request()
     user = UserFactory()
     user.save()
     req.user = user
     self.assertIsNone(self.middleware.process_request(req))
     self.assertNoSneakPeek(req, self.open_course, check_auth=False)
Esempio n. 11
0
    def test_enroll_inactive_user_again(self, auto_enroll):
        course_key = CourseLocator('Robot', 'fAKE', 'C--se--ID')
        before_ideal = SettableEnrollmentState(
            user=True,
            enrollment=False,
            allowed=True,
            auto_enroll=auto_enroll,
        )
        print("checking initialization...")
        user = UserFactory()
        user.is_active = False
        user.save()
        eobjs = EnrollmentObjects(
            user.email, None, None,
            CourseEnrollmentAllowed.objects.create(email=user.email,
                                                   course_id=course_key,
                                                   auto_enroll=auto_enroll))
        before = EmailEnrollmentState(course_key, eobjs.email)
        self.assertEqual(before, before_ideal)

        print('running action...')
        enroll_email(self.course_key, eobjs.email, auto_enroll=auto_enroll)

        print('checking effects...')

        after_ideal = SettableEnrollmentState(
            user=True,
            enrollment=False,
            allowed=True,
            auto_enroll=auto_enroll,
        )
        after = EmailEnrollmentState(self.course_key, eobjs.email)
        self.assertEqual(after, after_ideal)
Esempio n. 12
0
def test_is_username_retired_is_retired():
    """
    Check functionality of is_username_retired when username is retired
    """
    user = UserFactory()
    original_username = user.username
    retired_username = get_retired_username_by_username(user.username)

    # Fake username retirement.
    user.username = retired_username
    user.save()

    assert is_username_retired(original_username)
Esempio n. 13
0
def test_is_email_retired_is_retired():
    """
    Check functionality of is_email_retired when email is retired
    """
    user = UserFactory()
    original_email = user.email
    retired_email = get_retired_email_by_email(user.email)

    # Fake email retirement.
    user.email = retired_email
    user.save()

    assert is_email_retired(original_email)
Esempio n. 14
0
def test_is_email_retired_is_retired():
    """
    Check functionality of is_email_retired when email is retired
    """
    user = UserFactory()
    original_email = user.email
    retired_email = get_retired_email_by_email(user.email)

    # Fake email retirement.
    user.email = retired_email
    user.save()

    assert is_email_retired(original_email)
Esempio n. 15
0
def test_is_username_retired_is_retired():
    """
    Check functionality of is_username_retired when username is retired
    """
    user = UserFactory()
    original_username = user.username
    retired_username = get_retired_username_by_username(user.username)

    # Fake username retirement.
    user.username = retired_username
    user.save()

    assert is_username_retired(original_username)
Esempio n. 16
0
    def test_session_auth(self):
        """ Verify the endpoint supports session authentication, and only allows authorization for staff users. """
        user = UserFactory(password=self.password, is_staff=False)
        self.client.login(username=user.username, password=self.password)

        # Non-staff users should not have access to the API
        response = self.client.get(self.path)
        self.assertEqual(response.status_code, 403)

        # Staff users should have access to the API
        user.is_staff = True
        user.save()
        response = self.client.get(self.path)
        self.assertEqual(response.status_code, 200)
Esempio n. 17
0
    def test_session_auth(self):
        """ Verify the endpoint supports session authentication, and only allows authorization for staff users. """
        user = UserFactory(password=self.password, is_staff=False)
        self.client.login(username=user.username, password=self.password)

        # Non-staff users should not have access to the API
        response = self.client.get(self.path)
        self.assertEqual(response.status_code, 403)

        # Staff users should have access to the API
        user.is_staff = True
        user.save()  # pylint: disable=no-member
        response = self.client.get(self.path)
        self.assertEqual(response.status_code, 200)
Esempio n. 18
0
    def test_retired_username(self):
        """
        Ensure that a retired username cannot be registered again.
        """
        user = UserFactory()
        orig_username = user.username

        # Fake retirement of the username.
        user.username = get_retired_username_by_username(orig_username)
        user.save()

        # Attempt to create another account with the same username that's been retired.
        self.url_params['username'] = orig_username
        response = self.client.post(self.url, self.url_params)
        self._validate_exiting_username_response(orig_username, response)
Esempio n. 19
0
    def test_retired_username(self):
        """
        Ensure that a retired username cannot be registered again.
        """
        user = UserFactory()
        orig_username = user.username

        # Fake retirement of the username.
        user.username = get_retired_username_by_username(orig_username)
        user.save()

        # Attempt to create another account with the same username that's been retired.
        self.url_params['username'] = orig_username
        response = self.client.post(self.url, self.url_params)
        self._validate_exiting_username_response(orig_username, response, self.INVALID_ERR_MSG[0], self.INVALID_ERR_MSG[1])
Esempio n. 20
0
    def test_oauth(self):
        """ Verify the endpoint supports OAuth, and only allows authorization for staff users. """
        user = UserFactory(is_staff=False)
        oauth_client = ClientFactory.create()
        access_token = AccessTokenFactory.create(user=user, client=oauth_client).token
        headers = {"HTTP_AUTHORIZATION": "Bearer " + access_token}

        # Non-staff users should not have access to the API
        response = self.client.get(self.path, **headers)
        self.assertEqual(response.status_code, 403)

        # Staff users should have access to the API
        user.is_staff = True
        user.save()  # pylint: disable=no-member
        response = self.client.get(self.path, **headers)
        self.assertEqual(response.status_code, 200)
Esempio n. 21
0
def test_get_potentially_retired_user_hashed_match():
    """
    Check that we can pass in a hashed username and get the
    user-to-be-retired back.
    """
    user = UserFactory()
    orig_username = user.username
    hashed_username = get_retired_username_by_username(orig_username)

    # Fake username retirement.
    user.username = hashed_username
    user.save()

    # Check to find the user by original username should fail,
    # 2nd check by hashed username should succeed.
    assert get_potentially_retired_user_by_username_and_hash(orig_username, hashed_username) == user
Esempio n. 22
0
class UniversityAPITest(TestCase):
    def setUp(self):
        self.api_url = reverse('fun-universities-api:universities-list')
        self.user = UserFactory(username='******',
                                password='******')  # user with profile
        self.univ_1 = UniversityFactory(code='test-university-1')
        self.univ_2 = UniversityFactory(code='test-university-2')

    def login_as_admin(self):
        self.user.is_staff = True
        self.user.save()
        self.client.login(username='******', password='******')

    def test_course_list_api_response_loads(self):
        self.login_as_admin()
        response = self.client.get(self.api_url)
        data = json.loads(response.content)
        self.assertIn('results', data)

    def test_can_update_university_score_as_admin(self):
        self.login_as_admin()
        self.univ_1.score = 0
        self.univ_1.save()
        data = json.dumps({'score': 100})
        url = reverse('fun-universities-api:universities-detail',
                      args=[self.univ_1.id])
        response = self.client.put(url, data, content_type='application/json')
        response_data = json.loads(response.content)
        self.assertEqual(100, response_data['score'])

    def test_cannot_update_university_if_set_as_prevent_auto_update(self):
        self.login_as_admin()
        self.univ_1.prevent_auto_update = True
        self.univ_1.save()
        data = {'score': 100}
        url = reverse('fun-universities-api:universities-detail',
                      args=[self.univ_1.id])
        response = self.client.put(url, data)
        self.assertNotEqual(response.status_code, 200)

    def test_cannot_update_course_score_if_not_logged_in(self):
        self.client.logout()
        data = {'score': 100}
        url = reverse('fun-universities-api:universities-detail',
                      args=[self.univ_1.id])
        response = self.client.put(url, data)
        self.assertNotEqual(response.status_code, 200)
Esempio n. 23
0
    def test_provider_login_can_handle_unicode_email_inactive_account(self):
        user = UserFactory(email=u"user.ąęł@gmail.com", username=u"ąęół")
        url = reverse("openid-provider-login")

        # login to the client so that we can persist session information
        user.profile.name = u"Jan ĄĘ"
        user.profile.save()
        self.client.login(username=user.username, password="******")
        # login once to get the right session information
        self.attempt_login(200)
        # We trigger situation where user is not active at final phase of
        # OpenId login.
        user.is_active = False
        user.save()
        post_args = {"email": user.email, "password": "******"}
        # call url again, this time with username and password
        self.client.post(url, post_args)
Esempio n. 24
0
    def test_oauth(self):
        """ Verify the endpoint supports OAuth, and only allows authorization for staff users. """
        user = UserFactory(is_staff=False)
        oauth_client = ClientFactory.create()
        access_token = AccessTokenFactory.create(user=user,
                                                 client=oauth_client).token
        headers = {'HTTP_AUTHORIZATION': 'Bearer ' + access_token}

        # Non-staff users should not have access to the API
        response = self.client.get(self.path, **headers)
        self.assertEqual(response.status_code, 403)

        # Staff users should have access to the API
        user.is_staff = True
        user.save()  # pylint: disable=no-member
        response = self.client.get(self.path, **headers)
        self.assertEqual(response.status_code, 200)
Esempio n. 25
0
    def test_provider_login_can_handle_unicode_email_inactive_account(self):
        user = UserFactory(email=u"user.ąęł@gmail.com")
        url = reverse('openid-provider-login')

        # login to the client so that we can persist session information
        user.profile.name = u'Jan ĄĘ'
        user.profile.save()  # pylint: disable=no-member
        self.client.login(username=user.username, password='******')
        # login once to get the right session information
        self.attempt_login(200)
        # We trigger situation where user is not active at final phase of
        # OpenId login.
        user.is_active = False
        user.save()  # pylint: disable=no-member
        post_args = {'email': user.email, 'password': '******'}
        # call url again, this time with username and password
        self.client.post(url, post_args)
Esempio n. 26
0
    def test_payment_accepted_order(self):
        """
        Tests payment_accepted cases with an order
        """
        student1 = UserFactory()
        student1.save()

        order1 = Order.get_cart_for_user(student1)
        params = {
            'card_accountNumber': '1234',
            'card_cardType': '001',
            'billTo_firstName': student1.first_name,
            'billTo_lastName': u"\u2603",
            'orderNumber': str(order1.id),
            'orderCurrency': 'usd',
            'decision': 'ACCEPT',
            'ccAuthReply_amount': '0.00'
        }

        # tests for an order number that doesn't match up
        params_bad_ordernum = params.copy()
        params_bad_ordernum['orderNumber'] = str(order1.id + 10)
        with self.assertRaises(CCProcessorDataException):
            payment_accepted(params_bad_ordernum)

        # tests for a reply amount of the wrong type
        params_wrong_type_amt = params.copy()
        params_wrong_type_amt['ccAuthReply_amount'] = 'ab'
        with self.assertRaises(CCProcessorDataException):
            payment_accepted(params_wrong_type_amt)

        # tests for a reply amount of the wrong type
        params_wrong_amt = params.copy()
        params_wrong_amt['ccAuthReply_amount'] = '1.00'
        with self.assertRaises(CCProcessorWrongAmountException):
            payment_accepted(params_wrong_amt)

        # tests for a not accepted order
        params_not_accepted = params.copy()
        params_not_accepted['decision'] = "REJECT"
        self.assertFalse(payment_accepted(params_not_accepted)['accepted'])

        # finally, tests an accepted order
        self.assertTrue(payment_accepted(params)['accepted'])
    def test_oauth_list(self, path_name):
        """ Verify the endpoints supports OAuth, and only allows authorization for staff users. """
        path = reverse(path_name,
                       kwargs={'course_key_string': self.course_str})
        user = UserFactory(is_staff=False)
        oauth_client = ClientFactory.create()
        access_token = AccessTokenFactory.create(user=user,
                                                 client=oauth_client).token
        headers = {'HTTP_AUTHORIZATION': 'Bearer ' + access_token}

        # Non-staff users should not have access to the API
        response = self.client.get(path=path, **headers)
        self.assertEqual(response.status_code, 403)

        # Staff users should have access to the API
        user.is_staff = True
        user.save()
        response = self.client.get(path=path, **headers)
        self.assertEqual(response.status_code, 200)
Esempio n. 28
0
    def test_oauth_list(self, path_name):
        """ Verify the endpoints supports OAuth, and only allows authorization for staff users. """
        path = reverse(path_name, kwargs={'course_key_string': self.course_str})
        user = UserFactory(is_staff=False)
        oauth_client = ClientFactory.create()
        access_token = AccessTokenFactory.create(user=user, client=oauth_client).token
        headers = {
            'HTTP_AUTHORIZATION': 'Bearer ' + access_token
        }

        # Non-staff users should not have access to the API
        response = self.client.get(path=path, **headers)
        self.assertEqual(response.status_code, 403)

        # Staff users should have access to the API
        user.is_staff = True
        user.save()
        response = self.client.get(path=path, **headers)
        self.assertEqual(response.status_code, 200)
Esempio n. 29
0
    def test_render_purchase_form_html(self, render):
        """
        Tests the rendering of the purchase form
        """
        student1 = UserFactory()
        student1.save()

        order1 = Order.get_cart_for_user(student1)
        item1 = OrderItem(order=order1, user=student1, unit_cost=1.0, line_cost=1.0)
        item1.save()
        render_purchase_form_html(order1)
        ((template, context), render_kwargs) = render.call_args

        self.assertEqual(template, 'shoppingcart/cybersource_form.html')
        self.assertDictContainsSubset({'amount': '1.00',
                                       'currency': 'usd',
                                       'orderPage_transactionType': 'sale',
                                       'orderNumber': str(order1.id)},
                                      context['params'])
Esempio n. 30
0
    def test_bulk_upsert_permissions(self):
        """ Only staff users can access the bulk upsert endpoint. """
        url = reverse('api_experiments:v0:data-bulk-upsert')
        data = []

        # Authentication is required
        response = self.client.put(url, data, format='json')
        self.assertEqual(response.status_code, 401)

        user = UserFactory()
        self.client.login(username=user.username, password=UserFactory._DEFAULT_PASSWORD)

        # No access to non-staff users
        response = self.client.put(url, data, format='json')
        self.assertEqual(response.status_code, 403)

        user.is_staff = True
        user.save()
        response = self.client.put(url, data, format='json')
        self.assertEqual(response.status_code, 200)
Esempio n. 31
0
    def test_bulk_upsert_permissions(self):
        """ Only staff users can access the bulk upsert endpoint. """
        url = reverse('api_experiments:v0:data-bulk-upsert')
        data = []

        # Authentication is required
        response = self.client.put(url, data, format='json')
        self.assertEqual(response.status_code, 401)

        user = UserFactory()
        self.client.login(username=user.username, password=UserFactory._DEFAULT_PASSWORD)

        # No access to non-staff users
        response = self.client.put(url, data, format='json')
        self.assertEqual(response.status_code, 403)

        user.is_staff = True
        user.save()
        response = self.client.put(url, data, format='json')
        self.assertEqual(response.status_code, 200)
    def test_provider_login_can_handle_unicode_email_inactive_account(self):
        user = UserFactory(email=u"user.ąęł@gmail.com")
        url = reverse('openid-provider-login')

        # login to the client so that we can persist session information
        user.profile.name = u'Jan ĄĘ'
        user.profile.save()  # pylint: disable=no-member
        self.client.login(username=user.username, password='******')
        # login once to get the right session information
        self.attempt_login(200)
        # We trigger situation where user is not active at final phase of
        # OpenId login.
        user.is_active = False
        user.save()  # pylint: disable=no-member
        post_args = {
            'email': user.email,
            'password': '******'
        }
        # call url again, this time with username and password
        self.client.post(url, post_args)
Esempio n. 33
0
    def test_oauth_csv(self):
        """ Verify the endpoint supports OAuth, and only allows authorization for staff users. """
        cohorts.add_cohort(self.course_key, "DEFAULT", "random")
        path = reverse('api_cohorts:cohort_users_csv',
                       kwargs={'course_key_string': self.course_str})
        user = UserFactory(is_staff=False)
        oauth_client = ApplicationFactory.create()
        access_token = AccessTokenFactory.create(
            user=user, application=oauth_client).token
        headers = {'HTTP_AUTHORIZATION': 'Bearer ' + access_token}

        # Non-staff users should not have access to the API
        response = self.client.post(path=path, **headers)
        self.assertEqual(response.status_code, 403)

        # Staff users should have access to the API
        user.is_staff = True
        user.save()
        response = self.client.post(path=path, **headers)
        self.assertEqual(response.status_code, 400)
Esempio n. 34
0
    def test_record_purchase(self):
        """
        Tests record_purchase with good and without returned CCNum
        """
        student1 = UserFactory()
        student1.save()
        student2 = UserFactory()
        student2.save()
        params_cc = {'card_accountNumber': '1234', 'card_cardType': '001', 'billTo_firstName': student1.first_name}
        params_nocc = {'card_accountNumber': '', 'card_cardType': '002', 'billTo_firstName': student2.first_name}
        order1 = Order.get_cart_for_user(student1)
        order2 = Order.get_cart_for_user(student2)
        record_purchase(params_cc, order1)
        record_purchase(params_nocc, order2)
        self.assertEqual(order1.bill_to_first, student1.first_name)
        self.assertEqual(order1.status, 'purchased')

        order2 = Order.objects.get(user=student2)
        self.assertEqual(order2.bill_to_first, student2.first_name)
        self.assertEqual(order2.status, 'purchased')
def _setup_users():
    """
    Creates and returns test users in the different states of needing rehash:
    - Skipped: has not yet been retired
    - Faked: has been fake-retired, but the retired username does not require updating
    - Needing rehash: has been fake-retired and name changed so it triggers a hash update
    """
    # When we loop through creating users, take additional action on these
    user_indexes_to_be_fake_retired = (2, 4, 6, 8, 10)
    user_indexes_to_be_rehashed = (4, 6)

    users_skipped = []
    users_faked = []
    users_needing_rehash = []
    retirements = {}

    # Create some test users with retirements
    for i in range(1, 11):
        user = UserFactory()
        retirement = UserRetirementStatus.create_retirement(user)
        retirements[user.id] = retirement

        if i in user_indexes_to_be_fake_retired:
            fake_completed_retirement(user)

            if i in user_indexes_to_be_rehashed:
                # In order to need a rehash user.username must be the same as
                # retirement.retired_username and NOT the same as the hash
                # generated when the script is run. So we force that here.
                retirement.retired_username = retirement.retired_username.upper(
                )
                user.username = retirement.retired_username
                retirement.save()
                user.save()
                users_needing_rehash.append(user)
            else:
                users_faked.append(user)
        else:
            users_skipped.append(user)
    return users_skipped, users_faked, users_needing_rehash, retirements
Esempio n. 36
0
    def test_transfer_students(self):
        student = UserFactory()
        student.set_password(self.PASSWORD)  # pylint: disable=E1101
        student.save()  # pylint: disable=E1101

        # Original Course
        original_course_location = locator.CourseLocator(
            'Org0', 'Course0', 'Run0')
        course = self._create_course(original_course_location)
        # Enroll the student in 'verified'
        CourseEnrollment.enroll(student, course.id, mode="verified")

        # New Course 1
        course_location_one = locator.CourseLocator('Org1', 'Course1', 'Run1')
        new_course_one = self._create_course(course_location_one)

        # New Course 2
        course_location_two = locator.CourseLocator('Org2', 'Course2', 'Run2')
        new_course_two = self._create_course(course_location_two)
        original_key = unicode(course.id)
        new_key_one = unicode(new_course_one.id)
        new_key_two = unicode(new_course_two.id)

        # Run the actual management command
        transfer_students.Command().handle(source_course=original_key,
                                           dest_course_list=new_key_one + "," +
                                           new_key_two)

        # Confirm the enrollment mode is verified on the new courses, and enrollment is enabled as appropriate.
        self.assertEquals(
            ('verified', False),
            CourseEnrollment.enrollment_mode_for_user(student, course.id))
        self.assertEquals(
            ('verified', True),
            CourseEnrollment.enrollment_mode_for_user(student,
                                                      new_course_one.id))
        self.assertEquals(
            ('verified', True),
            CourseEnrollment.enrollment_mode_for_user(student,
                                                      new_course_two.id))
Esempio n. 37
0
class UniversityAPITest(TestCase):

    def setUp(self):
        self.api_url = reverse('fun-universities-api:universities-list')
        self.user = UserFactory(username='******', password='******') # user with profile
        self.univ_1 = UniversityFactory(code='test-university-1')
        self.univ_2 = UniversityFactory(code='test-university-2')

    def login_as_admin(self):
        self.user.is_staff = True
        self.user.save()
        self.client.login(username='******', password='******')

    def test_course_list_api_response_loads(self):
        self.login_as_admin()
        response = self.client.get(self.api_url)
        data = json.loads(response.content)
        self.assertIn('results', data)

    def test_can_update_university_score_as_admin(self):
        self.login_as_admin()
        self.univ_1.score = 0
        self.univ_1.save()
        data = {'score': 100}
        url = reverse('fun-universities-api:universities-detail',
            args=[self.univ_1.id]
        )
        response = self.client.put(url, data)
        response_data = json.loads(response.content)
        self.assertEqual(100, response_data['score'])

    def test_cannot_update_course_score_if_not_logged_in(self):
        self.client.logout()
        data = {'score': 100}
        url = reverse('fun-universities-api:universities-detail',
            args=[self.univ_1.id]
        )
        response = self.client.put(url, data)
        self.assertNotEqual(response.status_code, 200)
Esempio n. 38
0
    def test_process_postpay_not_accepted(self):
        """
        Tests the non-ACCEPTED path of process_postpay
        """
        student1 = UserFactory()
        student1.save()

        order1 = Order.get_cart_for_user(student1)
        params = {
            'card_accountNumber': '1234',
            'card_cardType': '001',
            'billTo_firstName': student1.first_name,
            'orderNumber': str(order1.id),
            'orderCurrency': 'usd',
            'decision': 'REJECT',
            'ccAuthReply_amount': '0.00',
            'reasonCode': '207'
        }
        result = process_postpay_callback(params)
        self.assertFalse(result['success'])
        self.assertEqual(result['order'], order1)
        self.assertEqual(order1.status, 'cart')
        self.assertIn(REASONCODE_MAP['207'], result['error_html'])
Esempio n. 39
0
    def test_oauth_users(self):
        """ Verify the endpoint supports OAuth, and only allows authorization for staff users. """
        cohorts.add_cohort(self.course_key, "DEFAULT", "random")
        path = reverse('api_cohorts:cohort_users', kwargs={'course_key_string': self.course_str, 'cohort_id': 1})
        user = UserFactory(is_staff=False)
        oauth_client = ClientFactory.create()
        access_token = AccessTokenFactory.create(user=user, client=oauth_client).token
        headers = {
            'HTTP_AUTHORIZATION': 'Bearer ' + access_token
        }
        data = {
            'users': [user.username]
        }

        # Non-staff users should not have access to the API
        response = self.client.post(path=path, data=data, **headers)
        self.assertEqual(response.status_code, 403)

        # Staff users should have access to the API
        user.is_staff = True
        user.save()
        response = self.client.post(path=path, data=data, **headers)
        self.assertEqual(response.status_code, 200)
Esempio n. 40
0
    def test_process_postpay_accepted(self):
        """
        Tests the ACCEPTED path of process_postpay
        """
        student1 = UserFactory()
        student1.save()

        order1 = Order.get_cart_for_user(student1)
        params = {
            'card_accountNumber': '1234',
            'card_cardType': '001',
            'billTo_firstName': student1.first_name,
            'orderNumber': str(order1.id),
            'orderCurrency': 'usd',
            'decision': 'ACCEPT',
            'ccAuthReply_amount': '0.00'
        }
        result = process_postpay_callback(params)
        self.assertTrue(result['success'])
        self.assertEqual(result['order'], order1)
        order1 = Order.objects.get(id=order1.id)  # reload from DB to capture side-effect of process_postpay_callback
        self.assertEqual(order1.status, 'purchased')
        self.assertFalse(result['error_html'])
class TestRecentEnrollments(ModuleStoreTestCase):
    """
    Unit tests for getting the list of courses for a logged in user
    """
    PASSWORD = '******'

    def setUp(self):
        """
        Add a student
        """
        super(TestRecentEnrollments, self).setUp()
        self.student = UserFactory()
        self.student.set_password(self.PASSWORD)
        self.student.save()

        # Old Course
        old_course_location = locator.CourseLocator('Org0', 'Course0', 'Run0')
        course, enrollment = self._create_course_and_enrollment(old_course_location)
        enrollment.created = datetime.datetime(1900, 12, 31, 0, 0, 0, 0)
        enrollment.save()

        # New Course
        course_location = locator.CourseLocator('Org1', 'Course1', 'Run1')
        self.course, _ = self._create_course_and_enrollment(course_location)

    def _create_course_and_enrollment(self, course_location):
        """ Creates a course and associated enrollment. """
        course = CourseFactory.create(
            org=course_location.org,
            number=course_location.course,
            run=course_location.run
        )
        enrollment = CourseEnrollment.enroll(self.student, course.id)
        return course, enrollment

    def _configure_message_timeout(self, timeout):
        """Configure the amount of time the enrollment message will be displayed. """
        config = DashboardConfiguration(recent_enrollment_time_delta=timeout)
        config.save()

    def test_recently_enrolled_courses(self):
        """
        Test if the function for filtering recent enrollments works appropriately.
        """
        self._configure_message_timeout(60)

        # get courses through iterating all courses
        courses_list = list(get_course_enrollment_pairs(self.student, None, []))
        self.assertEqual(len(courses_list), 2)

        recent_course_list = _get_recently_enrolled_courses(courses_list)
        self.assertEqual(len(recent_course_list), 1)

    def test_zero_second_delta(self):
        """
        Tests that the recent enrollment list is empty if configured to zero seconds.
        """
        self._configure_message_timeout(0)
        courses_list = list(get_course_enrollment_pairs(self.student, None, []))
        self.assertEqual(len(courses_list), 2)

        recent_course_list = _get_recently_enrolled_courses(courses_list)
        self.assertEqual(len(recent_course_list), 0)

    def test_enrollments_sorted_most_recent(self):
        """
        Test that the list of newly created courses are properly sorted to show the most
        recent enrollments first.

        """
        self._configure_message_timeout(600)

        # Create a number of new enrollments and courses, and force their creation behind
        # the first enrollment
        courses = []
        for idx, seconds_past in zip(range(2, 6), [5, 10, 15, 20]):
            course_location = locator.CourseLocator(
                'Org{num}'.format(num=idx),
                'Course{num}'.format(num=idx),
                'Run{num}'.format(num=idx)
            )
            course, enrollment = self._create_course_and_enrollment(course_location)
            enrollment.created = datetime.datetime.now(UTC) - datetime.timedelta(seconds=seconds_past)
            enrollment.save()
            courses.append(course)

        courses_list = list(get_course_enrollment_pairs(self.student, None, []))
        self.assertEqual(len(courses_list), 6)

        recent_course_list = _get_recently_enrolled_courses(courses_list)
        self.assertEqual(len(recent_course_list), 5)

        self.assertEqual(recent_course_list[1], courses[0])
        self.assertEqual(recent_course_list[2], courses[1])
        self.assertEqual(recent_course_list[3], courses[2])
        self.assertEqual(recent_course_list[4], courses[3])

    def test_dashboard_rendering(self):
        """
        Tests that the dashboard renders the recent enrollment messages appropriately.
        """
        self._configure_message_timeout(600)
        self.client.login(username=self.student.username, password=self.PASSWORD)
        response = self.client.get(reverse("dashboard"))
        self.assertContains(response, "Thank you for enrolling in")

    @ddt.data(
        (['audit', 'honor', 'verified'], False),
        (['professional'], False),
        (['verified'], False),
        (['audit'], True),
        (['honor'], True),
        ([], True)
    )
    @ddt.unpack
    def test_donate_button(self, course_modes, show_donate):
        # Enable the enrollment success message
        self._configure_message_timeout(10000)

        # Enable donations
        DonationConfiguration(enabled=True).save()

        # Create the course mode(s)
        for mode in course_modes:
            CourseModeFactory(mode_slug=mode, course_id=self.course.id)

        # Check that the donate button is or is not displayed
        self.client.login(username=self.student.username, password=self.PASSWORD)
        response = self.client.get(reverse("dashboard"))

        if show_donate:
            self.assertContains(response, "donate-container")
        else:
            self.assertNotContains(response, "donate-container")
Esempio n. 42
0
class TestUsers(BaseCourseList):
    def setUp(self):
        super(TestUsers, self).setUp()

        self.fcourse1 = Course.objects.create(key=self.course1.id, university=self.university)
        self.fcourse2 = Course.objects.create(key=self.course2.id, university=self.university)

        self.user2 = UserFactory(username='******') # user with profile
        self.user3 = User.objects.create(username='******')  # user without profile should not appear

    def test_user_list(self):
        response = self.client.get(reverse('backoffice:user-list'))
        self.assertEqual(200, response.status_code)
        users = response.context['users'].object_list
        self.assertTrue(self.user2 in users)
        self.assertTrue(self.user in users)
        self.assertTrue(self.user3 not in users)

    def test_user_list_filtering(self):
        response = self.client.get(reverse('backoffice:user-list') + '?search=user1')
        users = response.context['users'].object_list
        self.assertEqual(200, response.status_code)
        self.assertTrue(self.user2 in users)
        self.assertTrue(self.user3 not in users)
        self.assertTrue(self.user not in users)

    def test_user_detail(self):
        CourseEnrollmentFactory(course_id=self.course1.id, user=self.user2)
        CourseAccessRoleFactory(course_id=self.course1.id, user=self.user2, role=u'test_role')
        response = self.client.get(reverse('backoffice:user-detail', args=[self.user2.username]))
        self.assertEqual(200, response.status_code)
        self.assertEqual(response.context['enrollments'][0][0], self.course1.display_name)
        self.assertEqual(response.context['enrollments'][0][1].to_deprecated_string(),
                         self.course1.id.to_deprecated_string())
        self.assertEqual(response.context['enrollments'][0][2], False)
        self.assertEqual(set(response.context['enrollments'][0][3]), set([u'test_role']))

    def test_change_user_detail(self):
        data = {
            'email': u"*****@*****.**",
            'name': u"Néw näme",
            'gender': u"o",
            'language': u"Français",
            'level_of_education': u"other",
            'location': u"Paris",
            'year_of_birth': u"1973",
            'mailing_address': u"",
            'city': u"",
            'country': u"",
            'goals': u"fun",
        }
        response = self.client.post(reverse('backoffice:user-detail',
                args=[self.user2.username]), data)

        self.assertEqual(302, response.status_code)
        user = User.objects.select_related('profile').get(username=self.user2.username)
        self.assertEqual(u"*****@*****.**", user.email)
        self.assertEqual(u"Français", user.profile.language)

    def test_change_user_password(self):
        data = {
            'action': u"change-password",
            'new-password': u"new-password"
            }
        response = self.client.post(reverse('backoffice:user-detail',
                args=[self.user2.username]), data)
        self.assertEqual(302, response.status_code)
        self.assertEqual(self.user2, authenticate(username=self.user2.username, password='******'))

    def test_ban_user(self):
        data = {
            'action': u"ban-user",
            'value': u"disable"
            }
        response = self.client.post(reverse('backoffice:user-detail',
                args=[self.user2.username]), data)
        self.assertEqual(302, response.status_code)
        self.assertTrue(UserStanding.objects.filter(user=self.user2,
                account_status=UserStanding.ACCOUNT_DISABLED).exists())

    def test_unban_user(self):
        UserStanding.objects.create(user=self.user2,
                account_status=UserStanding.ACCOUNT_DISABLED,
                changed_by=self.user)
        data = {
            'action': u"ban-user",
            'value': u"reenable"
            }
        response = self.client.post(reverse('backoffice:user-detail',
                args=[self.user2.username]), data)
        self.assertEqual(302, response.status_code)
        self.assertTrue(UserStanding.objects.filter(user=self.user2,
                account_status=UserStanding.ACCOUNT_ENABLED).exists())

    def _create_certificate(self, course, grade):
        return GeneratedCertificateFactory.create(user_id=self.user2.id,
                                                  course_id=course.id,
                                                  grade=str(grade))

    def _change_certificate_grade(self, certificate, new_grade):
        """ Changes the certificate grade.

        Args:
         certificate (GeneratedCertificate): The certificate to change the grade from.
         new_grade (float): The new grade.

        Returns (GeneratedCertificate) : The changed certificate.
        """

        data = {'action': u"change-grade",
                'course-id' : str(certificate.course_id),
                'new-grade': str(new_grade)}
        self.client.post(reverse('backoffice:user-detail', args=[self.user2.username]),
                         data)
        return GeneratedCertificate.objects.get(user=self.user2)

    def test_change_grade(self):
        certificate = self._change_certificate_grade(self._create_certificate(self.course1, 0.3),
                                                     0.8)
        self.assertEqual(certificate.grade, '0.8')

    def test_invalid_grade(self):
        certificate = self._change_certificate_grade(self._create_certificate(self.course1, 0.3),
                                                     "SUPERGRADE!!!!")
        self.assertEqual(certificate.grade, '0.3')

    def test_resend_activation_email_button(self):
        self.user2.is_active = False
        self.user2.save()
        response = self.client.get(reverse('backoffice:user-detail', args=[self.user2.username]))
        self.assertIn('value="resend-activation"', response.content)

        self.user3.is_active = True
        self.user3.save()
        response = self.client.get(reverse('backoffice:user-detail', args=[self.user3.username]))
        self.assertNotIn('value="resend-activation"', response.content)

    def test_resend_activation_email(self):
        self.user2.is_active = False
        self.user2.save()
        Registration.objects.create(user=self.user2, activation_key='test_activation_key')
        data = {
            'action': u"resend-activation"
        }
        response = self.client.post(reverse('backoffice:user-detail',
                args=[self.user2.username]), data)
        self.assertEquals(len(mail.outbox), 1)
Esempio n. 43
0
class EmbargoMiddlewareAccessTests(UrlResetMixin, ModuleStoreTestCase):
    """Tests of embargo middleware country access rules.

    There are detailed unit tests for the rule logic in
    `test_api.py`; here, we're mainly testing the integration
    with middleware

    """
    USERNAME = '******'
    PASSWORD = '******'

    @patch.dict(settings.FEATURES, {'EMBARGO': True})
    def setUp(self):
        super(EmbargoMiddlewareAccessTests, self).setUp('embargo')
        self.user = UserFactory(username=self.USERNAME, password=self.PASSWORD)
        self.course = CourseFactory.create()
        self.client.login(username=self.USERNAME, password=self.PASSWORD)

        self.courseware_url = reverse(
            'course_root',
            kwargs={'course_id': unicode(self.course.id)}
        )
        self.non_courseware_url = reverse('dashboard')

        # Clear the cache to avoid interference between tests
        django_cache.clear()
        config_cache.clear()

    @patch.dict(settings.FEATURES, {'EMBARGO': True})
    @ddt.data(True, False)
    def test_blocked(self, disable_access_check):
        with restrict_course(self.course.id, access_point='courseware', disable_access_check=disable_access_check) as redirect_url:  # pylint: disable=line-too-long
            response = self.client.get(self.courseware_url)
            if disable_access_check:
                self.assertEqual(response.status_code, 200)
            else:
                self.assertRedirects(response, redirect_url)

    @patch.dict(settings.FEATURES, {'EMBARGO': True})
    def test_allowed(self):
        # Add the course to the list of restricted courses
        # but don't create any access rules
        RestrictedCourse.objects.create(course_key=self.course.id)

        # Expect that we can access courseware
        response = self.client.get(self.courseware_url)
        self.assertEqual(response.status_code, 200)

    @patch.dict(settings.FEATURES, {'EMBARGO': True})
    def test_non_courseware_url(self):
        with restrict_course(self.course.id):
            response = self.client.get(self.non_courseware_url)
            self.assertEqual(response.status_code, 200)

    @patch.dict(settings.FEATURES, {'EMBARGO': True})
    @ddt.data(
        # request_ip, blacklist, whitelist, is_enabled, allow_access
        ('173.194.123.35', ['173.194.123.35'], [], True, False),
        ('173.194.123.35', ['173.194.0.0/16'], [], True, False),
        ('173.194.123.35', ['127.0.0.0/32', '173.194.0.0/16'], [], True, False),
        ('173.195.10.20', ['173.194.0.0/16'], [], True, True),
        ('173.194.123.35', ['173.194.0.0/16'], ['173.194.0.0/16'], True, False),
        ('173.194.123.35', [], ['173.194.0.0/16'], True, True),
        ('192.178.2.3', [], ['173.194.0.0/16'], True, True),
        ('173.194.123.35', ['173.194.123.35'], [], False, True),
    )
    @ddt.unpack
    def test_ip_access_rules(self, request_ip, blacklist, whitelist, is_enabled, allow_access):
        # Ensure that IP blocking works for anonymous users
        self.client.logout()

        # Set up the IP rules
        IPFilter.objects.create(
            blacklist=", ".join(blacklist),
            whitelist=", ".join(whitelist),
            enabled=is_enabled
        )

        # Check that access is enforced
        response = self.client.get(
            "/",
            HTTP_X_FORWARDED_FOR=request_ip,
            REMOTE_ADDR=request_ip
        )

        if allow_access:
            self.assertEqual(response.status_code, 200)
        else:
            redirect_url = reverse(
                'embargo_blocked_message',
                kwargs={
                    'access_point': 'courseware',
                    'message_key': 'embargo'
                }
            )
            self.assertRedirects(response, redirect_url)

    @patch.dict(settings.FEATURES, {'EMBARGO': True})
    @ddt.data(
        ('courseware', 'default'),
        ('courseware', 'embargo'),
        ('enrollment', 'default'),
        ('enrollment', 'embargo')
    )
    @ddt.unpack
    def test_always_allow_access_to_embargo_messages(self, access_point, msg_key):
        # Blacklist an IP address
        IPFilter.objects.create(
            blacklist="192.168.10.20",
            enabled=True
        )

        url = reverse(
            'embargo_blocked_message',
            kwargs={
                'access_point': access_point,
                'message_key': msg_key
            }
        )
        response = self.client.get(
            url,
            HTTP_X_FORWARDED_FOR="192.168.10.20",
            REMOTE_ADDR="192.168.10.20"
        )
        self.assertEqual(response.status_code, 200)

    @patch.dict(settings.FEATURES, {'EMBARGO': True})
    def test_whitelist_ip_skips_country_access_checks(self):
        # Whitelist an IP address
        IPFilter.objects.create(
            whitelist="192.168.10.20",
            enabled=True
        )

        # Set up country access rules so the user would
        # be restricted from the course.
        with restrict_course(self.course.id):
            # Make a request from the whitelisted IP address
            response = self.client.get(
                self.courseware_url,
                HTTP_X_FORWARDED_FOR="192.168.10.20",
                REMOTE_ADDR="192.168.10.20"
            )

        # Expect that we were still able to access the page,
        # even though we would have been blocked by country
        # access rules.
        self.assertEqual(response.status_code, 200)

    @patch.dict(settings.FEATURES, {'EMBARGO': True})
    def test_always_allow_course_detail_access(self):
        """ Access to the Course Structure API's course detail endpoint should always be granted. """
        # Make the user staff so that it has permissions to access the views.
        self.user.is_staff = True
        self.user.save()  # pylint: disable=no-member

        # Blacklist an IP address
        ip_address = "192.168.10.20"
        IPFilter.objects.create(
            blacklist=ip_address,
            enabled=True
        )

        url = reverse('course_structure_api:v0:detail', kwargs={'course_id': unicode(self.course.id)})
        response = self.client.get(
            url,
            HTTP_X_FORWARDED_FOR=ip_address,
            REMOTE_ADDR=ip_address
        )
        self.assertEqual(response.status_code, 200)

        # Test with a fully-restricted course
        with restrict_course(self.course.id):
            response = self.client.get(
                url,
                HTTP_X_FORWARDED_FOR=ip_address,
                REMOTE_ADDR=ip_address
            )
            self.assertEqual(response.status_code, 200)
Esempio n. 44
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()
        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')
        self.course = CourseOverviewFactory.create(id=self.course_key, end_date=self.TOMORROW,
                                                   certificate_available_date=self.THREE_YEARS_AGO,
                                                   lowest_passing_grade=0.3)
        self.course_enrollment = CourseEnrollmentFactory(course_id=self.course.id, user=self.user)
        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')
        self.course = CourseOverviewFactory.create(id=self.course_key, end_date=self.THREE_YEARS_AGO,
                                                   certificate_available_date=self.TOMORROW,
                                                   lowest_passing_grade=0.3)
        self.course_enrollment = CourseEnrollmentFactory(course_id=self.course.id, user=self.user)
        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)
        self.course_enrollment = CourseEnrollmentFactory(course_id=self.course.id, user=self.user)
        self.set_course_sharing_urls(set_marketing, set_social_sharing)

        # Assert course sharing icons
        response = self.client.get(reverse('dashboard'))
        self.assertEqual('Share on Twitter' in response.content.decode('utf-8'), set_marketing or set_social_sharing)
        self.assertEqual('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')
        self.course = CourseFactory.create(
            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)

        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('student.views.dashboard.get_visible_sessions_for_entitlement')
    @patch('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('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('entitlements.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('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('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)
        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)
        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('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)
        self.assertEqual(pq(response.content)(self.EMAIL_SETTINGS_ELEMENT_ID).length, 1)

    @patch.object(CourseOverview, 'get_from_id')
    @patch('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)
        self.assertEqual(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
        self.assertTrue(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):
        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)

        self.assertIn(
            view_button_html,
            dashboard_html
        )
        self.assertNotIn(
            resume_button_html,
            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)

        self.assertIn(
            resume_button_html,
            dashboard_html
        )
        self.assertNotIn(
            view_button_html,
            dashboard_html
        )

    @override_waffle_flag(COURSE_UPDATE_WAFFLE_FLAG, True)
    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=datetime(2018, 1, 1))
        self.override_waffle_switch(True)

        course = CourseFactory.create(start=self.THREE_YEARS_AGO)
        add_course_mode(course, upgrade_deadline_expired=False)
        enrollment = CourseEnrollmentFactory.create(
            user=self.user,
            course_id=course.id
        )

        # pylint: disable=unused-variable
        schedule = ScheduleFactory(start_date=self.THREE_YEARS_AGO + timedelta(days=1), enrollment=enrollment)

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

        self.assertNotIn(
            course_link_class,
            dashboard_html
        )

        self.assertIn(
            access_expired_substring,
            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]

            self.assertIn(
                expected_button,
                dashboard_html
            )
            self.assertNotIn(
                unexpected_button,
                dashboard_html
            )
Esempio n. 45
0
class OrdersViewTests(EnrollmentEventTestMixin, EcommerceApiTestMixin,
                      ModuleStoreTestCase):
    """
    Tests for the commerce orders view.
    """
    def _login(self):
        """ Log into LMS. """
        self.client.login(username=self.user.username, password='******')

    def _post_to_view(self, course_id=None):
        """
        POST to the view being tested.

        Arguments
            course_id (str) --  ID of course for which a seat should be ordered.

        :return: Response
        """
        course_id = unicode(course_id or self.course.id)
        return self.client.post(self.url, {'course_id': course_id})

    def assertResponseMessage(self, response, expected_msg):
        """ Asserts the detail field in the response's JSON body equals the expected message. """
        actual = json.loads(response.content)['detail']
        self.assertEqual(actual, expected_msg)

    def assertValidEcommerceInternalRequestErrorResponse(self, response):
        """ Asserts the response is a valid response sent when the E-Commerce API is unavailable. """
        self.assertEqual(response.status_code, 500)
        actual = json.loads(response.content)['detail']
        self.assertIn('Call to E-Commerce API failed', actual)

    def assertUserNotEnrolled(self):
        """ Asserts that the user is NOT enrolled in the course, and that an enrollment event was NOT fired. """
        self.assertFalse(
            CourseEnrollment.is_enrolled(self.user, self.course.id))
        self.assert_no_events_were_emitted()

    def setUp(self):
        super(OrdersViewTests, self).setUp()
        self.url = reverse('commerce:orders')
        self.user = UserFactory()
        self._login()

        self.course = CourseFactory.create()

        # TODO Verify this is the best method to create CourseMode objects.
        # TODO Find/create constants for the modes.
        for mode in [CourseMode.HONOR, CourseMode.VERIFIED, CourseMode.AUDIT]:
            CourseModeFactory.create(course_id=self.course.id,
                                     mode_slug=mode,
                                     mode_display_name=mode,
                                     sku=uuid4().hex.decode('ascii'))

        # Ignore events fired from UserFactory creation
        self.reset_tracker()

    def test_login_required(self):
        """
        The view should return HTTP 403 status if the user is not logged in.
        """
        self.client.logout()
        self.assertEqual(403, self._post_to_view().status_code)

    @data('delete', 'get', 'put')
    def test_post_required(self, method):
        """
        Verify that the view only responds to POST operations.
        """
        response = getattr(self.client, method)(self.url)
        self.assertEqual(405, response.status_code)

    def test_invalid_course(self):
        """
        If the course does not exist, the view should return HTTP 406.
        """
        # TODO Test inactive courses, and those not open for enrollment.
        self.assertEqual(406, self._post_to_view('aaa/bbb/ccc').status_code)

    def test_invalid_request_data(self):
        """
        If invalid data is supplied with the request, the view should return HTTP 406.
        """
        self.assertEqual(406, self.client.post(self.url, {}).status_code)
        self.assertEqual(
            406,
            self.client.post(self.url, {
                'not_course_id': ''
            }).status_code)

    def test_ecommerce_api_timeout(self):
        """
        If the call to the E-Commerce API times out, the view should log an error and return an HTTP 503 status.
        """
        with self.mock_create_order(side_effect=TimeoutError):
            response = self._post_to_view()

        self.assertValidEcommerceInternalRequestErrorResponse(response)
        self.assertUserNotEnrolled()

    def test_ecommerce_api_error(self):
        """
        If the E-Commerce API raises an error, the view should return an HTTP 503 status.
        """
        with self.mock_create_order(side_effect=ApiError):
            response = self._post_to_view()

        self.assertValidEcommerceInternalRequestErrorResponse(response)
        self.assertUserNotEnrolled()

    def _test_successful_ecommerce_api_call(self):
        """
        Verifies that the view contacts the E-Commerce API with the correct data and headers.
        """
        with self.mock_create_order():
            response = self._post_to_view()

        # Validate the response content
        msg = Messages.ORDER_COMPLETED.format(order_number=self.ORDER_NUMBER)
        self.assertResponseMessage(response, msg)

    @data(True, False)
    def test_course_with_honor_seat_sku(self, user_is_active):
        """
        If the course has a SKU, the view should get authorization from the E-Commerce API before enrolling
        the user in the course. If authorization is approved, the user should be redirected to the user dashboard.
        """

        # Set user's active flag
        self.user.is_active = user_is_active
        self.user.save()  # pylint: disable=no-member

        self._test_successful_ecommerce_api_call()

    def test_order_not_complete(self):
        with self.mock_create_order(
                return_value=(self.ORDER_NUMBER, OrderStatus.OPEN,
                              self.ECOMMERCE_API_SUCCESSFUL_BODY)):
            response = self._post_to_view()
        self.assertEqual(response.status_code, 202)
        msg = Messages.ORDER_INCOMPLETE_ENROLLED.format(
            order_number=self.ORDER_NUMBER)
        self.assertResponseMessage(response, msg)

        # TODO Eventually we should NOT be enrolling users directly from this view.
        self.assertTrue(CourseEnrollment.is_enrolled(self.user,
                                                     self.course.id))

    def _test_course_without_sku(self):
        """
        Validates the view bypasses the E-Commerce API when the course has no CourseModes with SKUs.
        """
        # Place an order
        with self.mock_create_order() as api_mock:
            response = self._post_to_view()

        # Validate the response content
        self.assertEqual(response.status_code, 200)
        msg = Messages.NO_SKU_ENROLLED.format(enrollment_mode='honor',
                                              course_id=self.course.id,
                                              username=self.user.username)
        self.assertResponseMessage(response, msg)

        # No calls made to the E-Commerce API
        self.assertFalse(api_mock.called)

    def test_course_without_sku(self):
        """
        If the course does NOT have a SKU, the user should be enrolled in the course (under the honor mode) and
        redirected to the user dashboard.
        """
        # Remove SKU from all course modes
        for course_mode in CourseMode.objects.filter(course_id=self.course.id):
            course_mode.sku = None
            course_mode.save()

        self._test_course_without_sku()

    @override_settings(ECOMMERCE_API_URL=None, ECOMMERCE_API_SIGNING_KEY=None)
    def test_ecommerce_service_not_configured(self):
        """
        If the E-Commerce Service is not configured, the view should enroll the user.
        """
        with self.mock_create_order() as api_mock:
            response = self._post_to_view()

        # Validate the response
        self.assertEqual(response.status_code, 200)
        msg = Messages.NO_ECOM_API.format(username=self.user.username,
                                          course_id=self.course.id)
        self.assertResponseMessage(response, msg)

        # Ensure that the user is not enrolled and that no calls were made to the E-Commerce API
        self.assertTrue(CourseEnrollment.is_enrolled(self.user,
                                                     self.course.id))
        self.assertFalse(api_mock.called)

    def assertProfessionalModeBypassed(self):
        """ Verifies that the view returns HTTP 406 when a course with no honor mode is encountered. """

        CourseMode.objects.filter(course_id=self.course.id).delete()
        mode = CourseMode.NO_ID_PROFESSIONAL_MODE
        CourseModeFactory.create(course_id=self.course.id,
                                 mode_slug=mode,
                                 mode_display_name=mode,
                                 sku=uuid4().hex.decode('ascii'))

        with self.mock_create_order() as api_mock:
            response = self._post_to_view()

        # The view should return an error status code
        self.assertEqual(response.status_code, 406)
        msg = Messages.NO_HONOR_MODE.format(course_id=self.course.id)
        self.assertResponseMessage(response, msg)

        # No calls should be made to the E-Commerce API.
        self.assertFalse(api_mock.called)

    def test_course_with_professional_mode_only(self):
        """ Verifies that the view behaves appropriately when the course only has a professional mode. """
        self.assertProfessionalModeBypassed()

    @override_settings(ECOMMERCE_API_URL=None, ECOMMERCE_API_SIGNING_KEY=None)
    def test_professional_mode_only_and_ecommerce_service_not_configured(self):
        """
        Verifies that the view behaves appropriately when the course only has a professional mode and
        the E-Commerce Service is not configured.
        """
        self.assertProfessionalModeBypassed()

    def test_empty_sku(self):
        """ If the CourseMode has an empty string for a SKU, the API should not be used. """
        # Set SKU to empty string for all modes.
        for course_mode in CourseMode.objects.filter(course_id=self.course.id):
            course_mode.sku = ''
            course_mode.save()

        self._test_course_without_sku()

    def test_existing_active_enrollment(self):
        """ The view should respond with HTTP 409 if the user has an existing active enrollment for the course. """

        # Enroll user in the course
        CourseEnrollment.enroll(self.user, self.course.id)
        self.assertTrue(CourseEnrollment.is_enrolled(self.user,
                                                     self.course.id))

        response = self._post_to_view()
        self.assertEqual(response.status_code, 409)
        msg = Messages.ENROLLMENT_EXISTS.format(username=self.user.username,
                                                course_id=self.course.id)
        self.assertResponseMessage(response, msg)

    def test_existing_inactive_enrollment(self):
        """
        If the user has an inactive enrollment for the course, the view should behave as if the
        user has no enrollment.
        """
        # Create an inactive enrollment
        CourseEnrollment.enroll(self.user, self.course.id)
        CourseEnrollment.unenroll(self.user, self.course.id, True)
        self.assertFalse(
            CourseEnrollment.is_enrolled(self.user, self.course.id))
        self.assertIsNotNone(
            get_enrollment(self.user.username, unicode(self.course.id)))

        self._test_successful_ecommerce_api_call()
Esempio n. 46
0
class TestCreateJWTs(AccessTokenMixin, TestCase):
    """ Tests for oauth_dispatch's jwt creation functionality. """
    def setUp(self):
        super(TestCreateJWTs, self).setUp()
        self.user = UserFactory()
        self.default_scopes = ['email', 'profile']

    def _create_client(self, oauth_adapter, client_restricted):
        """
        Creates and returns an OAuth client using the given oauth_adapter.
        Configures the client as a RestrictedApplication if client_restricted is
        True.
        """
        client = oauth_adapter.create_public_client(
            name='public app',
            user=self.user,
            redirect_uri='',
            client_id='public-client-id',
        )
        if client_restricted:
            RestrictedApplication.objects.create(application=client)
        return client

    def _create_jwt_for_token(
        self, oauth_adapter, use_asymmetric_key, client_restricted=False,
    ):
        """ Creates and returns the jwt returned by jwt_api.create_jwt_from_token. """
        client = self._create_client(oauth_adapter, client_restricted)
        expires_in = 60 * 60
        expires = now() + timedelta(seconds=expires_in)
        token_dict = dict(
            access_token=oauth_adapter.create_access_token_for_test('token', client, self.user, expires),
            expires_in=expires_in,
            scope=' '.join(self.default_scopes)
        )
        return jwt_api.create_jwt_from_token(token_dict, oauth_adapter, use_asymmetric_key=use_asymmetric_key)

    def _assert_jwt_is_valid(self, jwt_token, should_be_asymmetric_key):
        """ Asserts the given jwt_token is valid and meets expectations. """
        self.assert_valid_jwt_access_token(
            jwt_token, self.user, self.default_scopes, should_be_asymmetric_key=should_be_asymmetric_key,
        )

    @ddt.data(DOPAdapter, DOPAdapter)
    def test_create_jwt_for_token(self, oauth_adapter_cls):
        oauth_adapter = oauth_adapter_cls()
        jwt_token = self._create_jwt_for_token(oauth_adapter, use_asymmetric_key=False)
        self._assert_jwt_is_valid(jwt_token, should_be_asymmetric_key=False)

    def test_dot_create_jwt_for_token_with_asymmetric(self):
        jwt_token = self._create_jwt_for_token(DOTAdapter(), use_asymmetric_key=True)
        self._assert_jwt_is_valid(jwt_token, should_be_asymmetric_key=True)

    @ddt.data(*itertools.product(
        (True, False),
        (True, False),
    ))
    @ddt.unpack
    def test_dot_create_jwt_for_token(self, scopes_enforced, client_restricted):
        with ENFORCE_JWT_SCOPES.override(scopes_enforced):
            jwt_token = self._create_jwt_for_token(
                DOTAdapter(),
                use_asymmetric_key=None,
                client_restricted=client_restricted,
            )
            self._assert_jwt_is_valid(jwt_token, should_be_asymmetric_key=scopes_enforced and client_restricted)

    @patch('openedx.core.djangoapps.oauth_dispatch.jwt.create_role_auth_claim_for_user')
    @ddt.data(True, False)
    def test_create_jwt_for_user(self, user_email_verified, mock_create_roles):
        mock_create_roles.return_value = ['superuser', 'enterprise-admin']
        self.user.is_active = user_email_verified
        self.user.save()

        aud = 'test_aud'
        secret = 'test_secret'
        additional_claims = {'claim1_key': 'claim1_val'}
        jwt_token = jwt_api.create_jwt_for_user(self.user, secret=secret, aud=aud, additional_claims=additional_claims)
        token_payload = self.assert_valid_jwt_access_token(
            jwt_token, self.user, self.default_scopes, aud=aud, secret=secret,
        )
        self.assertDictContainsSubset(additional_claims, token_payload)
        self.assertEqual(user_email_verified, token_payload['email_verified'])
        self.assertEqual(token_payload['roles'], mock_create_roles.return_value)
Esempio n. 47
0
class EdxRestApiClientTest(TestCase):
    """ Tests to ensure the client is initialized properly. """

    TEST_USER_EMAIL = '*****@*****.**'
    TEST_CLIENT_ID = 'test-client-id'

    def setUp(self):
        super(EdxRestApiClientTest, self).setUp()

        self.user = UserFactory()
        self.user.email = self.TEST_USER_EMAIL
        self.user.save()  # pylint: disable=no-member

    @httpretty.activate
    @freeze_time('2015-7-2')
    @override_settings(JWT_ISSUER='http://example.com/oauth',
                       JWT_EXPIRATION=30)
    def test_tracking_context(self):
        """
        Ensure the tracking context is set up in the api client correctly and
        automatically.
        """

        # fake an ecommerce api request.
        httpretty.register_uri(httpretty.POST,
                               '{}/baskets/1/'.format(TEST_API_URL),
                               status=200,
                               body='{}',
                               adding_headers={'Content-Type': JSON})

        mock_tracker = mock.Mock()
        mock_tracker.resolve_context = mock.Mock(return_value={
            'client_id': self.TEST_CLIENT_ID,
            'ip': '127.0.0.1'
        })
        with mock.patch('commerce.tracker.get_tracker',
                        return_value=mock_tracker):
            ecommerce_api_client(self.user).baskets(1).post()

        # make sure the request's JWT token payload included correct tracking context values.
        actual_header = httpretty.last_request().headers['Authorization']
        expected_payload = {
            'username':
            self.user.username,
            'full_name':
            self.user.profile.name,
            'email':
            self.user.email,
            'iss':
            settings.JWT_ISSUER,
            'exp':
            datetime.datetime.utcnow() +
            datetime.timedelta(seconds=settings.JWT_EXPIRATION),
            'tracking_context': {
                'lms_user_id': self.user.id,  # pylint: disable=no-member
                'lms_client_id': self.TEST_CLIENT_ID,
                'lms_ip': '127.0.0.1',
            },
        }

        expected_header = 'JWT {}'.format(
            jwt.encode(expected_payload, TEST_API_SIGNING_KEY))
        self.assertEqual(actual_header, expected_header)

    @httpretty.activate
    def test_client_unicode(self):
        """
        The client should handle json responses properly when they contain
        unicode character data.

        Regression test for ECOM-1606.
        """
        expected_content = '{"result": "Préparatoire"}'
        httpretty.register_uri(
            httpretty.GET,
            '{}/baskets/1/order/'.format(TEST_API_URL),
            status=200,
            body=expected_content,
            adding_headers={'Content-Type': JSON},
        )
        actual_object = ecommerce_api_client(self.user).baskets(1).order.get()
        self.assertEqual(actual_object, {u"result": u"Préparatoire"})
Esempio n. 48
0
class TestRecentEnrollments(ModuleStoreTestCase, XssTestMixin):
    """
    Unit tests for getting the list of courses for a logged in user
    """
    PASSWORD = '******'

    def setUp(self):
        """
        Add a student
        """
        super(TestRecentEnrollments, self).setUp()
        self.student = UserFactory()
        self.student.set_password(self.PASSWORD)
        self.student.save()

        # Old Course
        old_course_location = locator.CourseLocator('Org0', 'Course0', 'Run0')
        __, enrollment = self._create_course_and_enrollment(old_course_location)
        enrollment.created = datetime.datetime(1900, 12, 31, 0, 0, 0, 0)
        enrollment.save()

        # New Course
        course_location = locator.CourseLocator('Org1', 'Course1', 'Run1')
        self.course, self.enrollment = self._create_course_and_enrollment(course_location)

    def _create_course_and_enrollment(self, course_location):
        """ Creates a course and associated enrollment. """
        course = CourseFactory.create(
            org=course_location.org,
            number=course_location.course,
            run=course_location.run
        )
        enrollment = CourseEnrollment.enroll(self.student, course.id)
        return course, enrollment

    def _configure_message_timeout(self, timeout):
        """Configure the amount of time the enrollment message will be displayed. """
        config = DashboardConfiguration(recent_enrollment_time_delta=timeout)
        config.save()

    def test_recently_enrolled_courses(self):
        """
        Test if the function for filtering recent enrollments works appropriately.
        """
        self._configure_message_timeout(60)

        # get courses through iterating all courses
        courses_list = list(get_course_enrollments(self.student, None, []))
        self.assertEqual(len(courses_list), 2)

        recent_course_list = _get_recently_enrolled_courses(courses_list)
        self.assertEqual(len(recent_course_list), 1)

    def test_zero_second_delta(self):
        """
        Tests that the recent enrollment list is empty if configured to zero seconds.
        """
        self._configure_message_timeout(0)
        courses_list = list(get_course_enrollments(self.student, None, []))
        self.assertEqual(len(courses_list), 2)

        recent_course_list = _get_recently_enrolled_courses(courses_list)
        self.assertEqual(len(recent_course_list), 0)

    def test_enrollments_sorted_most_recent(self):
        """
        Test that the list of newly created courses are properly sorted to show the most
        recent enrollments first.

        """
        self._configure_message_timeout(600)

        # Create a number of new enrollments and courses, and force their creation behind
        # the first enrollment
        courses = []
        for idx, seconds_past in zip(range(2, 6), [5, 10, 15, 20]):
            course_location = locator.CourseLocator(
                'Org{num}'.format(num=idx),
                'Course{num}'.format(num=idx),
                'Run{num}'.format(num=idx)
            )
            course, enrollment = self._create_course_and_enrollment(course_location)
            enrollment.created = datetime.datetime.now(UTC) - datetime.timedelta(seconds=seconds_past)
            enrollment.save()
            courses.append(course)

        courses_list = list(get_course_enrollments(self.student, None, []))
        self.assertEqual(len(courses_list), 6)

        recent_course_list = _get_recently_enrolled_courses(courses_list)
        self.assertEqual(len(recent_course_list), 5)

        self.assertEqual(recent_course_list[1].course.id, courses[0].id)
        self.assertEqual(recent_course_list[2].course.id, courses[1].id)
        self.assertEqual(recent_course_list[3].course.id, courses[2].id)
        self.assertEqual(recent_course_list[4].course.id, courses[3].id)

    def test_dashboard_rendering(self):
        """
        Tests that the dashboard renders the recent enrollment messages appropriately.
        """
        self._configure_message_timeout(600)
        self.client.login(username=self.student.username, password=self.PASSWORD)
        response = self.client.get(reverse("dashboard"))
        self.assertContains(response, "Thank you for enrolling in")

    def test_dashboard_escaped_rendering(self):
        """
        Tests that the dashboard renders the escaped recent enrollment messages appropriately.
        """
        self._configure_message_timeout(600)
        self.client.login(username=self.student.username, password=self.PASSWORD)

        # New Course
        course_location = locator.CourseLocator('TestOrg', 'TestCourse', 'TestRun')
        xss_content = "<script>alert('XSS')</script>"
        course = CourseFactory.create(
            org=course_location.org,
            number=course_location.course,
            run=course_location.run,
            display_name=xss_content
        )
        CourseEnrollment.enroll(self.student, course.id)

        response = self.client.get(reverse("dashboard"))
        self.assertContains(response, "Thank you for enrolling in")

        # Check if response is escaped
        self.assert_no_xss(response, xss_content)

    @ddt.data(
        # Register as honor in any course modes with no payment option
        ([('audit', 0), ('honor', 0)], 'honor', True),
        ([('honor', 0)], 'honor', True),
        # Register as honor in any course modes which has payment option
        ([('honor', 10)], 'honor', False),  # This is a paid course
        ([('audit', 0), ('honor', 0), ('professional', 20)], 'honor', True),
        ([('audit', 0), ('honor', 0), ('verified', 20)], 'honor', True),
        ([('audit', 0), ('honor', 0), ('verified', 20), ('professional', 20)], 'honor', True),
        # Register as audit in any course modes with no payment option
        ([('audit', 0), ('honor', 0)], 'audit', True),
        ([('audit', 0)], 'audit', True),
        ([], 'audit', True),
        # Register as audit in any course modes which has no payment option
        ([('audit', 0), ('honor', 0), ('verified', 10)], 'audit', True),
        # Register as verified in any course modes which has payment option
        ([('professional', 20)], 'professional', False),
        ([('verified', 20)], 'verified', False),
        ([('professional', 20), ('verified', 20)], 'verified', False),
        ([('audit', 0), ('honor', 0), ('verified', 20)], 'verified', False)
    )
    @ddt.unpack
    def test_donate_button(self, course_modes, enrollment_mode, show_donate):
        # Enable the enrollment success message
        self._configure_message_timeout(10000)

        # Enable donations
        DonationConfiguration(enabled=True).save()

        # Create the course mode(s)
        for mode, min_price in course_modes:
            CourseModeFactory.create(mode_slug=mode, course_id=self.course.id, min_price=min_price)

        self.enrollment.mode = enrollment_mode
        self.enrollment.save()

        # Check that the donate button is or is not displayed
        self.client.login(username=self.student.username, password=self.PASSWORD)
        response = self.client.get(reverse("dashboard"))

        if show_donate:
            self.assertContains(response, "donate-container")
        else:
            self.assertNotContains(response, "donate-container")

    def test_donate_button_honor_with_price(self):
        # Enable the enrollment success message and donations
        self._configure_message_timeout(10000)
        DonationConfiguration(enabled=True).save()

        # Create a white-label course mode
        # (honor mode with a price set)
        CourseModeFactory.create(mode_slug="honor", course_id=self.course.id, min_price=100)

        # Check that the donate button is NOT displayed
        self.client.login(username=self.student.username, password=self.PASSWORD)
        response = self.client.get(reverse("dashboard"))
        self.assertNotContains(response, "donate-container")

    @ddt.data(
        (True, False,),
        (True, True,),
        (False, False,),
        (False, True,),
    )
    @ddt.unpack
    def test_donate_button_with_enabled_site_configuration(self, enable_donation_config, enable_donation_site_config):
        # Enable the enrollment success message and donations
        self._configure_message_timeout(10000)

        # DonationConfiguration has low precedence if 'ENABLE_DONATIONS' is enable in SiteConfiguration
        DonationConfiguration(enabled=enable_donation_config).save()

        CourseModeFactory.create(mode_slug="audit", course_id=self.course.id, min_price=0)
        self.enrollment.mode = "audit"
        self.enrollment.save()
        self.client.login(username=self.student.username, password=self.PASSWORD)

        with with_site_configuration_context(configuration={'ENABLE_DONATIONS': enable_donation_site_config}):
            response = self.client.get(reverse("dashboard"))
            if enable_donation_site_config:
                self.assertContains(response, "donate-container")
            else:
                self.assertNotContains(response, "donate-container")
Esempio n. 49
0
class EmbargoMiddlewareAccessTests(UrlResetMixin, ModuleStoreTestCase):
    """Tests of embargo middleware country access rules.

    There are detailed unit tests for the rule logic in
    `test_api.py`; here, we're mainly testing the integration
    with middleware

    """
    USERNAME = '******'
    PASSWORD = '******'

    @patch.dict(settings.FEATURES, {'EMBARGO': True})
    def setUp(self):
        super(EmbargoMiddlewareAccessTests, self).setUp('embargo')
        self.user = UserFactory(username=self.USERNAME, password=self.PASSWORD)
        self.course = CourseFactory.create()
        self.client.login(username=self.USERNAME, password=self.PASSWORD)

        self.courseware_url = reverse(
            'course_root', kwargs={'course_id': unicode(self.course.id)})
        self.non_courseware_url = reverse('dashboard')

        # Clear the cache to avoid interference between tests
        django_cache.clear()
        config_cache.clear()

    @patch.dict(settings.FEATURES, {'EMBARGO': True})
    @ddt.data(True, False)
    def test_blocked(self, disable_access_check):
        with restrict_course(self.course.id, access_point='courseware', disable_access_check=disable_access_check) as redirect_url:  # pylint: disable=line-too-long
            response = self.client.get(self.courseware_url)
            if disable_access_check:
                self.assertEqual(response.status_code, 200)
            else:
                self.assertRedirects(response, redirect_url)

    @patch.dict(settings.FEATURES, {'EMBARGO': True})
    def test_allowed(self):
        # Add the course to the list of restricted courses
        # but don't create any access rules
        RestrictedCourse.objects.create(course_key=self.course.id)

        # Expect that we can access courseware
        response = self.client.get(self.courseware_url)
        self.assertEqual(response.status_code, 200)

    @patch.dict(settings.FEATURES, {'EMBARGO': True})
    def test_non_courseware_url(self):
        with restrict_course(self.course.id):
            response = self.client.get(self.non_courseware_url)
            self.assertEqual(response.status_code, 200)

    @patch.dict(settings.FEATURES, {'EMBARGO': True})
    @ddt.data(
        # request_ip, blacklist, whitelist, is_enabled, allow_access
        ('173.194.123.35', ['173.194.123.35'], [], True, False),
        ('173.194.123.35', ['173.194.0.0/16'], [], True, False),
        ('173.194.123.35', ['127.0.0.0/32', '173.194.0.0/16'
                            ], [], True, False),
        ('173.195.10.20', ['173.194.0.0/16'], [], True, True),
        ('173.194.123.35', ['173.194.0.0/16'], ['173.194.0.0/16'
                                                ], True, False),
        ('173.194.123.35', [], ['173.194.0.0/16'], True, True),
        ('192.178.2.3', [], ['173.194.0.0/16'], True, True),
        ('173.194.123.35', ['173.194.123.35'], [], False, True),
    )
    @ddt.unpack
    def test_ip_access_rules(self, request_ip, blacklist, whitelist,
                             is_enabled, allow_access):
        # Ensure that IP blocking works for anonymous users
        self.client.logout()

        # Set up the IP rules
        IPFilter.objects.create(blacklist=", ".join(blacklist),
                                whitelist=", ".join(whitelist),
                                enabled=is_enabled)

        # Check that access is enforced
        response = self.client.get("/",
                                   HTTP_X_FORWARDED_FOR=request_ip,
                                   REMOTE_ADDR=request_ip)

        if allow_access:
            self.assertEqual(response.status_code, 200)
        else:
            redirect_url = reverse('embargo_blocked_message',
                                   kwargs={
                                       'access_point': 'courseware',
                                       'message_key': 'embargo'
                                   })
            self.assertRedirects(response, redirect_url)

    @patch.dict(settings.FEATURES, {'EMBARGO': True})
    @ddt.data(('courseware', 'default'), ('courseware', 'embargo'),
              ('enrollment', 'default'), ('enrollment', 'embargo'))
    @ddt.unpack
    def test_always_allow_access_to_embargo_messages(self, access_point,
                                                     msg_key):
        # Blacklist an IP address
        IPFilter.objects.create(blacklist="192.168.10.20", enabled=True)

        url = reverse('embargo_blocked_message',
                      kwargs={
                          'access_point': access_point,
                          'message_key': msg_key
                      })
        response = self.client.get(url,
                                   HTTP_X_FORWARDED_FOR="192.168.10.20",
                                   REMOTE_ADDR="192.168.10.20")
        self.assertEqual(response.status_code, 200)

    @patch.dict(settings.FEATURES, {'EMBARGO': True})
    def test_whitelist_ip_skips_country_access_checks(self):
        # Whitelist an IP address
        IPFilter.objects.create(whitelist="192.168.10.20", enabled=True)

        # Set up country access rules so the user would
        # be restricted from the course.
        with restrict_course(self.course.id):
            # Make a request from the whitelisted IP address
            response = self.client.get(self.courseware_url,
                                       HTTP_X_FORWARDED_FOR="192.168.10.20",
                                       REMOTE_ADDR="192.168.10.20")

        # Expect that we were still able to access the page,
        # even though we would have been blocked by country
        # access rules.
        self.assertEqual(response.status_code, 200)

    @patch.dict(settings.FEATURES, {'EMBARGO': True})
    def test_always_allow_course_detail_access(self):
        """ Access to the Course Structure API's course detail endpoint should always be granted. """
        # Make the user staff so that it has permissions to access the views.
        self.user.is_staff = True
        self.user.save()  # pylint: disable=no-member

        # Blacklist an IP address
        ip_address = "192.168.10.20"
        IPFilter.objects.create(blacklist=ip_address, enabled=True)

        url = reverse('course_structure_api:v0:detail',
                      kwargs={'course_id': unicode(self.course.id)})
        response = self.client.get(url,
                                   HTTP_X_FORWARDED_FOR=ip_address,
                                   REMOTE_ADDR=ip_address)
        self.assertEqual(response.status_code, 200)

        # Test with a fully-restricted course
        with restrict_course(self.course.id):
            response = self.client.get(url,
                                       HTTP_X_FORWARDED_FOR=ip_address,
                                       REMOTE_ADDR=ip_address)
            self.assertEqual(response.status_code, 200)
class TestRecentEnrollments(ModuleStoreTestCase, XssTestMixin):
    """
    Unit tests for getting the list of courses for a logged in user
    """
    shard = 3
    PASSWORD = '******'

    def setUp(self):
        """
        Add a student
        """
        super(TestRecentEnrollments, self).setUp()
        self.student = UserFactory()
        self.student.set_password(self.PASSWORD)
        self.student.save()

        # Old Course
        old_course_location = locator.CourseLocator('Org0', 'Course0', 'Run0')
        __, enrollment = self._create_course_and_enrollment(old_course_location)
        enrollment.created = datetime.datetime(1900, 12, 31, 0, 0, 0, 0, tzinfo=UTC)
        enrollment.save()

        # New Course
        course_location = locator.CourseLocator('Org1', 'Course1', 'Run1')
        self.course, self.enrollment = self._create_course_and_enrollment(course_location)

    def _create_course_and_enrollment(self, course_location):
        """ Creates a course and associated enrollment. """
        course = CourseFactory.create(
            org=course_location.org,
            number=course_location.course,
            run=course_location.run
        )
        enrollment = CourseEnrollment.enroll(self.student, course.id)
        return course, enrollment

    def _configure_message_timeout(self, timeout):
        """Configure the amount of time the enrollment message will be displayed. """
        config = DashboardConfiguration(recent_enrollment_time_delta=timeout)
        config.save()

    def test_recently_enrolled_courses(self):
        """
        Test if the function for filtering recent enrollments works appropriately.
        """
        self._configure_message_timeout(60)

        # get courses through iterating all courses
        courses_list = list(get_course_enrollments(self.student, None, []))
        self.assertEqual(len(courses_list), 2)

        recent_course_list = _get_recently_enrolled_courses(courses_list)
        self.assertEqual(len(recent_course_list), 1)

    def test_zero_second_delta(self):
        """
        Tests that the recent enrollment list is empty if configured to zero seconds.
        """
        self._configure_message_timeout(0)
        courses_list = list(get_course_enrollments(self.student, None, []))
        self.assertEqual(len(courses_list), 2)

        recent_course_list = _get_recently_enrolled_courses(courses_list)
        self.assertEqual(len(recent_course_list), 0)

    def test_enrollments_sorted_most_recent(self):
        """
        Test that the list of newly created courses are properly sorted to show the most
        recent enrollments first.
        Also test recent enrollment message rendered appropriately for more than two courses.
        """
        self._configure_message_timeout(600)

        # Create a number of new enrollments and courses, and force their creation behind
        # the first enrollment
        courses = []
        for idx, seconds_past in zip(range(2, 6), [5, 10, 15, 20]):
            course_location = locator.CourseLocator(
                'Org{num}'.format(num=idx),
                'Course{num}'.format(num=idx),
                'Run{num}'.format(num=idx)
            )
            course, enrollment = self._create_course_and_enrollment(course_location)
            enrollment.created = now() - datetime.timedelta(seconds=seconds_past)
            enrollment.save()
            courses.append(course)

        courses_list = list(get_course_enrollments(self.student, None, []))
        self.assertEqual(len(courses_list), 6)

        recent_course_list = _get_recently_enrolled_courses(courses_list)
        self.assertEqual(len(recent_course_list), 5)

        self.assertEqual(recent_course_list[1].course.id, courses[0].id)
        self.assertEqual(recent_course_list[2].course.id, courses[1].id)
        self.assertEqual(recent_course_list[3].course.id, courses[2].id)
        self.assertEqual(recent_course_list[4].course.id, courses[3].id)

        self.client.login(username=self.student.username, password=self.PASSWORD)
        response = self.client.get(reverse("dashboard"))

        # verify recent enrollment message
        self.assertContains(
            response,
            'Thank you for enrolling in:'.format(course_name=self.course.display_name)
        )
        self.assertContains(
            response,
            ', '.join(enrollment.course.display_name for enrollment in recent_course_list)
        )

    def test_dashboard_rendering_with_single_course(self):
        """
        Tests that the dashboard renders the recent enrollment message appropriately for single course.
        """
        self._configure_message_timeout(600)
        self.client.login(username=self.student.username, password=self.PASSWORD)
        response = self.client.get(reverse("dashboard"))
        self.assertContains(
            response,
            "Thank you for enrolling in {course_name}".format(course_name=self.course.display_name)
        )

    def test_dashboard_rendering_with_two_courses(self):
        """
        Tests that the dashboard renders the recent enrollment message appropriately for two courses.
        """
        self._configure_message_timeout(600)
        course_location = locator.CourseLocator(
            'Org2',
            'Course2',
            'Run2'
        )
        course, _ = self._create_course_and_enrollment(course_location)

        self.client.login(username=self.student.username, password=self.PASSWORD)
        response = self.client.get(reverse("dashboard"))

        courses_enrollments = list(get_course_enrollments(self.student, None, []))
        courses_enrollments.sort(key=lambda x: x.created, reverse=True)
        self.assertEqual(len(courses_enrollments), 3)

        recent_course_enrollments = _get_recently_enrolled_courses(courses_enrollments)
        self.assertEqual(len(recent_course_enrollments), 2)

        self.assertContains(
            response,
            "Thank you for enrolling in:".format(course_name=self.course.display_name)
        )
        self.assertContains(
            response,
            ' and '.join(enrollment.course.display_name for enrollment in recent_course_enrollments)
        )

    def test_dashboard_escaped_rendering(self):
        """
        Tests that the dashboard renders the escaped recent enrollment messages appropriately.
        """
        self._configure_message_timeout(600)
        self.client.login(username=self.student.username, password=self.PASSWORD)

        # New Course
        course_location = locator.CourseLocator('TestOrg', 'TestCourse', 'TestRun')
        xss_content = "<script>alert('XSS')</script>"
        course = CourseFactory.create(
            org=course_location.org,
            number=course_location.course,
            run=course_location.run,
            display_name=xss_content
        )
        CourseEnrollment.enroll(self.student, course.id)

        response = self.client.get(reverse("dashboard"))
        self.assertContains(response, "Thank you for enrolling in")

        # Check if response is escaped
        self.assert_no_xss(response, xss_content)

    @ddt.data(
        # Register as honor in any course modes with no payment option
        ([('audit', 0), ('honor', 0)], 'honor', True),
        ([('honor', 0)], 'honor', True),
        # Register as honor in any course modes which has payment option
        ([('honor', 10)], 'honor', False),  # This is a paid course
        ([('audit', 0), ('honor', 0), ('professional', 20)], 'honor', True),
        ([('audit', 0), ('honor', 0), ('verified', 20)], 'honor', True),
        ([('audit', 0), ('honor', 0), ('verified', 20), ('professional', 20)], 'honor', True),
        # Register as audit in any course modes with no payment option
        ([('audit', 0), ('honor', 0)], 'audit', True),
        ([('audit', 0)], 'audit', True),
        ([], 'audit', True),
        # Register as audit in any course modes which has no payment option
        ([('audit', 0), ('honor', 0), ('verified', 10)], 'audit', True),
        # Register as verified in any course modes which has payment option
        ([('professional', 20)], 'professional', False),
        ([('verified', 20)], 'verified', False),
        ([('professional', 20), ('verified', 20)], 'verified', False),
        ([('audit', 0), ('honor', 0), ('verified', 20)], 'verified', False)
    )
    @ddt.unpack
    def test_donate_button(self, course_modes, enrollment_mode, show_donate):
        # Enable the enrollment success message
        self._configure_message_timeout(10000)

        # Enable donations
        DonationConfiguration(enabled=True).save()

        # Create the course mode(s)
        for mode, min_price in course_modes:
            CourseModeFactory.create(mode_slug=mode, course_id=self.course.id, min_price=min_price)

        self.enrollment.mode = enrollment_mode
        self.enrollment.save()

        # Check that the donate button is or is not displayed
        self.client.login(username=self.student.username, password=self.PASSWORD)
        response = self.client.get(reverse("dashboard"))

        if show_donate:
            self.assertContains(response, "donate-container")
        else:
            self.assertNotContains(response, "donate-container")

    def test_donate_button_honor_with_price(self):
        # Enable the enrollment success message and donations
        self._configure_message_timeout(10000)
        DonationConfiguration(enabled=True).save()

        # Create a white-label course mode
        # (honor mode with a price set)
        CourseModeFactory.create(mode_slug="honor", course_id=self.course.id, min_price=100)

        # Check that the donate button is NOT displayed
        self.client.login(username=self.student.username, password=self.PASSWORD)
        response = self.client.get(reverse("dashboard"))
        self.assertNotContains(response, "donate-container")

    @ddt.data(
        (True, False,),
        (True, True,),
        (False, False,),
        (False, True,),
    )
    @ddt.unpack
    def test_donate_button_with_enabled_site_configuration(self, enable_donation_config, enable_donation_site_config):
        # Enable the enrollment success message and donations
        self._configure_message_timeout(10000)

        # DonationConfiguration has low precedence if 'ENABLE_DONATIONS' is enable in SiteConfiguration
        DonationConfiguration(enabled=enable_donation_config).save()

        CourseModeFactory.create(mode_slug="audit", course_id=self.course.id, min_price=0)
        self.enrollment.mode = "audit"
        self.enrollment.save()
        self.client.login(username=self.student.username, password=self.PASSWORD)

        with with_site_configuration_context(configuration={'ENABLE_DONATIONS': enable_donation_site_config}):
            response = self.client.get(reverse("dashboard"))
            if enable_donation_site_config:
                self.assertContains(response, "donate-container")
            else:
                self.assertNotContains(response, "donate-container")
Esempio n. 51
0
class TestRecentEnrollments(ModuleStoreTestCase):
    """
    Unit tests for getting the list of courses for a logged in user
    """
    PASSWORD = '******'

    def setUp(self):
        """
        Add a student
        """
        super(TestRecentEnrollments, self).setUp()
        self.student = UserFactory()
        self.student.set_password(self.PASSWORD)
        self.student.save()

        # Old Course
        old_course_location = locator.CourseLocator('Org0', 'Course0', 'Run0')
        course, enrollment = self._create_course_and_enrollment(
            old_course_location)
        enrollment.created = datetime.datetime(1900, 12, 31, 0, 0, 0, 0)
        enrollment.save()

        # New Course
        course_location = locator.CourseLocator('Org1', 'Course1', 'Run1')
        self.course, _ = self._create_course_and_enrollment(course_location)

    def _create_course_and_enrollment(self, course_location):
        """ Creates a course and associated enrollment. """
        course = CourseFactory.create(org=course_location.org,
                                      number=course_location.course,
                                      run=course_location.run)
        enrollment = CourseEnrollment.enroll(self.student, course.id)
        return course, enrollment

    def _configure_message_timeout(self, timeout):
        """Configure the amount of time the enrollment message will be displayed. """
        config = DashboardConfiguration(recent_enrollment_time_delta=timeout)
        config.save()

    def test_recently_enrolled_courses(self):
        """
        Test if the function for filtering recent enrollments works appropriately.
        """
        self._configure_message_timeout(60)

        # get courses through iterating all courses
        courses_list = list(get_course_enrollment_pairs(
            self.student, None, []))
        self.assertEqual(len(courses_list), 2)

        recent_course_list = _get_recently_enrolled_courses(courses_list)
        self.assertEqual(len(recent_course_list), 1)

    def test_zero_second_delta(self):
        """
        Tests that the recent enrollment list is empty if configured to zero seconds.
        """
        self._configure_message_timeout(0)
        courses_list = list(get_course_enrollment_pairs(
            self.student, None, []))
        self.assertEqual(len(courses_list), 2)

        recent_course_list = _get_recently_enrolled_courses(courses_list)
        self.assertEqual(len(recent_course_list), 0)

    def test_enrollments_sorted_most_recent(self):
        """
        Test that the list of newly created courses are properly sorted to show the most
        recent enrollments first.

        """
        self._configure_message_timeout(600)

        # Create a number of new enrollments and courses, and force their creation behind
        # the first enrollment
        courses = []
        for idx, seconds_past in zip(range(2, 6), [5, 10, 15, 20]):
            course_location = locator.CourseLocator(
                'Org{num}'.format(num=idx), 'Course{num}'.format(num=idx),
                'Run{num}'.format(num=idx))
            course, enrollment = self._create_course_and_enrollment(
                course_location)
            enrollment.created = datetime.datetime.now(
                UTC) - datetime.timedelta(seconds=seconds_past)
            enrollment.save()
            courses.append(course)

        courses_list = list(get_course_enrollment_pairs(
            self.student, None, []))
        self.assertEqual(len(courses_list), 6)

        recent_course_list = _get_recently_enrolled_courses(courses_list)
        self.assertEqual(len(recent_course_list), 5)

        self.assertEqual(recent_course_list[1], courses[0])
        self.assertEqual(recent_course_list[2], courses[1])
        self.assertEqual(recent_course_list[3], courses[2])
        self.assertEqual(recent_course_list[4], courses[3])

    def test_dashboard_rendering(self):
        """
        Tests that the dashboard renders the recent enrollment messages appropriately.
        """
        self._configure_message_timeout(600)
        self.client.login(username=self.student.username,
                          password=self.PASSWORD)
        response = self.client.get(reverse("dashboard"))
        self.assertContains(response, "Thank you for enrolling in")

    @ddt.data((['audit', 'honor', 'verified'], False),
              (['professional'], False), (['verified'], False),
              (['audit'], True), (['honor'], True), ([], True))
    @ddt.unpack
    def test_donate_button(self, course_modes, show_donate):
        # Enable the enrollment success message
        self._configure_message_timeout(10000)

        # Enable donations
        DonationConfiguration(enabled=True).save()

        # Create the course mode(s)
        for mode in course_modes:
            CourseModeFactory(mode_slug=mode, course_id=self.course.id)

        # Check that the donate button is or is not displayed
        self.client.login(username=self.student.username,
                          password=self.PASSWORD)
        response = self.client.get(reverse("dashboard"))

        if show_donate:
            self.assertContains(response, "donate-container")
        else:
            self.assertNotContains(response, "donate-container")

    def test_donate_button_honor_with_price(self):
        # Enable the enrollment success message and donations
        self._configure_message_timeout(10000)
        DonationConfiguration(enabled=True).save()

        # Create a white-label course mode
        # (honor mode with a price set)
        CourseModeFactory(mode_slug="honor",
                          course_id=self.course.id,
                          min_price=100)

        # Check that the donate button is NOT displayed
        self.client.login(username=self.student.username,
                          password=self.PASSWORD)
        response = self.client.get(reverse("dashboard"))
        self.assertNotContains(response, "donate-container")
Esempio n. 52
0
class EdxRestApiClientTest(TestCase):
    """ Tests to ensure the client is initialized properly. """

    TEST_USER_EMAIL = '*****@*****.**'
    TEST_CLIENT_ID = 'test-client-id'

    def setUp(self):
        super(EdxRestApiClientTest, self).setUp()

        self.user = UserFactory()
        self.user.email = self.TEST_USER_EMAIL
        self.user.save()  # pylint: disable=no-member

    @httpretty.activate
    @freeze_time('2015-7-2')
    @override_settings(JWT_AUTH={'JWT_ISSUER': 'http://example.com/oauth', 'JWT_EXPIRATION': 30})
    def test_tracking_context(self):
        """
        Ensure the tracking context is set up in the api client correctly and
        automatically.
        """

        # fake an ecommerce api request.
        httpretty.register_uri(
            httpretty.POST,
            '{}/baskets/1/'.format(TEST_API_URL),
            status=200, body='{}',
            adding_headers={'Content-Type': JSON}
        )

        mock_tracker = mock.Mock()
        mock_tracker.resolve_context = mock.Mock(return_value={'client_id': self.TEST_CLIENT_ID, 'ip': '127.0.0.1'})
        with mock.patch('openedx.core.djangoapps.commerce.utils.tracker.get_tracker', return_value=mock_tracker):
            ecommerce_api_client(self.user).baskets(1).post()

        # make sure the request's JWT token payload included correct tracking context values.
        actual_header = httpretty.last_request().headers['Authorization']
        expected_payload = {
            'username': self.user.username,
            'full_name': self.user.profile.name,
            'email': self.user.email,
            'iss': settings.JWT_AUTH['JWT_ISSUER'],
            'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=settings.JWT_AUTH['JWT_EXPIRATION']),
            'tracking_context': {
                'lms_user_id': self.user.id,  # pylint: disable=no-member
                'lms_client_id': self.TEST_CLIENT_ID,
                'lms_ip': '127.0.0.1',
            },
        }

        expected_header = 'JWT {}'.format(jwt.encode(expected_payload, TEST_API_SIGNING_KEY))
        self.assertEqual(actual_header, expected_header)

    @httpretty.activate
    def test_client_unicode(self):
        """
        The client should handle json responses properly when they contain
        unicode character data.

        Regression test for ECOM-1606.
        """
        expected_content = '{"result": "Préparatoire"}'
        httpretty.register_uri(
            httpretty.GET,
            '{}/baskets/1/order/'.format(TEST_API_URL),
            status=200, body=expected_content,
            adding_headers={'Content-Type': JSON},
        )
        actual_object = ecommerce_api_client(self.user).baskets(1).order.get()
        self.assertEqual(actual_object, {u"result": u"Préparatoire"})

    def test_client_with_user_without_profile(self):
        """
        Verify client initialize successfully for users having no profile.
        """
        worker = User.objects.create_user(username='******', email='*****@*****.**')
        api_client = ecommerce_api_client(worker)

        self.assertEqual(api_client._store['session'].auth.__dict__['username'], worker.username)  # pylint: disable=protected-access
        self.assertIsNone(api_client._store['session'].auth.__dict__['full_name'])  # pylint: disable=protected-access
Esempio n. 53
0
class TestCourseListing(ModuleStoreTestCase, XssTestMixin):
    """
    Unit tests for getting the list of courses for a logged in user
    """
    def setUp(self):
        """
        Add a user and a course
        """
        super(TestCourseListing, self).setUp()
        # create and log in a staff user.
        # create and log in a non-staff user
        self.user = UserFactory()
        self.factory = RequestFactory()
        self.request = self.factory.get('/course')
        self.request.user = self.user
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password='******')

    def _create_course_with_access_groups(self, course_location, user=None, store=ModuleStoreEnum.Type.split):
        """
        Create dummy course with 'CourseFactory' and role (instructor/staff) groups
        """
        course = CourseFactory.create(
            org=course_location.org,
            number=course_location.course,
            run=course_location.run,
            default_store=store
        )

        if user is not None:
            for role in [CourseInstructorRole, CourseStaffRole]:
                role(course.id).add_users(user)

        return course

    def tearDown(self):
        """
        Reverse the setup
        """
        self.client.logout()
        ModuleStoreTestCase.tearDown(self)

    def test_course_listing_is_escaped(self):
        """
        Tests course listing returns escaped data.
        """
        escaping_content = "<script>alert('ESCAPE')</script>"

        # Make user staff to access course listing
        self.user.is_staff = True
        self.user.save()  # pylint: disable=no-member

        self.client = Client()
        self.client.login(username=self.user.username, password='******')

        # Change 'display_coursenumber' field and update the course.
        course = CourseFactory.create()
        course.display_coursenumber = escaping_content
        course = self.store.update_item(course, self.user.id)  # pylint: disable=no-member
        self.assertEqual(course.display_coursenumber, escaping_content)

        # Check if response is escaped
        response = self.client.get('/home')
        self.assertEqual(response.status_code, 200)
        self.assert_no_xss(response, escaping_content)

    def test_get_course_list(self):
        """
        Test getting courses with new access group format e.g. 'instructor_edx.course.run'
        """
        course_location = self.store.make_course_key('Org1', 'Course1', 'Run1')
        self._create_course_with_access_groups(course_location, self.user)

        # get courses through iterating all courses
        courses_list, __ = _accessible_courses_list(self.request)
        self.assertEqual(len(courses_list), 1)

        courses_summary_list, __ = _accessible_courses_summary_list(self.request)
        self.assertEqual(len(courses_summary_list), 1)

        # get courses by reversing group name formats
        courses_list_by_groups, __ = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list_by_groups), 1)

        # check both course lists have same courses
        self.assertEqual(courses_list, courses_list_by_groups)

    @ddt.data(
        (ModuleStoreEnum.Type.split, 'xmodule.modulestore.split_mongo.split_mongo_kvs.SplitMongoKVS'),
        (ModuleStoreEnum.Type.mongo, 'xmodule.modulestore.mongo.base.MongoKeyValueStore')
    )
    @ddt.unpack
    def test_errored_course_global_staff(self, store, path_to_patch):
        """
        Test the course list for global staff when get_course returns an ErrorDescriptor
        """
        GlobalStaff().add_users(self.user)

        with self.store.default_store(store):
            course_key = self.store.make_course_key('Org1', 'Course1', 'Run1')
            self._create_course_with_access_groups(course_key, self.user, store=store)

            with patch(path_to_patch, Mock(side_effect=Exception)):
                self.assertIsInstance(self.store.get_course(course_key), ErrorDescriptor)

                # get courses through iterating all courses
                courses_list, __ = _accessible_courses_list(self.request)
                self.assertEqual(courses_list, [])

                # get courses by reversing group name formats
                courses_list_by_groups, __ = _accessible_courses_list_from_groups(self.request)
                self.assertEqual(courses_list_by_groups, [])

    @ddt.data(
        (ModuleStoreEnum.Type.split, 5),
        (ModuleStoreEnum.Type.mongo, 3)
    )
    @ddt.unpack
    def test_staff_course_listing(self, default_store, mongo_calls):
        """
        Create courses and verify they take certain amount of mongo calls to call get_courses_accessible_to_user.
        Also verify that fetch accessible courses list for staff user returns CourseSummary instances.
        """

        # Assign & verify staff role to the user
        GlobalStaff().add_users(self.user)
        self.assertTrue(GlobalStaff().has_user(self.user))

        with self.store.default_store(default_store):
            # Create few courses
            for num in xrange(TOTAL_COURSES_COUNT):
                course_location = self.store.make_course_key('Org', 'CreatedCourse' + str(num), 'Run')
                self._create_course_with_access_groups(course_location, self.user, default_store)

        # Fetch accessible courses list & verify their count
        courses_list_by_staff, __ = get_courses_accessible_to_user(self.request)
        self.assertEqual(len(courses_list_by_staff), TOTAL_COURSES_COUNT)

        # Verify fetched accessible courses list is a list of CourseSummery instances
        self.assertTrue(all(isinstance(course, CourseSummary) for course in courses_list_by_staff))

        # Now count the db queries for staff
        with check_mongo_calls(mongo_calls):
            _accessible_courses_summary_list(self.request)

    @ddt.data(
        (ModuleStoreEnum.Type.split, 'xmodule.modulestore.split_mongo.split_mongo_kvs.SplitMongoKVS'),
        (ModuleStoreEnum.Type.mongo, 'xmodule.modulestore.mongo.base.MongoKeyValueStore')
    )
    @ddt.unpack
    def test_errored_course_regular_access(self, store, path_to_patch):
        """
        Test the course list for regular staff when get_course returns an ErrorDescriptor
        """
        GlobalStaff().remove_users(self.user)

        with self.store.default_store(store):
            CourseStaffRole(self.store.make_course_key('Non', 'Existent', 'Course')).add_users(self.user)

            course_key = self.store.make_course_key('Org1', 'Course1', 'Run1')
            self._create_course_with_access_groups(course_key, self.user, store)

            with patch(path_to_patch, Mock(side_effect=Exception)):
                self.assertIsInstance(self.store.get_course(course_key), ErrorDescriptor)

                # get courses through iterating all courses
                courses_list, __ = _accessible_courses_list(self.request)
                self.assertEqual(courses_list, [])

                # get courses by reversing group name formats
                courses_list_by_groups, __ = _accessible_courses_list_from_groups(self.request)
                self.assertEqual(courses_list_by_groups, [])
                self.assertEqual(courses_list, courses_list_by_groups)

    @ddt.data(ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.mongo)
    def test_get_course_list_with_invalid_course_location(self, store):
        """
        Test getting courses with invalid course location (course deleted from modulestore).
        """
        with self.store.default_store(store):
            course_key = self.store.make_course_key('Org', 'Course', 'Run')
            self._create_course_with_access_groups(course_key, self.user, store)

        # get courses through iterating all courses
        courses_list, __ = _accessible_courses_list(self.request)
        self.assertEqual(len(courses_list), 1)

        courses_summary_list, __ = _accessible_courses_summary_list(self.request)

        # Verify fetched accessible courses list is a list of CourseSummery instances and only one course
        # is returned
        self.assertTrue(all(isinstance(course, CourseSummary) for course in courses_summary_list))
        self.assertEqual(len(courses_summary_list), 1)

        # get courses by reversing group name formats
        courses_list_by_groups, __ = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list_by_groups), 1)

        # check course lists have same courses
        self.assertEqual(courses_list, courses_list_by_groups)

        # now delete this course and re-add user to instructor group of this course
        delete_course_and_groups(course_key, self.user.id)

        CourseInstructorRole(course_key).add_users(self.user)

        # Get courses through iterating all courses
        courses_list, __ = _accessible_courses_list(self.request)

        # Get course summaries by iterating all courses
        courses_summary_list, __ = _accessible_courses_summary_list(self.request)

        # Get courses by reversing group name formats
        courses_list_by_groups, __ = _accessible_courses_list_from_groups(self.request)

        # Test that course list returns no course
        self.assertEqual(
            [len(courses_list), len(courses_list_by_groups), len(courses_summary_list)],
            [0, 0, 0]
        )

    @ddt.data(
        (ModuleStoreEnum.Type.split, 150, 505),
        (ModuleStoreEnum.Type.mongo, USER_COURSES_COUNT, 3)
    )
    @ddt.unpack
    def test_course_listing_performance(self, store, courses_list_from_group_calls, courses_list_calls):
        """
        Create large number of courses and give access of some of these courses to the user and
        compare the time to fetch accessible courses for the user through traversing all courses and
        reversing django groups
        """
        # create list of random course numbers which will be accessible to the user
        user_course_ids = random.sample(range(TOTAL_COURSES_COUNT), USER_COURSES_COUNT)

        # create courses and assign those to the user which have their number in user_course_ids
        with self.store.default_store(store):
            for number in range(TOTAL_COURSES_COUNT):
                org = 'Org{0}'.format(number)
                course = 'Course{0}'.format(number)
                run = 'Run{0}'.format(number)
                course_location = self.store.make_course_key(org, course, run)
                if number in user_course_ids:
                    self._create_course_with_access_groups(course_location, self.user, store=store)
                else:
                    self._create_course_with_access_groups(course_location, store=store)

        # time the get courses by iterating through all courses
        with Timer() as iteration_over_courses_time_1:
            courses_list, __ = _accessible_courses_list(self.request)
        self.assertEqual(len(courses_list), USER_COURSES_COUNT)

        # time again the get courses by iterating through all courses
        with Timer() as iteration_over_courses_time_2:
            courses_list, __ = _accessible_courses_list(self.request)
        self.assertEqual(len(courses_list), USER_COURSES_COUNT)

        # time the get courses by reversing django groups
        with Timer() as iteration_over_groups_time_1:
            courses_list, __ = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list), USER_COURSES_COUNT)

        # time again the get courses by reversing django groups
        with Timer() as iteration_over_groups_time_2:
            courses_list, __ = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list), USER_COURSES_COUNT)

        # test that the time taken by getting courses through reversing django groups is lower then the time
        # taken by traversing through all courses (if accessible courses are relatively small)
        self.assertGreaterEqual(iteration_over_courses_time_1.elapsed, iteration_over_groups_time_1.elapsed)
        self.assertGreaterEqual(iteration_over_courses_time_2.elapsed, iteration_over_groups_time_2.elapsed)

        # Now count the db queries
        with check_mongo_calls(courses_list_from_group_calls):
            _accessible_courses_list_from_groups(self.request)

        with check_mongo_calls(courses_list_calls):
            _accessible_courses_list(self.request)
        # Calls:
        #    1) query old mongo
        #    2) get_more on old mongo
        #    3) query split (but no courses so no fetching of data)

    @ddt.data(ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.mongo)
    def test_course_listing_errored_deleted_courses(self, store):
        """
        Create good courses, courses that won't load, and deleted courses which still have
        roles. Test course listing.
        """
        with self.store.default_store(store):
            course_location = self.store.make_course_key('testOrg', 'testCourse', 'RunBabyRun')
            self._create_course_with_access_groups(course_location, self.user, store)

            course_location = self.store.make_course_key('testOrg', 'doomedCourse', 'RunBabyRun')
            self._create_course_with_access_groups(course_location, self.user, store)
            self.store.delete_course(course_location, self.user.id)  # pylint: disable=no-member

        courses_list, __ = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list), 1, courses_list)

    @ddt.data(OrgStaffRole('AwesomeOrg'), OrgInstructorRole('AwesomeOrg'))
    def test_course_listing_org_permissions(self, role):
        """
        Create multiple courses within the same org.  Verify that someone with org-wide permissions can access
        all of them.
        """
        org_course_one = self.store.make_course_key('AwesomeOrg', 'Course1', 'RunBabyRun')
        CourseFactory.create(
            org=org_course_one.org,
            number=org_course_one.course,
            run=org_course_one.run
        )

        org_course_two = self.store.make_course_key('AwesomeOrg', 'Course2', 'RunBabyRun')
        CourseFactory.create(
            org=org_course_two.org,
            number=org_course_two.course,
            run=org_course_two.run
        )

        # Two types of org-wide roles have edit permissions: staff and instructor.  We test both
        role.add_users(self.user)

        with self.assertRaises(AccessListFallback):
            _accessible_courses_list_from_groups(self.request)
        courses_list, __ = get_courses_accessible_to_user(self.request)

        # Verify fetched accessible courses list is a list of CourseSummery instances and test expacted
        # course count is returned
        self.assertEqual(len(courses_list), 2)
        self.assertTrue(all(isinstance(course, CourseSummary) for course in courses_list))

    def test_course_listing_with_actions_in_progress(self):
        sourse_course_key = CourseLocator('source-Org', 'source-Course', 'source-Run')

        num_courses_to_create = 3
        courses = [
            self._create_course_with_access_groups(CourseLocator('Org', 'CreatedCourse' + str(num), 'Run'), self.user)
            for num in range(num_courses_to_create)
        ]
        courses_in_progress = [
            self._create_course_with_access_groups(CourseLocator('Org', 'InProgressCourse' + str(num), 'Run'), self.user)
            for num in range(num_courses_to_create)
        ]

        # simulate initiation of course actions
        for course in courses_in_progress:
            CourseRerunState.objects.initiated(
                sourse_course_key, destination_course_key=course.id, user=self.user, display_name="test course"
            )

        # verify return values
        for method in (_accessible_courses_list_from_groups, _accessible_courses_list):
            def set_of_course_keys(course_list, key_attribute_name='id'):
                """Returns a python set of course keys by accessing the key with the given attribute name."""
                return set(getattr(c, key_attribute_name) for c in course_list)

            found_courses, unsucceeded_course_actions = method(self.request)
            self.assertSetEqual(set_of_course_keys(courses + courses_in_progress), set_of_course_keys(found_courses))
            self.assertSetEqual(
                set_of_course_keys(courses_in_progress), set_of_course_keys(unsucceeded_course_actions, 'course_key')
            )
Esempio n. 54
0
class TestCourseListing(ModuleStoreTestCase, XssTestMixin):
    """
    Unit tests for getting the list of courses for a logged in user
    """
    def setUp(self):
        """
        Add a user and a course
        """
        super(TestCourseListing, self).setUp()
        # create and log in a staff user.
        # create and log in a non-staff user
        self.user = UserFactory()
        self.factory = RequestFactory()
        self.request = self.factory.get('/course')
        self.request.user = self.user
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password='******')

    def _create_course_with_access_groups(self, course_location, user=None, store=ModuleStoreEnum.Type.split):
        """
        Create dummy course with 'CourseFactory' and role (instructor/staff) groups
        """
        course = CourseFactory.create(
            org=course_location.org,
            number=course_location.course,
            run=course_location.run,
            default_store=store
        )

        if user is not None:
            for role in [CourseInstructorRole, CourseStaffRole]:
                role(course.id).add_users(user)

        return course

    def tearDown(self):
        """
        Reverse the setup
        """
        self.client.logout()
        ModuleStoreTestCase.tearDown(self)

    def test_course_listing_is_escaped(self):
        """
        Tests course listing returns escaped data.
        """
        escaping_content = "<script>alert('ESCAPE')</script>"

        # Make user staff to access course listing
        self.user.is_staff = True
        self.user.save()  # pylint: disable=no-member

        self.client = Client()
        self.client.login(username=self.user.username, password='******')

        # Change 'display_coursenumber' field and update the course.
        course = CourseFactory.create()
        course.display_coursenumber = escaping_content
        course = self.store.update_item(course, self.user.id)  # pylint: disable=no-member
        self.assertEqual(course.display_coursenumber, escaping_content)

        # Check if response is escaped
        response = self.client.get('/home')
        self.assertEqual(response.status_code, 200)
        self.assert_no_xss(response, escaping_content)

    def test_empty_course_listing(self):
        """
        Test on empty course listing, studio name is properly displayed
        """
        message = "Are you staff on an existing {studio_name} course?".format(studio_name=settings.STUDIO_SHORT_NAME)
        response = self.client.get('/home')
        self.assertEqual(response.status_code, 200)
        self.assertIn(message, response.content)

    def test_get_course_list(self):
        """
        Test getting courses with new access group format e.g. 'instructor_edx.course.run'
        """
        course_location = self.store.make_course_key('Org1', 'Course1', 'Run1')
        self._create_course_with_access_groups(course_location, self.user)

        # get courses through iterating all courses
        courses_list, __ = _accessible_courses_list(self.request)
        self.assertEqual(len(courses_list), 1)

        courses_summary_list, __ = _accessible_courses_summary_list(self.request)
        self.assertEqual(len(courses_summary_list), 1)

        # get courses by reversing group name formats
        courses_list_by_groups, __ = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list_by_groups), 1)

        # check both course lists have same courses
        self.assertEqual(courses_list, courses_list_by_groups)

    @ddt.data(
        (ModuleStoreEnum.Type.split, 'xmodule.modulestore.split_mongo.split_mongo_kvs.SplitMongoKVS'),
        (ModuleStoreEnum.Type.mongo, 'xmodule.modulestore.mongo.base.MongoKeyValueStore')
    )
    @ddt.unpack
    def test_errored_course_global_staff(self, store, path_to_patch):
        """
        Test the course list for global staff when get_course returns an ErrorDescriptor
        """
        GlobalStaff().add_users(self.user)

        with self.store.default_store(store):
            course_key = self.store.make_course_key('Org1', 'Course1', 'Run1')
            self._create_course_with_access_groups(course_key, self.user, store=store)

            with patch(path_to_patch, Mock(side_effect=Exception)):
                self.assertIsInstance(self.store.get_course(course_key), ErrorDescriptor)

                # get courses through iterating all courses
                courses_list, __ = _accessible_courses_list(self.request)
                self.assertEqual(courses_list, [])

                # get courses by reversing group name formats
                courses_list_by_groups, __ = _accessible_courses_list_from_groups(self.request)
                self.assertEqual(courses_list_by_groups, [])

    @ddt.data(
        (ModuleStoreEnum.Type.split, 3),
        (ModuleStoreEnum.Type.mongo, 2)
    )
    @ddt.unpack
    def test_staff_course_listing(self, default_store, mongo_calls):
        """
        Create courses and verify they take certain amount of mongo calls to call get_courses_accessible_to_user.
        Also verify that fetch accessible courses list for staff user returns CourseSummary instances.
        """

        # Assign & verify staff role to the user
        GlobalStaff().add_users(self.user)
        self.assertTrue(GlobalStaff().has_user(self.user))

        with self.store.default_store(default_store):
            # Create few courses
            for num in xrange(TOTAL_COURSES_COUNT):
                course_location = self.store.make_course_key('Org', 'CreatedCourse' + str(num), 'Run')
                self._create_course_with_access_groups(course_location, self.user, default_store)

        # Fetch accessible courses list & verify their count
        courses_list_by_staff, __ = get_courses_accessible_to_user(self.request)
        self.assertEqual(len(courses_list_by_staff), TOTAL_COURSES_COUNT)

        # Verify fetched accessible courses list is a list of CourseSummery instances
        self.assertTrue(all(isinstance(course, CourseSummary) for course in courses_list_by_staff))

        # Now count the db queries for staff
        with check_mongo_calls(mongo_calls):
            _accessible_courses_summary_list(self.request)

    @ddt.data(
        (ModuleStoreEnum.Type.split, 'xmodule.modulestore.split_mongo.split_mongo_kvs.SplitMongoKVS'),
        (ModuleStoreEnum.Type.mongo, 'xmodule.modulestore.mongo.base.MongoKeyValueStore')
    )
    @ddt.unpack
    def test_errored_course_regular_access(self, store, path_to_patch):
        """
        Test the course list for regular staff when get_course returns an ErrorDescriptor
        """
        GlobalStaff().remove_users(self.user)

        with self.store.default_store(store):
            CourseStaffRole(self.store.make_course_key('Non', 'Existent', 'Course')).add_users(self.user)

            course_key = self.store.make_course_key('Org1', 'Course1', 'Run1')
            self._create_course_with_access_groups(course_key, self.user, store)

            with patch(path_to_patch, Mock(side_effect=Exception)):
                self.assertIsInstance(self.store.get_course(course_key), ErrorDescriptor)

                # get courses through iterating all courses
                courses_list, __ = _accessible_courses_list(self.request)
                self.assertEqual(courses_list, [])

                # get courses by reversing group name formats
                courses_list_by_groups, __ = _accessible_courses_list_from_groups(self.request)
                self.assertEqual(courses_list_by_groups, [])
                self.assertEqual(courses_list, courses_list_by_groups)

    @ddt.data(ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.mongo)
    def test_get_course_list_with_invalid_course_location(self, store):
        """
        Test getting courses with invalid course location (course deleted from modulestore).
        """
        with self.store.default_store(store):
            course_key = self.store.make_course_key('Org', 'Course', 'Run')
            self._create_course_with_access_groups(course_key, self.user, store)

        # get courses through iterating all courses
        courses_list, __ = _accessible_courses_list(self.request)
        self.assertEqual(len(courses_list), 1)

        courses_summary_list, __ = _accessible_courses_summary_list(self.request)

        # Verify fetched accessible courses list is a list of CourseSummery instances and only one course
        # is returned
        self.assertTrue(all(isinstance(course, CourseSummary) for course in courses_summary_list))
        self.assertEqual(len(courses_summary_list), 1)

        # get courses by reversing group name formats
        courses_list_by_groups, __ = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list_by_groups), 1)

        # check course lists have same courses
        self.assertEqual(courses_list, courses_list_by_groups)

        # now delete this course and re-add user to instructor group of this course
        delete_course_and_groups(course_key, self.user.id)

        CourseInstructorRole(course_key).add_users(self.user)

        # Get courses through iterating all courses
        courses_list, __ = _accessible_courses_list(self.request)

        # Get course summaries by iterating all courses
        courses_summary_list, __ = _accessible_courses_summary_list(self.request)

        # Get courses by reversing group name formats
        courses_list_by_groups, __ = _accessible_courses_list_from_groups(self.request)

        # Test that course list returns no course
        self.assertEqual(
            [len(courses_list), len(courses_list_by_groups), len(courses_summary_list)],
            [0, 0, 0]
        )

    @ddt.data(
        (ModuleStoreEnum.Type.split, 3, 13),
        (ModuleStoreEnum.Type.mongo, USER_COURSES_COUNT, 2)
    )
    @ddt.unpack
    def test_course_listing_performance(self, store, courses_list_from_group_calls, courses_list_calls):
        """
        Create large number of courses and give access of some of these courses to the user and
        compare the time to fetch accessible courses for the user through traversing all courses and
        reversing django groups
        """
        # create list of random course numbers which will be accessible to the user
        user_course_ids = random.sample(range(TOTAL_COURSES_COUNT), USER_COURSES_COUNT)

        # create courses and assign those to the user which have their number in user_course_ids
        with self.store.default_store(store):
            for number in range(TOTAL_COURSES_COUNT):
                org = 'Org{0}'.format(number)
                course = 'Course{0}'.format(number)
                run = 'Run{0}'.format(number)
                course_location = self.store.make_course_key(org, course, run)
                if number in user_course_ids:
                    self._create_course_with_access_groups(course_location, self.user, store=store)
                else:
                    self._create_course_with_access_groups(course_location, store=store)

        # time the get courses by iterating through all courses
        with Timer() as iteration_over_courses_time_1:
            courses_list, __ = _accessible_courses_list(self.request)
        self.assertEqual(len(courses_list), USER_COURSES_COUNT)

        # time again the get courses by iterating through all courses
        with Timer() as iteration_over_courses_time_2:
            courses_list, __ = _accessible_courses_list(self.request)
        self.assertEqual(len(courses_list), USER_COURSES_COUNT)

        # time the get courses by reversing django groups
        with Timer() as iteration_over_groups_time_1:
            courses_list, __ = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list), USER_COURSES_COUNT)

        # time again the get courses by reversing django groups
        with Timer() as iteration_over_groups_time_2:
            courses_list, __ = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list), USER_COURSES_COUNT)

        # test that the time taken by getting courses through reversing django groups is lower then the time
        # taken by traversing through all courses (if accessible courses are relatively small)
        self.assertGreaterEqual(iteration_over_courses_time_1.elapsed, iteration_over_groups_time_1.elapsed)
        self.assertGreaterEqual(iteration_over_courses_time_2.elapsed, iteration_over_groups_time_2.elapsed)

        # Now count the db queries
        with check_mongo_calls(courses_list_from_group_calls):
            _accessible_courses_list_from_groups(self.request)

        with check_mongo_calls(courses_list_calls):
            _accessible_courses_list(self.request)
        # Calls:
        #    1) query old mongo
        #    2) get_more on old mongo
        #    3) query split (but no courses so no fetching of data)

    @ddt.data(ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.mongo)
    def test_course_listing_errored_deleted_courses(self, store):
        """
        Create good courses, courses that won't load, and deleted courses which still have
        roles. Test course listing.
        """
        with self.store.default_store(store):
            course_location = self.store.make_course_key('testOrg', 'testCourse', 'RunBabyRun')
            self._create_course_with_access_groups(course_location, self.user, store)

            course_location = self.store.make_course_key('testOrg', 'doomedCourse', 'RunBabyRun')
            self._create_course_with_access_groups(course_location, self.user, store)
            self.store.delete_course(course_location, self.user.id)  # pylint: disable=no-member

        courses_list, __ = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list), 1, courses_list)

    @ddt.data(OrgStaffRole('AwesomeOrg'), OrgInstructorRole('AwesomeOrg'))
    def test_course_listing_org_permissions(self, role):
        """
        Create multiple courses within the same org.  Verify that someone with org-wide permissions can access
        all of them.
        """
        org_course_one = self.store.make_course_key('AwesomeOrg', 'Course1', 'RunBabyRun')
        CourseFactory.create(
            org=org_course_one.org,
            number=org_course_one.course,
            run=org_course_one.run
        )

        org_course_two = self.store.make_course_key('AwesomeOrg', 'Course2', 'RunBabyRun')
        CourseFactory.create(
            org=org_course_two.org,
            number=org_course_two.course,
            run=org_course_two.run
        )

        # Two types of org-wide roles have edit permissions: staff and instructor.  We test both
        role.add_users(self.user)

        with self.assertRaises(AccessListFallback):
            _accessible_courses_list_from_groups(self.request)
        courses_list, __ = get_courses_accessible_to_user(self.request)

        # Verify fetched accessible courses list is a list of CourseSummery instances and test expacted
        # course count is returned
        self.assertEqual(len(courses_list), 2)
        self.assertTrue(all(isinstance(course, CourseSummary) for course in courses_list))

    def test_course_listing_with_actions_in_progress(self):
        sourse_course_key = CourseLocator('source-Org', 'source-Course', 'source-Run')

        num_courses_to_create = 3
        courses = [
            self._create_course_with_access_groups(CourseLocator('Org', 'CreatedCourse' + str(num), 'Run'), self.user)
            for num in range(num_courses_to_create)
        ]
        courses_in_progress = [
            self._create_course_with_access_groups(CourseLocator('Org', 'InProgressCourse' + str(num), 'Run'), self.user)
            for num in range(num_courses_to_create)
        ]

        # simulate initiation of course actions
        for course in courses_in_progress:
            CourseRerunState.objects.initiated(
                sourse_course_key, destination_course_key=course.id, user=self.user, display_name="test course"
            )

        # verify return values
        for method in (_accessible_courses_list_from_groups, _accessible_courses_list):
            def set_of_course_keys(course_list, key_attribute_name='id'):
                """Returns a python set of course keys by accessing the key with the given attribute name."""
                return set(getattr(c, key_attribute_name) for c in course_list)

            found_courses, unsucceeded_course_actions = method(self.request)
            self.assertSetEqual(set_of_course_keys(courses + courses_in_progress), set_of_course_keys(found_courses))
            self.assertSetEqual(
                set_of_course_keys(courses_in_progress), set_of_course_keys(unsucceeded_course_actions, 'course_key')
            )