From 25d8085b79bb347191c18d6501d7d1b0d3aa3082 Mon Sep 17 00:00:00 2001 From: Amir Qayyum Khan Date: Thu, 21 Jul 2016 16:25:43 +0500 Subject: [PATCH 1/3] Fixed authentication classes to support Django OAUTH Toolkit --- lms/djangoapps/ccx/api/v0/tests/test_views.py | 485 +++++++++++++++++- lms/djangoapps/ccx/api/v0/views.py | 19 +- 2 files changed, 486 insertions(+), 18 deletions(-) diff --git a/lms/djangoapps/ccx/api/v0/tests/test_views.py b/lms/djangoapps/ccx/api/v0/tests/test_views.py index b28cee0a04..21d5524de0 100644 --- a/lms/djangoapps/ccx/api/v0/tests/test_views.py +++ b/lms/djangoapps/ccx/api/v0/tests/test_views.py @@ -1,13 +1,13 @@ """ Tests for the CCX REST APIs. """ -import datetime import json import math import pytz import string import urllib import urlparse +from datetime import datetime, timedelta from itertools import izip import ddt @@ -20,6 +20,7 @@ from django.core.urlresolvers import ( Resolver404 ) from nose.plugins.attrib import attr +from oauth2_provider import models as dot_models from provider.constants import CONFIDENTIAL from provider.oauth2.models import ( Client, @@ -52,6 +53,9 @@ from student.roles import ( from student.tests.factories import AdminFactory +USER_PASSWORD = 'test' + + class CcxRestApiTest(CcxTestCase, APITestCase): """ Base class with common methods to be used in the test classes of this module @@ -70,7 +74,7 @@ class CcxRestApiTest(CcxTestCase, APITestCase): self.master_course_key_str = unicode(self.master_course_key) # OAUTH2 setup # create a specific user for the application - app_user = UserFactory(username='test_app_user', email='test_app_user@openedx.org', password='test') + app_user = UserFactory(username='test_app_user', email='test_app_user@openedx.org', password=USER_PASSWORD) # add staff role to the app user CourseStaffRole(self.master_course_key).add_users(app_user) @@ -92,6 +96,25 @@ class CcxRestApiTest(CcxTestCase, APITestCase): client=self.app_client, redirect_uri='http://localhost//' ) + + # create an oauth2 provider client app entry + app_client_oauth2_provider = dot_models.Application.objects.create( + name='test client', + user=app_user, + client_type='confidential', + authorization_grant_type='authorization-code', + redirect_uris='http://localhost:8079/complete/edxorg/' + ) + # create an authorization code + auth_oauth2_provider = dot_models.AccessToken.objects.create( + user=app_user, + application=app_client_oauth2_provider, + expires=datetime.utcnow() + timedelta(weeks=1), + scope='read write', + token='16MGyP3OaQYHmpT1lK7Q6MMNAZsjwF' + ) + self.auth_header_oauth2_provider = "Bearer {0}".format(auth_oauth2_provider) + self.course.enable_ccx = True self.mstore.update_item(self.course, self.coach.id) self.auth = self.get_auth_token() @@ -175,15 +198,24 @@ class CcxListTest(CcxRestApiTest): for auth in auth_list: resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=auth) self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED) + resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=self.auth) self.assertEqual(resp.status_code, status.HTTP_200_OK) + # test for oauth2_provider + resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.assertEqual(resp.status_code, status.HTTP_200_OK) + def test_authorization_no_oauth_staff(self): """ Check authorization for staff users logged in without oauth """ # create a staff user - staff_user = UserFactory(username='test_staff_user', email='test_staff_user@openedx.org', password='test') + staff_user = UserFactory( + username='test_staff_user', + email='test_staff_user@openedx.org', + password=USER_PASSWORD + ) # add staff role to the staff user CourseStaffRole(self.master_course_key).add_users(staff_user) @@ -194,7 +226,7 @@ class CcxListTest(CcxRestApiTest): 'coach_email': self.coach.email } # the staff user can perform the request - self.client.login(username=staff_user.username, password='test') + self.client.login(username=staff_user.username, password=USER_PASSWORD) resp = self.client.get(self.list_url_master_course) self.assertEqual(resp.status_code, status.HTTP_200_OK) resp = self.client.post(self.list_url, data, format='json') @@ -206,7 +238,7 @@ class CcxListTest(CcxRestApiTest): """ # create an instructor user instructor_user = UserFactory( - username='test_instructor_user', email='test_instructor_user@openedx.org', password='test' + username='test_instructor_user', email='test_instructor_user@openedx.org', password=USER_PASSWORD ) # add instructor role to the instructor user CourseInstructorRole(self.master_course_key).add_users(instructor_user) @@ -219,7 +251,7 @@ class CcxListTest(CcxRestApiTest): } # the instructor user can perform the request - self.client.login(username=instructor_user.username, password='test') + self.client.login(username=instructor_user.username, password=USER_PASSWORD) resp = self.client.get(self.list_url_master_course) self.assertEqual(resp.status_code, status.HTTP_200_OK) resp = self.client.post(self.list_url, data, format='json') @@ -231,7 +263,7 @@ class CcxListTest(CcxRestApiTest): """ # create an coach user coach_user = UserFactory( - username='test_coach_user', email='test_coach_user@openedx.org', password='test' + username='test_coach_user', email='test_coach_user@openedx.org', password=USER_PASSWORD ) # add coach role to the coach user CourseCcxCoachRole(self.master_course_key).add_users(coach_user) @@ -243,7 +275,7 @@ class CcxListTest(CcxRestApiTest): 'coach_email': self.coach.email } # the coach user cannot perform the request: this type of user can only get her own CCX - self.client.login(username=coach_user.username, password='test') + self.client.login(username=coach_user.username, password=USER_PASSWORD) resp = self.client.get(self.list_url_master_course) self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) resp = self.client.post(self.list_url, data, format='json') @@ -260,19 +292,38 @@ class CcxListTest(CcxRestApiTest): # case with no master_course_id provided resp = self.client.get(self.list_url, {}, HTTP_AUTHORIZATION=self.auth) self.expect_error(status.HTTP_400_BAD_REQUEST, 'master_course_id_not_provided', resp) + + # test for oauth2_provider + resp = self.client.get(self.list_url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.expect_error(status.HTTP_400_BAD_REQUEST, 'master_course_id_not_provided', resp) + base_url = urlparse.urljoin(self.list_url, '?master_course_id=') # case with empty master_course_id resp = self.client.get(base_url, {}, HTTP_AUTHORIZATION=self.auth) self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_id_not_valid', resp) + + # test for oauth2_provider + resp = self.client.get(base_url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_id_not_valid', resp) + # case with invalid master_course_id url = '{0}invalid_master_course_str'.format(base_url) resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth) self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_id_not_valid', resp) + + # test for oauth2_provider + resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_id_not_valid', resp) + # case with inexistent master_course_id url = '{0}course-v1%3Aorg_foo.0%2Bcourse_bar_0%2BRun_0'.format(base_url) resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth) self.expect_error(status.HTTP_404_NOT_FOUND, 'course_id_does_not_exist', resp) + # test for oauth2_provider + resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.expect_error(status.HTTP_404_NOT_FOUND, 'course_id_does_not_exist', resp) + def test_get_list(self): """ Tests the API to get a list of CCX Courses @@ -282,6 +333,11 @@ class CcxListTest(CcxRestApiTest): self.assertIn('count', resp.data) # pylint: disable=no-member self.assertEqual(resp.data['count'], 0) # pylint: disable=no-member + # test for oauth2_provider + resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.assertIn('count', resp.data) # pylint: disable=no-member + self.assertEqual(resp.data['count'], 0) # pylint: disable=no-member + # create few ccx courses num_ccx = 10 for _ in xrange(num_ccx): @@ -293,6 +349,14 @@ class CcxListTest(CcxRestApiTest): self.assertIn('results', resp.data) # pylint: disable=no-member self.assertEqual(len(resp.data['results']), num_ccx) # pylint: disable=no-member + # test for oauth2_provider + resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.assertEqual(resp.status_code, status.HTTP_200_OK) + self.assertIn('count', resp.data) # pylint: disable=no-member + self.assertEqual(resp.data['count'], num_ccx) # pylint: disable=no-member + self.assertIn('results', resp.data) # pylint: disable=no-member + self.assertEqual(len(resp.data['results']), num_ccx) # pylint: disable=no-member + def test_get_sorted_list(self): """ Tests the API to get a sorted list of CCX Courses @@ -318,6 +382,15 @@ class CcxListTest(CcxRestApiTest): # the display_name should be sorted as "Title CCX x", "Title CCX y", "Title CCX z" for num, ccx in enumerate(resp.data['results']): # pylint: disable=no-member self.assertEqual(title_str.format(string.ascii_lowercase[-(num_ccx - num)]), ccx['display_name']) + + # test for oauth2_provider + resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.assertEqual(resp.status_code, status.HTTP_200_OK) + self.assertEqual(len(resp.data['results']), num_ccx) # pylint: disable=no-member + # the display_name should be sorted as "Title CCX x", "Title CCX y", "Title CCX z" + for num, ccx in enumerate(resp.data['results']): # pylint: disable=no-member + self.assertEqual(title_str.format(string.ascii_lowercase[-(num_ccx - num)]), ccx['display_name']) + # add sort order desc url = '{0}&order_by=display_name&sort_order=desc'.format(self.list_url_master_course) resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth) @@ -326,6 +399,13 @@ class CcxListTest(CcxRestApiTest): for num, ccx in enumerate(resp.data['results']): # pylint: disable=no-member self.assertEqual(title_str.format(string.ascii_lowercase[-(num + 1)]), ccx['display_name']) + # test for oauth2_provider + resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + # the only thing I can check is that the display name is in alphabetically reversed order + # in the same way when the field has been updated above, so with the id asc + for num, ccx in enumerate(resp.data['results']): # pylint: disable=no-member + self.assertEqual(title_str.format(string.ascii_lowercase[-(num + 1)]), ccx['display_name']) + def test_get_paginated_list(self): """ Tests the API to get a paginated list of CCX Courses @@ -345,6 +425,17 @@ class CcxListTest(CcxRestApiTest): self.assertEqual(resp.data['start'], 0) # pylint: disable=no-member self.assertIsNotNone(resp.data['next']) # pylint: disable=no-member self.assertIsNone(resp.data['previous']) # pylint: disable=no-member + + # test for oauth2_provider + resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.assertEqual(resp.status_code, status.HTTP_200_OK) + self.assertEqual(resp.data['count'], num_ccx) # pylint: disable=no-member + self.assertEqual(resp.data['num_pages'], num_pages) # pylint: disable=no-member + self.assertEqual(resp.data['current_page'], 1) # pylint: disable=no-member + self.assertEqual(resp.data['start'], 0) # pylint: disable=no-member + self.assertIsNotNone(resp.data['next']) # pylint: disable=no-member + self.assertIsNone(resp.data['previous']) # pylint: disable=no-member + # get a page in the middle url = '{0}&page=24'.format(self.list_url_master_course) resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth) @@ -355,6 +446,17 @@ class CcxListTest(CcxRestApiTest): self.assertEqual(resp.data['start'], (resp.data['current_page'] - 1) * page_size) # pylint: disable=no-member self.assertIsNotNone(resp.data['next']) # pylint: disable=no-member self.assertIsNotNone(resp.data['previous']) # pylint: disable=no-member + + # test for oauth2_provider + resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.assertEqual(resp.status_code, status.HTTP_200_OK) + self.assertEqual(resp.data['count'], num_ccx) # pylint: disable=no-member + self.assertEqual(resp.data['num_pages'], num_pages) # pylint: disable=no-member + self.assertEqual(resp.data['current_page'], 24) # pylint: disable=no-member + self.assertEqual(resp.data['start'], (resp.data['current_page'] - 1) * page_size) # pylint: disable=no-member + self.assertIsNotNone(resp.data['next']) # pylint: disable=no-member + self.assertIsNotNone(resp.data['previous']) # pylint: disable=no-member + # get last page url = '{0}&page={1}'.format(self.list_url_master_course, num_pages) resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth) @@ -365,11 +467,26 @@ class CcxListTest(CcxRestApiTest): self.assertEqual(resp.data['start'], (resp.data['current_page'] - 1) * page_size) # pylint: disable=no-member self.assertIsNone(resp.data['next']) # pylint: disable=no-member self.assertIsNotNone(resp.data['previous']) # pylint: disable=no-member + + # test for oauth2_provider + resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.assertEqual(resp.status_code, status.HTTP_200_OK) + self.assertEqual(resp.data['count'], num_ccx) # pylint: disable=no-member + self.assertEqual(resp.data['num_pages'], num_pages) # pylint: disable=no-member + self.assertEqual(resp.data['current_page'], num_pages) # pylint: disable=no-member + self.assertEqual(resp.data['start'], (resp.data['current_page'] - 1) * page_size) # pylint: disable=no-member + self.assertIsNone(resp.data['next']) # pylint: disable=no-member + self.assertIsNotNone(resp.data['previous']) # pylint: disable=no-member + # last page + 1 url = '{0}&page={1}'.format(self.list_url_master_course, num_pages + 1) resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth) self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND) + # test for oauth2_provider + resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND) + @ddt.data( ( {}, @@ -410,6 +527,15 @@ class CcxListTest(CcxRestApiTest): resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=self.auth) self.expect_error(expected_http_error, expected_error_string, resp) + # test for oauth2_provider + resp = self.client.post( + self.list_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.expect_error(expected_http_error, expected_error_string, resp) + def test_post_list_wrong_master_course_special_cases(self): """ Same as test_post_list_wrong_master_course, @@ -419,16 +545,45 @@ class CcxListTest(CcxRestApiTest): self.course.enable_ccx = False self.mstore.update_item(self.course, self.coach.id) data = {'master_course_id': self.master_course_key_str} + resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=self.auth) self.expect_error(status.HTTP_403_FORBIDDEN, 'ccx_not_enabled_for_master_course', resp) self.course.enable_ccx = True self.mstore.update_item(self.course, self.coach.id) + # case with deprecated master_course_id with mock.patch('courseware.courses.get_course_by_id', autospec=True) as mocked: mocked.return_value.id.deprecated = True resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=self.auth) + self.expect_error(status.HTTP_400_BAD_REQUEST, 'deprecated_master_course_id', resp) + # test for oauth2_provider + self.course.enable_ccx = False + self.mstore.update_item(self.course, self.coach.id) + data = {'master_course_id': self.master_course_key_str} + + resp_header_oauth2_provider = self.client.post( + self.list_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.expect_error(status.HTTP_403_FORBIDDEN, 'ccx_not_enabled_for_master_course', resp_header_oauth2_provider) + self.course.enable_ccx = True + self.mstore.update_item(self.course, self.coach.id) + + # case with deprecated master_course_id + with mock.patch('courseware.courses.get_course_by_id', autospec=True) as mocked: + mocked.return_value.id.deprecated = True + resp_header_oauth2_provider = self.client.post( + self.list_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.expect_error(status.HTTP_400_BAD_REQUEST, 'deprecated_master_course_id', resp_header_oauth2_provider) + @ddt.data( ( {}, @@ -521,6 +676,15 @@ class CcxListTest(CcxRestApiTest): resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=self.auth) self.expect_error_fields(expected_errors, resp) + # test for oauth2_provider + resp = self.client.post( + self.list_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.expect_error_fields(expected_errors, resp) + def test_post_list_coach_does_not_exist(self): """ Specific test for the case when the input data is valid but the coach does not exist. @@ -534,6 +698,15 @@ class CcxListTest(CcxRestApiTest): resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=self.auth) self.expect_error(status.HTTP_404_NOT_FOUND, 'coach_user_does_not_exist', resp) + # test for oauth2_provider + resp = self.client.post( + self.list_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.expect_error(status.HTTP_404_NOT_FOUND, 'coach_user_does_not_exist', resp) + def test_post_list_wrong_modules(self): """ Specific test for the case when the input data is valid but the @@ -552,6 +725,15 @@ class CcxListTest(CcxRestApiTest): resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=self.auth) self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_module_list_not_belonging_to_master_course', resp) + # test for oauth2_provider + resp = self.client.post( + self.list_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_module_list_not_belonging_to_master_course', resp) + def test_post_list_mixed_wrong_and_valid_modules(self): """ Specific test for the case when the input data is valid but some of @@ -568,6 +750,15 @@ class CcxListTest(CcxRestApiTest): resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=self.auth) self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_module_list_not_belonging_to_master_course', resp) + # test for oauth2_provider + resp = self.client.post( + self.list_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_module_list_not_belonging_to_master_course', resp) + def test_post_list(self): """ Test the creation of a CCX @@ -605,6 +796,37 @@ class CcxListTest(CcxRestApiTest): self.assertEqual(len(outbox), 1) self.assertIn(self.coach.email, outbox[0].recipients()) # pylint: disable=no-member + # test for oauth2_provider + resp = self.client.post( + self.list_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.assertEqual(resp.status_code, status.HTTP_201_CREATED) + # check if the response has at least the same data of the request + for key, val in data.iteritems(): + self.assertEqual(resp.data.get(key), val) # pylint: disable=no-member + self.assertIn('ccx_course_id', resp.data) # pylint: disable=no-member + # check that the new CCX actually exists + course_key = CourseKey.from_string(resp.data.get('ccx_course_id')) # pylint: disable=no-member + ccx_course = CustomCourseForEdX.objects.get(pk=course_key.ccx) + self.assertEqual( + unicode(CCXLocator.from_course_locator(ccx_course.course.id, ccx_course.id)), + resp.data.get('ccx_course_id') # pylint: disable=no-member + ) + # check that the coach user has coach role on the master course + coach_role_on_master_course = CourseCcxCoachRole(self.master_course_key) + self.assertTrue(coach_role_on_master_course.has_user(self.coach)) + # check that the coach has been enrolled in the ccx + ccx_course_object = courses.get_course_by_id(course_key) + self.assertTrue( + CourseEnrollment.objects.filter(course_id=ccx_course_object.id, user=self.coach).exists() + ) + # check that an email has been sent to the coach + self.assertEqual(len(outbox), 2) + self.assertIn(self.coach.email, outbox[0].recipients()) # pylint: disable=no-member + def test_post_list_duplicated_modules(self): """ Test the creation of a CCX, but with duplicated modules @@ -622,6 +844,16 @@ class CcxListTest(CcxRestApiTest): self.assertEqual(resp.status_code, status.HTTP_201_CREATED) self.assertEqual(resp.data.get('course_modules'), chapters) # pylint: disable=no-member + # test for oauth2_provider + resp = self.client.post( + self.list_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.assertEqual(resp.status_code, status.HTTP_201_CREATED) + self.assertEqual(resp.data.get('course_modules'), chapters) # pylint: disable=no-member + def test_post_list_staff_master_course_in_ccx(self): """ Specific test to check that the staff and instructor of the master @@ -659,6 +891,32 @@ class CcxListTest(CcxRestApiTest): for course_user, ccx_user in izip(sorted(list_instructor_master_course), sorted(list_instructor_ccx_course)): self.assertEqual(course_user, ccx_user) + # test for oauth2_provider + resp = self.client.post( + self.list_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.assertEqual(resp.status_code, status.HTTP_201_CREATED) + # check that only one email has been sent and it is to to the coach + self.assertEqual(len(outbox), 2) + self.assertIn(self.coach.email, outbox[0].recipients()) # pylint: disable=no-member + + list_staff_master_course = list_with_level(self.course, 'staff') + list_instructor_master_course = list_with_level(self.course, 'instructor') + course_key = CourseKey.from_string(resp.data.get('ccx_course_id')) # pylint: disable=no-member + with ccx_course_cm(course_key) as course_ccx: + list_staff_ccx_course = list_with_level(course_ccx, 'staff') + list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') + + self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) + for course_user, ccx_user in izip(sorted(list_staff_master_course), sorted(list_staff_ccx_course)): + self.assertEqual(course_user, ccx_user) + self.assertEqual(len(list_instructor_master_course), len(list_instructor_ccx_course)) + for course_user, ccx_user in izip(sorted(list_instructor_master_course), sorted(list_instructor_ccx_course)): + self.assertEqual(course_user, ccx_user) + @attr(shard=1) @ddt.ddt @@ -687,7 +945,7 @@ class CcxDetailTest(CcxRestApiTest): ccx.structure_json = json.dumps(self.master_course_chapters) ccx.save() - today = datetime.datetime.today() + today = datetime.today() start = today.replace(tzinfo=pytz.UTC) override_field_for_ccx(ccx, self.course, 'start', start) override_field_for_ccx(ccx, self.course, 'due', None) @@ -731,9 +989,14 @@ class CcxDetailTest(CcxRestApiTest): for auth in auth_list: resp = self.client.get(self.detail_url, {}, HTTP_AUTHORIZATION=auth) self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED) + resp = self.client.get(self.detail_url, {}, HTTP_AUTHORIZATION=self.auth) self.assertEqual(resp.status_code, status.HTTP_200_OK) + # test for oauth2_provider + resp = self.client.get(self.detail_url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.assertEqual(resp.status_code, status.HTTP_200_OK) + def test_authorization_no_oauth_staff(self): """ Check authorization for staff users logged in without oauth @@ -745,7 +1008,7 @@ class CcxDetailTest(CcxRestApiTest): data = {'display_name': 'CCX Title'} # the staff user can perform the request - self.client.login(username=staff_user.username, password='test') + self.client.login(username=staff_user.username, password=USER_PASSWORD) resp = self.client.get(self.detail_url) self.assertEqual(resp.status_code, status.HTTP_200_OK) resp = self.client.patch(self.detail_url, data, format='json') @@ -762,7 +1025,7 @@ class CcxDetailTest(CcxRestApiTest): data = {'display_name': 'CCX Title'} # the instructor user can perform the request - self.client.login(username=instructor_user.username, password='test') + self.client.login(username=instructor_user.username, password=USER_PASSWORD) resp = self.client.get(self.detail_url) self.assertEqual(resp.status_code, status.HTTP_200_OK) resp = self.client.patch(self.detail_url, data, format='json') @@ -779,7 +1042,7 @@ class CcxDetailTest(CcxRestApiTest): data = {'display_name': 'CCX Title'} # the coach user cannot perform the request: this type of user can only get her own CCX - self.client.login(username=coach_user.username, password='test') + self.client.login(username=coach_user.username, password=USER_PASSWORD) resp = self.client.get(self.detail_url) self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) resp = self.client.patch(self.detail_url, data, format='json') @@ -791,7 +1054,7 @@ class CcxDetailTest(CcxRestApiTest): """ data = {'display_name': 'CCX Title'} # the coach owner of the CCX can perform the request only if it is a get - self.client.login(username=self.coach.username, password='test') + self.client.login(username=self.coach.username, password=USER_PASSWORD) resp = self.client.get(self.detail_url) self.assertEqual(resp.status_code, status.HTTP_200_OK) resp = self.client.patch(self.detail_url, data, format='json') @@ -836,33 +1099,61 @@ class CcxDetailTest(CcxRestApiTest): # the permission class will give a 403 error because will not find the CCX resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth) self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) + + # test for oauth2_provider + resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) + # bypassing the permission class we get another kind of error with mock.patch(mock_class_str, autospec=True) as mocked_perm_class: mocked_perm_class.return_value = True resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth) self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_id_not_valid_ccx_id', resp) + # test for oauth2_provider + resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_id_not_valid_ccx_id', resp) + # use an non existing ccx id url = reverse('ccx_api:v0:ccx:detail', kwargs={'ccx_course_id': 'ccx-v1:foo.0+course_bar_0+Run_0+ccx@1'}) # the permission class will give a 403 error because will not find the CCX resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth) self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) + + # test for oauth2_provider + resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) + # bypassing the permission class we get another kind of error with mock.patch(mock_class_str, autospec=True) as mocked_perm_class: mocked_perm_class.return_value = True resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth) self.expect_error(status.HTTP_404_NOT_FOUND, 'ccx_course_id_does_not_exist', resp) + + # test for oauth2_provider + resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.expect_error(status.HTTP_404_NOT_FOUND, 'ccx_course_id_does_not_exist', resp) + # get a valid ccx key and add few 0s to get a non existing ccx for a valid course ccx_key_str = '{0}000000'.format(self.ccx_key_str) url = reverse('ccx_api:v0:ccx:detail', kwargs={'ccx_course_id': ccx_key_str}) # the permission class will give a 403 error because will not find the CCX resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth) self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) + + # test for oauth2_provider + resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) + # bypassing the permission class we get another kind of error with mock.patch(mock_class_str, autospec=True) as mocked_perm_class: mocked_perm_class.return_value = True resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth) self.expect_error(status.HTTP_404_NOT_FOUND, 'ccx_course_id_does_not_exist', resp) + # test for oauth2_provider + resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.expect_error(status.HTTP_404_NOT_FOUND, 'ccx_course_id_does_not_exist', resp) + def test_get_detail(self): """ Test for getting detail of a ccx course @@ -879,6 +1170,19 @@ class CcxDetailTest(CcxRestApiTest): self.assertEqual(resp.data.get('master_course_id'), unicode(self.ccx.course_id)) # pylint: disable=no-member self.assertItemsEqual(resp.data.get('course_modules'), self.master_course_chapters) # pylint: disable=no-member + # test for oauth2_provider + resp = self.client.get(self.detail_url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.assertEqual(resp.status_code, status.HTTP_200_OK) + self.assertEqual(resp.data.get('ccx_course_id'), self.ccx_key_str) # pylint: disable=no-member + self.assertEqual(resp.data.get('display_name'), self.ccx.display_name) # pylint: disable=no-member + self.assertEqual( + resp.data.get('max_students_allowed'), # pylint: disable=no-member + self.ccx.max_student_enrollments_allowed # pylint: disable=no-member + ) + self.assertEqual(resp.data.get('coach_email'), self.ccx.coach.email) # pylint: disable=no-member + self.assertEqual(resp.data.get('master_course_id'), unicode(self.ccx.course_id)) # pylint: disable=no-member + self.assertItemsEqual(resp.data.get('course_modules'), self.master_course_chapters) # pylint: disable=no-member + def test_delete_detail(self): """ Test for deleting a ccx course @@ -896,6 +1200,23 @@ class CcxDetailTest(CcxRestApiTest): self.assertEqual(CcxFieldOverride.objects.filter(ccx=self.ccx).count(), 0) self.assertEqual(CourseEnrollment.objects.filter(course_id=self.ccx_key).count(), 0) + def test_delete_detail_oauth2_provider(self): + """ + Test for deleting a ccx course with oauth2_provider header + """ + # check that there are overrides + self.assertGreater(CcxFieldOverride.objects.filter(ccx=self.ccx).count(), 0) + self.assertGreater(CourseEnrollment.objects.filter(course_id=self.ccx_key).count(), 0) + resp = self.client.delete(self.detail_url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) + self.assertIsNone(resp.data) # pylint: disable=no-member + # the CCX does not exist any more + with self.assertRaises(CustomCourseForEdX.DoesNotExist): + CustomCourseForEdX.objects.get(id=self.ccx.id) + # check that there are no overrides + self.assertEqual(CcxFieldOverride.objects.filter(ccx=self.ccx).count(), 0) + self.assertEqual(CourseEnrollment.objects.filter(course_id=self.ccx_key).count(), 0) + def test_patch_detail_change_master_course(self): """ Test to patch a ccx course to change a master course @@ -906,6 +1227,15 @@ class CcxDetailTest(CcxRestApiTest): resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) self.expect_error(status.HTTP_403_FORBIDDEN, 'master_course_id_change_not_allowed', resp) + # test for oauth2_provider + resp = self.client.patch( + self.detail_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.expect_error(status.HTTP_403_FORBIDDEN, 'master_course_id_change_not_allowed', resp) + @ddt.data( ( { @@ -952,6 +1282,15 @@ class CcxDetailTest(CcxRestApiTest): resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) self.expect_error_fields(expected_errors, resp) + # test for oauth2_provider + resp = self.client.patch( + self.detail_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.expect_error_fields(expected_errors, resp) + def test_empty_patch(self): """ An empty patch does not modify anything @@ -968,6 +1307,20 @@ class CcxDetailTest(CcxRestApiTest): self.assertEqual(coach_email, ccx.coach.email) self.assertEqual(ccx_structure, ccx.structure) + # test for oauth2_provider + resp = self.client.patch( + self.detail_url, + {}, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) + ccx = CustomCourseForEdX.objects.get(id=self.ccx.id) + self.assertEqual(display_name, ccx.display_name) + self.assertEqual(max_students_allowed, ccx.max_student_enrollments_allowed) + self.assertEqual(coach_email, ccx.coach.email) + self.assertEqual(ccx_structure, ccx.structure) + def test_patch_detail_coach_does_not_exist(self): """ Specific test for the case when the input data is valid but the coach does not exist. @@ -980,6 +1333,15 @@ class CcxDetailTest(CcxRestApiTest): resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) self.expect_error(status.HTTP_404_NOT_FOUND, 'coach_user_does_not_exist', resp) + # test for oauth2_provider + resp = self.client.patch( + self.detail_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.expect_error(status.HTTP_404_NOT_FOUND, 'coach_user_does_not_exist', resp) + def test_patch_detail_wrong_modules(self): """ Specific test for the case when the input data is valid but the @@ -994,6 +1356,15 @@ class CcxDetailTest(CcxRestApiTest): resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_module_list_not_belonging_to_master_course', resp) + # test for oauth2_provider + resp = self.client.patch( + self.detail_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_module_list_not_belonging_to_master_course', resp) + def test_patch_detail_mixed_wrong_and_valid_modules(self): """ Specific test for the case when the input data is valid but some of @@ -1006,6 +1377,15 @@ class CcxDetailTest(CcxRestApiTest): resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_module_list_not_belonging_to_master_course', resp) + # test for oauth2_provider + resp = self.client.patch( + self.detail_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_module_list_not_belonging_to_master_course', resp) + def test_patch_detail(self): """ Test for successful patch @@ -1036,6 +1416,30 @@ class CcxDetailTest(CcxRestApiTest): self.assertEqual(len(outbox), 1) self.assertIn(new_coach.email, outbox[0].recipients()) # pylint: disable=no-member + # test for oauth2_provider + resp = self.client.patch( + self.detail_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) + ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) + self.assertEqual(ccx_from_db.max_student_enrollments_allowed, data['max_students_allowed']) + self.assertEqual(ccx_from_db.display_name, data['display_name']) + self.assertEqual(ccx_from_db.coach.email, data['coach_email']) + # check that the coach user has coach role on the master course + coach_role_on_master_course = CourseCcxCoachRole(self.master_course_key) + self.assertTrue(coach_role_on_master_course.has_user(new_coach)) + # check that the coach has been enrolled in the ccx + ccx_course_object = courses.get_course_by_id(self.ccx_key) + self.assertTrue( + CourseEnrollment.objects.filter(course_id=ccx_course_object.id, user=new_coach).exists() + ) + # check that an email has been sent to the coach + self.assertEqual(len(outbox), 1) + self.assertIn(new_coach.email, outbox[0].recipients()) # pylint: disable=no-member + def test_patch_detail_modules(self): """ Specific test for successful patch of the course modules @@ -1046,27 +1450,82 @@ class CcxDetailTest(CcxRestApiTest): ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) self.assertItemsEqual(ccx_from_db.structure, data['course_modules']) + # test for oauth2_provider + resp = self.client.patch( + self.detail_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) + ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) + self.assertItemsEqual(ccx_from_db.structure, data['course_modules']) + data = {'course_modules': []} resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) self.assertItemsEqual(ccx_from_db.structure, []) + # test for oauth2_provider + resp = self.client.patch( + self.detail_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) + ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) + self.assertItemsEqual(ccx_from_db.structure, []) + data = {'course_modules': self.master_course_chapters} resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) self.assertItemsEqual(ccx_from_db.structure, self.master_course_chapters) + # test for oauth2_provider + resp = self.client.patch( + self.detail_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) + ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) + self.assertItemsEqual(ccx_from_db.structure, self.master_course_chapters) + data = {'course_modules': None} resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) self.assertEqual(ccx_from_db.structure, None) + # test for oauth2_provider + resp = self.client.patch( + self.detail_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) + ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) + self.assertEqual(ccx_from_db.structure, None) + chapters = self.master_course_chapters[0:1] data = {'course_modules': chapters * 3} resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) self.assertItemsEqual(ccx_from_db.structure, chapters) + + # test for oauth2_provider + resp = self.client.patch( + self.detail_url, + data, + format='json', + HTTP_AUTHORIZATION=self.auth_header_oauth2_provider + ) + self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) + ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) + self.assertItemsEqual(ccx_from_db.structure, chapters) diff --git a/lms/djangoapps/ccx/api/v0/views.py b/lms/djangoapps/ccx/api/v0/views.py index cf871fdd62..8b2c5cc93f 100644 --- a/lms/djangoapps/ccx/api/v0/views.py +++ b/lms/djangoapps/ccx/api/v0/views.py @@ -9,11 +9,9 @@ from django.contrib.auth.models import User from django.db import transaction from django.http import Http404 from rest_framework import status -from rest_framework.authentication import SessionAuthentication from rest_framework.generics import GenericAPIView from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response -from rest_framework_oauth.authentication import OAuth2Authentication from ccx_keys.locator import CCXLocator from courseware import courses @@ -26,7 +24,10 @@ from lms.djangoapps.instructor.enrollment import ( from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey, UsageKey from openedx.core.djangoapps.content.course_overviews.models import CourseOverview -from openedx.core.lib.api import permissions +from openedx.core.lib.api import ( + authentication, + permissions, +) from student.models import CourseEnrollment from student.roles import CourseCcxCoachRole @@ -363,7 +364,11 @@ class CCXListView(GenericAPIView): ] } """ - authentication_classes = (JwtAuthentication, OAuth2Authentication, SessionAuthentication,) + authentication_classes = ( + JwtAuthentication, + authentication.OAuth2AuthenticationAllowInactiveUser, + authentication.SessionAuthenticationAllowInactiveUser, + ) permission_classes = (IsAuthenticated, permissions.IsMasterCourseStaffInstructor) serializer_class = CCXCourseSerializer pagination_class = CCXAPIPagination @@ -609,7 +614,11 @@ class CCXDetailView(GenericAPIView): response is returned. """ - authentication_classes = (JwtAuthentication, OAuth2Authentication, SessionAuthentication,) + authentication_classes = ( + JwtAuthentication, + authentication.OAuth2AuthenticationAllowInactiveUser, + authentication.SessionAuthenticationAllowInactiveUser, + ) permission_classes = (IsAuthenticated, permissions.IsCourseStaffInstructor) serializer_class = CCXCourseSerializer From 164d324449d8fd31fe07cd7258a4d053f1ffc565 Mon Sep 17 00:00:00 2001 From: Amir Qayyum Khan Date: Thu, 13 Oct 2016 14:35:36 +0500 Subject: [PATCH 2/3] Refactor tests --- lms/djangoapps/ccx/api/v0/tests/test_views.py | 813 +++++++----------- 1 file changed, 307 insertions(+), 506 deletions(-) diff --git a/lms/djangoapps/ccx/api/v0/tests/test_views.py b/lms/djangoapps/ccx/api/v0/tests/test_views.py index 21d5524de0..134632b158 100644 --- a/lms/djangoapps/ccx/api/v0/tests/test_views.py +++ b/lms/djangoapps/ccx/api/v0/tests/test_views.py @@ -54,6 +54,7 @@ from student.tests.factories import AdminFactory USER_PASSWORD = 'test' +AUTH_ATTRS = ('auth', 'auth_header_oauth2_provider') class CcxRestApiTest(CcxTestCase, APITestCase): @@ -183,7 +184,8 @@ class CcxListTest(CcxRestApiTest): '?master_course_id={0}'.format(urllib.quote_plus(self.master_course_key_str)) ) - def test_authorization(self): + @ddt.data(*AUTH_ATTRS) + def test_authorization(self, auth_attr): """ Test that only the right token is authorized """ @@ -199,12 +201,9 @@ class CcxListTest(CcxRestApiTest): resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=auth) self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED) - resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=self.auth) + resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_200_OK) - # test for oauth2_provider - resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) - self.assertEqual(resp.status_code, status.HTTP_200_OK) def test_authorization_no_oauth_staff(self): """ @@ -281,7 +280,8 @@ class CcxListTest(CcxRestApiTest): resp = self.client.post(self.list_url, data, format='json') self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) - def test_get_list_wrong_master_course(self): + @ddt.data(*AUTH_ATTRS) + def test_get_list_wrong_master_course(self, auth_attr): """ Test for various get requests with wrong master course string """ @@ -290,51 +290,31 @@ class CcxListTest(CcxRestApiTest): with mock.patch(mock_class_str, autospec=True) as mocked_perm_class: mocked_perm_class.return_value = True # case with no master_course_id provided - resp = self.client.get(self.list_url, {}, HTTP_AUTHORIZATION=self.auth) - self.expect_error(status.HTTP_400_BAD_REQUEST, 'master_course_id_not_provided', resp) - - # test for oauth2_provider - resp = self.client.get(self.list_url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + resp = self.client.get(self.list_url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.expect_error(status.HTTP_400_BAD_REQUEST, 'master_course_id_not_provided', resp) base_url = urlparse.urljoin(self.list_url, '?master_course_id=') # case with empty master_course_id - resp = self.client.get(base_url, {}, HTTP_AUTHORIZATION=self.auth) - self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_id_not_valid', resp) - - # test for oauth2_provider - resp = self.client.get(base_url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + resp = self.client.get(base_url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_id_not_valid', resp) # case with invalid master_course_id url = '{0}invalid_master_course_str'.format(base_url) - resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth) - self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_id_not_valid', resp) - - # test for oauth2_provider - resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + resp = self.client.get(url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_id_not_valid', resp) # case with inexistent master_course_id url = '{0}course-v1%3Aorg_foo.0%2Bcourse_bar_0%2BRun_0'.format(base_url) - resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth) + resp = self.client.get(url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.expect_error(status.HTTP_404_NOT_FOUND, 'course_id_does_not_exist', resp) - # test for oauth2_provider - resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) - self.expect_error(status.HTTP_404_NOT_FOUND, 'course_id_does_not_exist', resp) - - def test_get_list(self): + @ddt.data(*AUTH_ATTRS) + def test_get_list(self, auth_attr): """ Tests the API to get a list of CCX Courses """ # there are no CCX courses - resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=self.auth) - self.assertIn('count', resp.data) # pylint: disable=no-member - self.assertEqual(resp.data['count'], 0) # pylint: disable=no-member - - # test for oauth2_provider - resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertIn('count', resp.data) # pylint: disable=no-member self.assertEqual(resp.data['count'], 0) # pylint: disable=no-member @@ -342,22 +322,15 @@ class CcxListTest(CcxRestApiTest): num_ccx = 10 for _ in xrange(num_ccx): self.make_ccx() - resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=self.auth) + resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertIn('count', resp.data) # pylint: disable=no-member self.assertEqual(resp.data['count'], num_ccx) # pylint: disable=no-member self.assertIn('results', resp.data) # pylint: disable=no-member self.assertEqual(len(resp.data['results']), num_ccx) # pylint: disable=no-member - # test for oauth2_provider - resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) - self.assertEqual(resp.status_code, status.HTTP_200_OK) - self.assertIn('count', resp.data) # pylint: disable=no-member - self.assertEqual(resp.data['count'], num_ccx) # pylint: disable=no-member - self.assertIn('results', resp.data) # pylint: disable=no-member - self.assertEqual(len(resp.data['results']), num_ccx) # pylint: disable=no-member - - def test_get_sorted_list(self): + @ddt.data(*AUTH_ATTRS) + def test_get_sorted_list(self, auth_attr): """ Tests the API to get a sorted list of CCX Courses """ @@ -376,15 +349,7 @@ class CcxListTest(CcxRestApiTest): # sort by display name url = '{0}&order_by=display_name'.format(self.list_url_master_course) - resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth) - self.assertEqual(resp.status_code, status.HTTP_200_OK) - self.assertEqual(len(resp.data['results']), num_ccx) # pylint: disable=no-member - # the display_name should be sorted as "Title CCX x", "Title CCX y", "Title CCX z" - for num, ccx in enumerate(resp.data['results']): # pylint: disable=no-member - self.assertEqual(title_str.format(string.ascii_lowercase[-(num_ccx - num)]), ccx['display_name']) - - # test for oauth2_provider - resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + resp = self.client.get(url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertEqual(len(resp.data['results']), num_ccx) # pylint: disable=no-member # the display_name should be sorted as "Title CCX x", "Title CCX y", "Title CCX z" @@ -393,20 +358,14 @@ class CcxListTest(CcxRestApiTest): # add sort order desc url = '{0}&order_by=display_name&sort_order=desc'.format(self.list_url_master_course) - resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth) + resp = self.client.get(url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) # the only thing I can check is that the display name is in alphabetically reversed order # in the same way when the field has been updated above, so with the id asc for num, ccx in enumerate(resp.data['results']): # pylint: disable=no-member self.assertEqual(title_str.format(string.ascii_lowercase[-(num + 1)]), ccx['display_name']) - # test for oauth2_provider - resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) - # the only thing I can check is that the display name is in alphabetically reversed order - # in the same way when the field has been updated above, so with the id asc - for num, ccx in enumerate(resp.data['results']): # pylint: disable=no-member - self.assertEqual(title_str.format(string.ascii_lowercase[-(num + 1)]), ccx['display_name']) - - def test_get_paginated_list(self): + @ddt.data(*AUTH_ATTRS) + def test_get_paginated_list(self, auth_attr): """ Tests the API to get a paginated list of CCX Courses """ @@ -417,17 +376,7 @@ class CcxListTest(CcxRestApiTest): page_size = settings.REST_FRAMEWORK.get('PAGE_SIZE', 10) num_pages = int(math.ceil(num_ccx / float(page_size))) # get first page - resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=self.auth) - self.assertEqual(resp.status_code, status.HTTP_200_OK) - self.assertEqual(resp.data['count'], num_ccx) # pylint: disable=no-member - self.assertEqual(resp.data['num_pages'], num_pages) # pylint: disable=no-member - self.assertEqual(resp.data['current_page'], 1) # pylint: disable=no-member - self.assertEqual(resp.data['start'], 0) # pylint: disable=no-member - self.assertIsNotNone(resp.data['next']) # pylint: disable=no-member - self.assertIsNone(resp.data['previous']) # pylint: disable=no-member - - # test for oauth2_provider - resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertEqual(resp.data['count'], num_ccx) # pylint: disable=no-member self.assertEqual(resp.data['num_pages'], num_pages) # pylint: disable=no-member @@ -438,17 +387,7 @@ class CcxListTest(CcxRestApiTest): # get a page in the middle url = '{0}&page=24'.format(self.list_url_master_course) - resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth) - self.assertEqual(resp.status_code, status.HTTP_200_OK) - self.assertEqual(resp.data['count'], num_ccx) # pylint: disable=no-member - self.assertEqual(resp.data['num_pages'], num_pages) # pylint: disable=no-member - self.assertEqual(resp.data['current_page'], 24) # pylint: disable=no-member - self.assertEqual(resp.data['start'], (resp.data['current_page'] - 1) * page_size) # pylint: disable=no-member - self.assertIsNotNone(resp.data['next']) # pylint: disable=no-member - self.assertIsNotNone(resp.data['previous']) # pylint: disable=no-member - - # test for oauth2_provider - resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + resp = self.client.get(url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertEqual(resp.data['count'], num_ccx) # pylint: disable=no-member self.assertEqual(resp.data['num_pages'], num_pages) # pylint: disable=no-member @@ -459,17 +398,7 @@ class CcxListTest(CcxRestApiTest): # get last page url = '{0}&page={1}'.format(self.list_url_master_course, num_pages) - resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth) - self.assertEqual(resp.status_code, status.HTTP_200_OK) - self.assertEqual(resp.data['count'], num_ccx) # pylint: disable=no-member - self.assertEqual(resp.data['num_pages'], num_pages) # pylint: disable=no-member - self.assertEqual(resp.data['current_page'], num_pages) # pylint: disable=no-member - self.assertEqual(resp.data['start'], (resp.data['current_page'] - 1) * page_size) # pylint: disable=no-member - self.assertIsNone(resp.data['next']) # pylint: disable=no-member - self.assertIsNotNone(resp.data['previous']) # pylint: disable=no-member - - # test for oauth2_provider - resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + resp = self.client.get(url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertEqual(resp.data['count'], num_ccx) # pylint: disable=no-member self.assertEqual(resp.data['num_pages'], num_pages) # pylint: disable=no-member @@ -480,42 +409,73 @@ class CcxListTest(CcxRestApiTest): # last page + 1 url = '{0}&page={1}'.format(self.list_url_master_course, num_pages + 1) - resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth) - self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND) - - # test for oauth2_provider - resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + resp = self.client.get(url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND) @ddt.data( ( {}, status.HTTP_400_BAD_REQUEST, - 'master_course_id_not_provided' + 'master_course_id_not_provided', + 'auth_header_oauth2_provider' + ), + ( + {}, + status.HTTP_400_BAD_REQUEST, + 'master_course_id_not_provided', + 'auth' ), ( {'master_course_id': None}, status.HTTP_400_BAD_REQUEST, - 'master_course_id_not_provided' + 'master_course_id_not_provided', + 'auth_header_oauth2_provider' + ), + ( + {'master_course_id': None}, + status.HTTP_400_BAD_REQUEST, + 'master_course_id_not_provided', + 'auth' ), ( {'master_course_id': ''}, status.HTTP_400_BAD_REQUEST, - 'course_id_not_valid' + 'course_id_not_valid', + 'auth_header_oauth2_provider' + ), + ( + {'master_course_id': ''}, + status.HTTP_400_BAD_REQUEST, + 'course_id_not_valid', + 'auth' ), ( {'master_course_id': 'invalid_master_course_str'}, status.HTTP_400_BAD_REQUEST, - 'course_id_not_valid' + 'course_id_not_valid', + 'auth' + ), + ( + {'master_course_id': 'invalid_master_course_str'}, + status.HTTP_400_BAD_REQUEST, + 'course_id_not_valid', + 'auth_header_oauth2_provider' ), ( {'master_course_id': 'course-v1:org_foo.0+course_bar_0+Run_0'}, status.HTTP_404_NOT_FOUND, - 'course_id_does_not_exist' + 'course_id_does_not_exist', + 'auth' + ), + ( + {'master_course_id': 'course-v1:org_foo.0+course_bar_0+Run_0'}, + status.HTTP_404_NOT_FOUND, + 'course_id_does_not_exist', + 'auth_header_oauth2_provider' ), ) @ddt.unpack - def test_post_list_wrong_master_course(self, data, expected_http_error, expected_error_string): + def test_post_list_wrong_master_course(self, data, expected_http_error, expected_error_string, auth_attr): """ Test for various post requests with wrong master course string """ @@ -524,19 +484,11 @@ class CcxListTest(CcxRestApiTest): with mock.patch(mock_class_str, autospec=True) as mocked_perm_class: mocked_perm_class.return_value = True # case with no master_course_id provided - resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=self.auth) + resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.expect_error(expected_http_error, expected_error_string, resp) - # test for oauth2_provider - resp = self.client.post( - self.list_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) - self.expect_error(expected_http_error, expected_error_string, resp) - - def test_post_list_wrong_master_course_special_cases(self): + @ddt.data(*AUTH_ATTRS) + def test_post_list_wrong_master_course_special_cases(self, auth_attr): """ Same as test_post_list_wrong_master_course, but different ways to test the wrong master_course_id @@ -546,7 +498,7 @@ class CcxListTest(CcxRestApiTest): self.mstore.update_item(self.course, self.coach.id) data = {'master_course_id': self.master_course_key_str} - resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=self.auth) + resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.expect_error(status.HTTP_403_FORBIDDEN, 'ccx_not_enabled_for_master_course', resp) self.course.enable_ccx = True self.mstore.update_item(self.course, self.coach.id) @@ -554,36 +506,10 @@ class CcxListTest(CcxRestApiTest): # case with deprecated master_course_id with mock.patch('courseware.courses.get_course_by_id', autospec=True) as mocked: mocked.return_value.id.deprecated = True - resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=self.auth) + resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.expect_error(status.HTTP_400_BAD_REQUEST, 'deprecated_master_course_id', resp) - # test for oauth2_provider - self.course.enable_ccx = False - self.mstore.update_item(self.course, self.coach.id) - data = {'master_course_id': self.master_course_key_str} - - resp_header_oauth2_provider = self.client.post( - self.list_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) - self.expect_error(status.HTTP_403_FORBIDDEN, 'ccx_not_enabled_for_master_course', resp_header_oauth2_provider) - self.course.enable_ccx = True - self.mstore.update_item(self.course, self.coach.id) - - # case with deprecated master_course_id - with mock.patch('courseware.courses.get_course_by_id', autospec=True) as mocked: - mocked.return_value.id.deprecated = True - resp_header_oauth2_provider = self.client.post( - self.list_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) - self.expect_error(status.HTTP_400_BAD_REQUEST, 'deprecated_master_course_id', resp_header_oauth2_provider) - @ddt.data( ( {}, @@ -591,7 +517,17 @@ class CcxListTest(CcxRestApiTest): 'max_students_allowed': 'missing_field_max_students_allowed', 'display_name': 'missing_field_display_name', 'coach_email': 'missing_field_coach_email' - } + }, + 'auth' + ), + ( + {}, + { + 'max_students_allowed': 'missing_field_max_students_allowed', + 'display_name': 'missing_field_display_name', + 'coach_email': 'missing_field_coach_email' + }, + 'auth_header_oauth2_provider' ), ( { @@ -600,7 +536,18 @@ class CcxListTest(CcxRestApiTest): }, { 'coach_email': 'missing_field_coach_email' - } + }, + 'auth' + ), + ( + { + 'max_students_allowed': 10, + 'display_name': 'CCX Title' + }, + { + 'coach_email': 'missing_field_coach_email' + }, + 'auth_header_oauth2_provider' ), ( { @@ -612,7 +559,21 @@ class CcxListTest(CcxRestApiTest): 'max_students_allowed': 'null_field_max_students_allowed', 'display_name': 'null_field_display_name', 'coach_email': 'null_field_coach_email' - } + }, + 'auth' + ), + ( + { + 'max_students_allowed': None, + 'display_name': None, + 'coach_email': None + }, + { + 'max_students_allowed': 'null_field_max_students_allowed', + 'display_name': 'null_field_display_name', + 'coach_email': 'null_field_coach_email' + }, + 'auth_header_oauth2_provider' ), ( { @@ -620,7 +581,17 @@ class CcxListTest(CcxRestApiTest): 'display_name': 'CCX Title', 'coach_email': 'this is not an email@test.com' }, - {'coach_email': 'invalid_coach_email'} + {'coach_email': 'invalid_coach_email'}, + 'auth' + ), + ( + { + 'max_students_allowed': 10, + 'display_name': 'CCX Title', + 'coach_email': 'this is not an email@test.com' + }, + {'coach_email': 'invalid_coach_email'}, + 'auth_header_oauth2_provider' ), ( { @@ -628,7 +599,17 @@ class CcxListTest(CcxRestApiTest): 'display_name': '', 'coach_email': 'email@test.com' }, - {'display_name': 'invalid_display_name'} + {'display_name': 'invalid_display_name'}, + 'auth' + ), + ( + { + 'max_students_allowed': 10, + 'display_name': '', + 'coach_email': 'email@test.com' + }, + {'display_name': 'invalid_display_name'}, + 'auth_header_oauth2_provider' ), ( { @@ -636,7 +617,17 @@ class CcxListTest(CcxRestApiTest): 'display_name': 'CCX Title', 'coach_email': 'email@test.com' }, - {'max_students_allowed': 'invalid_max_students_allowed'} + {'max_students_allowed': 'invalid_max_students_allowed'}, + 'auth' + ), + ( + { + 'max_students_allowed': 'a', + 'display_name': 'CCX Title', + 'coach_email': 'email@test.com' + }, + {'max_students_allowed': 'invalid_max_students_allowed'}, + 'auth_header_oauth2_provider' ), ( { @@ -645,7 +636,18 @@ class CcxListTest(CcxRestApiTest): 'coach_email': 'email@test.com', 'course_modules': {'foo': 'bar'} }, - {'course_modules': 'invalid_course_module_list'} + {'course_modules': 'invalid_course_module_list'}, + 'auth' + ), + ( + { + 'max_students_allowed': 10, + 'display_name': 'CCX Title', + 'coach_email': 'email@test.com', + 'course_modules': {'foo': 'bar'} + }, + {'course_modules': 'invalid_course_module_list'}, + 'auth_header_oauth2_provider' ), ( { @@ -654,7 +656,18 @@ class CcxListTest(CcxRestApiTest): 'coach_email': 'email@test.com', 'course_modules': 'block-v1:org.0+course_0+Run_0+type@chapter+block@chapter_1' }, - {'course_modules': 'invalid_course_module_list'} + {'course_modules': 'invalid_course_module_list'}, + 'auth' + ), + ( + { + 'max_students_allowed': 10, + 'display_name': 'CCX Title', + 'coach_email': 'email@test.com', + 'course_modules': 'block-v1:org.0+course_0+Run_0+type@chapter+block@chapter_1' + }, + {'course_modules': 'invalid_course_module_list'}, + 'auth_header_oauth2_provider' ), ( { @@ -663,29 +676,32 @@ class CcxListTest(CcxRestApiTest): 'coach_email': 'email@test.com', 'course_modules': ['foo', 'bar'] }, - {'course_modules': 'invalid_course_module_keys'} + {'course_modules': 'invalid_course_module_keys'}, + 'auth' + ), + ( + { + 'max_students_allowed': 10, + 'display_name': 'CCX Title', + 'coach_email': 'email@test.com', + 'course_modules': ['foo', 'bar'] + }, + {'course_modules': 'invalid_course_module_keys'}, + 'auth_header_oauth2_provider' ), ) @ddt.unpack - def test_post_list_wrong_input_data(self, data, expected_errors): + def test_post_list_wrong_input_data(self, data, expected_errors, auth_attr): """ Test for various post requests with wrong input data """ # add the master_course_key_str to the request data data['master_course_id'] = self.master_course_key_str - resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=self.auth) + resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.expect_error_fields(expected_errors, resp) - # test for oauth2_provider - resp = self.client.post( - self.list_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) - self.expect_error_fields(expected_errors, resp) - - def test_post_list_coach_does_not_exist(self): + @ddt.data(*AUTH_ATTRS) + def test_post_list_coach_does_not_exist(self, auth_attr): """ Specific test for the case when the input data is valid but the coach does not exist. """ @@ -695,19 +711,11 @@ class CcxListTest(CcxRestApiTest): 'display_name': 'CCX Title', 'coach_email': 'inexisting_email@test.com' } - resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=self.auth) + resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.expect_error(status.HTTP_404_NOT_FOUND, 'coach_user_does_not_exist', resp) - # test for oauth2_provider - resp = self.client.post( - self.list_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) - self.expect_error(status.HTTP_404_NOT_FOUND, 'coach_user_does_not_exist', resp) - - def test_post_list_wrong_modules(self): + @ddt.data(*AUTH_ATTRS) + def test_post_list_wrong_modules(self, auth_attr): """ Specific test for the case when the input data is valid but the course modules do not belong to the master course @@ -722,19 +730,11 @@ class CcxListTest(CcxRestApiTest): 'block-v1:org.0+course_0+Run_0+type@chapter+block@chapter_bar' ] } - resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=self.auth) + resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_module_list_not_belonging_to_master_course', resp) - # test for oauth2_provider - resp = self.client.post( - self.list_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) - self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_module_list_not_belonging_to_master_course', resp) - - def test_post_list_mixed_wrong_and_valid_modules(self): + @ddt.data(*AUTH_ATTRS) + def test_post_list_mixed_wrong_and_valid_modules(self, auth_attr): """ Specific test for the case when the input data is valid but some of the course modules do not belong to the master course @@ -747,19 +747,11 @@ class CcxListTest(CcxRestApiTest): 'coach_email': self.coach.email, 'course_modules': modules } - resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=self.auth) + resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_module_list_not_belonging_to_master_course', resp) - # test for oauth2_provider - resp = self.client.post( - self.list_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) - self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_module_list_not_belonging_to_master_course', resp) - - def test_post_list(self): + @ddt.data(*AUTH_ATTRS) + def test_post_list(self, auth_attr): """ Test the creation of a CCX """ @@ -771,7 +763,7 @@ class CcxListTest(CcxRestApiTest): 'coach_email': self.coach.email, 'course_modules': self.master_course_chapters[0:1] } - resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=self.auth) + resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_201_CREATED) # check if the response has at least the same data of the request for key, val in data.iteritems(): @@ -796,38 +788,8 @@ class CcxListTest(CcxRestApiTest): self.assertEqual(len(outbox), 1) self.assertIn(self.coach.email, outbox[0].recipients()) # pylint: disable=no-member - # test for oauth2_provider - resp = self.client.post( - self.list_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) - self.assertEqual(resp.status_code, status.HTTP_201_CREATED) - # check if the response has at least the same data of the request - for key, val in data.iteritems(): - self.assertEqual(resp.data.get(key), val) # pylint: disable=no-member - self.assertIn('ccx_course_id', resp.data) # pylint: disable=no-member - # check that the new CCX actually exists - course_key = CourseKey.from_string(resp.data.get('ccx_course_id')) # pylint: disable=no-member - ccx_course = CustomCourseForEdX.objects.get(pk=course_key.ccx) - self.assertEqual( - unicode(CCXLocator.from_course_locator(ccx_course.course.id, ccx_course.id)), - resp.data.get('ccx_course_id') # pylint: disable=no-member - ) - # check that the coach user has coach role on the master course - coach_role_on_master_course = CourseCcxCoachRole(self.master_course_key) - self.assertTrue(coach_role_on_master_course.has_user(self.coach)) - # check that the coach has been enrolled in the ccx - ccx_course_object = courses.get_course_by_id(course_key) - self.assertTrue( - CourseEnrollment.objects.filter(course_id=ccx_course_object.id, user=self.coach).exists() - ) - # check that an email has been sent to the coach - self.assertEqual(len(outbox), 2) - self.assertIn(self.coach.email, outbox[0].recipients()) # pylint: disable=no-member - - def test_post_list_duplicated_modules(self): + @ddt.data(*AUTH_ATTRS) + def test_post_list_duplicated_modules(self, auth_attr): """ Test the creation of a CCX, but with duplicated modules """ @@ -840,21 +802,12 @@ class CcxListTest(CcxRestApiTest): 'coach_email': self.coach.email, 'course_modules': duplicated_chapters } - resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=self.auth) + resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_201_CREATED) self.assertEqual(resp.data.get('course_modules'), chapters) # pylint: disable=no-member - # test for oauth2_provider - resp = self.client.post( - self.list_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) - self.assertEqual(resp.status_code, status.HTTP_201_CREATED) - self.assertEqual(resp.data.get('course_modules'), chapters) # pylint: disable=no-member - - def test_post_list_staff_master_course_in_ccx(self): + @ddt.data(*AUTH_ATTRS) + def test_post_list_staff_master_course_in_ccx(self, auth_attr): """ Specific test to check that the staff and instructor of the master course are assigned to the CCX. @@ -866,7 +819,7 @@ class CcxListTest(CcxRestApiTest): 'display_name': 'CCX Test Title', 'coach_email': self.coach.email } - resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=self.auth) + resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_201_CREATED) # check that only one email has been sent and it is to to the coach self.assertEqual(len(outbox), 1) @@ -891,32 +844,6 @@ class CcxListTest(CcxRestApiTest): for course_user, ccx_user in izip(sorted(list_instructor_master_course), sorted(list_instructor_ccx_course)): self.assertEqual(course_user, ccx_user) - # test for oauth2_provider - resp = self.client.post( - self.list_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) - self.assertEqual(resp.status_code, status.HTTP_201_CREATED) - # check that only one email has been sent and it is to to the coach - self.assertEqual(len(outbox), 2) - self.assertIn(self.coach.email, outbox[0].recipients()) # pylint: disable=no-member - - list_staff_master_course = list_with_level(self.course, 'staff') - list_instructor_master_course = list_with_level(self.course, 'instructor') - course_key = CourseKey.from_string(resp.data.get('ccx_course_id')) # pylint: disable=no-member - with ccx_course_cm(course_key) as course_ccx: - list_staff_ccx_course = list_with_level(course_ccx, 'staff') - list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') - - self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) - for course_user, ccx_user in izip(sorted(list_staff_master_course), sorted(list_staff_ccx_course)): - self.assertEqual(course_user, ccx_user) - self.assertEqual(len(list_instructor_master_course), len(list_instructor_ccx_course)) - for course_user, ccx_user in izip(sorted(list_instructor_master_course), sorted(list_instructor_ccx_course)): - self.assertEqual(course_user, ccx_user) - @attr(shard=1) @ddt.ddt @@ -974,7 +901,8 @@ class CcxDetailTest(CcxRestApiTest): ) return ccx - def test_authorization(self): + @ddt.data(*AUTH_ATTRS) + def test_authorization(self, auth_attr): """ Test that only the right token is authorized """ @@ -990,11 +918,7 @@ class CcxDetailTest(CcxRestApiTest): resp = self.client.get(self.detail_url, {}, HTTP_AUTHORIZATION=auth) self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED) - resp = self.client.get(self.detail_url, {}, HTTP_AUTHORIZATION=self.auth) - self.assertEqual(resp.status_code, status.HTTP_200_OK) - - # test for oauth2_provider - resp = self.client.get(self.detail_url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + resp = self.client.get(self.detail_url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_200_OK) def test_authorization_no_oauth_staff(self): @@ -1084,9 +1008,16 @@ class CcxDetailTest(CcxRestApiTest): self.assertEqual(views.CCXDetailView.__name__, resolver.func.__name__) self.assertEqual(views.CCXDetailView.__module__, resolver.func.__module__) - @ddt.data(('get',), ('delete',), ('patch',)) + @ddt.data( + ('get', AUTH_ATTRS[0]), + ('get', AUTH_ATTRS[1]), + ('delete', AUTH_ATTRS[0]), + ('delete', AUTH_ATTRS[1]), + ('patch', AUTH_ATTRS[0]), + ('patch', AUTH_ATTRS[1]) + ) @ddt.unpack - def test_detail_wrong_ccx(self, http_method): + def test_detail_wrong_ccx(self, http_method, auth_attr): """ Test for different methods for detail of a ccx course. All check the validity of the ccx course id @@ -1097,68 +1028,46 @@ class CcxDetailTest(CcxRestApiTest): url = reverse('ccx_api:v0:ccx:detail', kwargs={'ccx_course_id': self.master_course_key_str}) # the permission class will give a 403 error because will not find the CCX - resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth) - self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) - - # test for oauth2_provider - resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + resp = client_request(url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) # bypassing the permission class we get another kind of error with mock.patch(mock_class_str, autospec=True) as mocked_perm_class: mocked_perm_class.return_value = True - resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth) - self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_id_not_valid_ccx_id', resp) - # test for oauth2_provider - resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + resp = client_request(url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_id_not_valid_ccx_id', resp) # use an non existing ccx id url = reverse('ccx_api:v0:ccx:detail', kwargs={'ccx_course_id': 'ccx-v1:foo.0+course_bar_0+Run_0+ccx@1'}) # the permission class will give a 403 error because will not find the CCX - resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth) - self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) - - # test for oauth2_provider - resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + resp = client_request(url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) # bypassing the permission class we get another kind of error with mock.patch(mock_class_str, autospec=True) as mocked_perm_class: mocked_perm_class.return_value = True - resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth) - self.expect_error(status.HTTP_404_NOT_FOUND, 'ccx_course_id_does_not_exist', resp) - - # test for oauth2_provider - resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + resp = client_request(url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.expect_error(status.HTTP_404_NOT_FOUND, 'ccx_course_id_does_not_exist', resp) # get a valid ccx key and add few 0s to get a non existing ccx for a valid course ccx_key_str = '{0}000000'.format(self.ccx_key_str) url = reverse('ccx_api:v0:ccx:detail', kwargs={'ccx_course_id': ccx_key_str}) # the permission class will give a 403 error because will not find the CCX - resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth) - self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) - - # test for oauth2_provider - resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) + resp = client_request(url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) # bypassing the permission class we get another kind of error with mock.patch(mock_class_str, autospec=True) as mocked_perm_class: mocked_perm_class.return_value = True - resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth) + resp = client_request(url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.expect_error(status.HTTP_404_NOT_FOUND, 'ccx_course_id_does_not_exist', resp) - # test for oauth2_provider - resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) - self.expect_error(status.HTTP_404_NOT_FOUND, 'ccx_course_id_does_not_exist', resp) - - def test_get_detail(self): + @ddt.data(*AUTH_ATTRS) + def test_get_detail(self, auth_attr): """ Test for getting detail of a ccx course """ - resp = self.client.get(self.detail_url, {}, HTTP_AUTHORIZATION=self.auth) + resp = self.client.get(self.detail_url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertEqual(resp.data.get('ccx_course_id'), self.ccx_key_str) # pylint: disable=no-member self.assertEqual(resp.data.get('display_name'), self.ccx.display_name) # pylint: disable=no-member @@ -1170,27 +1079,15 @@ class CcxDetailTest(CcxRestApiTest): self.assertEqual(resp.data.get('master_course_id'), unicode(self.ccx.course_id)) # pylint: disable=no-member self.assertItemsEqual(resp.data.get('course_modules'), self.master_course_chapters) # pylint: disable=no-member - # test for oauth2_provider - resp = self.client.get(self.detail_url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) - self.assertEqual(resp.status_code, status.HTTP_200_OK) - self.assertEqual(resp.data.get('ccx_course_id'), self.ccx_key_str) # pylint: disable=no-member - self.assertEqual(resp.data.get('display_name'), self.ccx.display_name) # pylint: disable=no-member - self.assertEqual( - resp.data.get('max_students_allowed'), # pylint: disable=no-member - self.ccx.max_student_enrollments_allowed # pylint: disable=no-member - ) - self.assertEqual(resp.data.get('coach_email'), self.ccx.coach.email) # pylint: disable=no-member - self.assertEqual(resp.data.get('master_course_id'), unicode(self.ccx.course_id)) # pylint: disable=no-member - self.assertItemsEqual(resp.data.get('course_modules'), self.master_course_chapters) # pylint: disable=no-member - - def test_delete_detail(self): + @ddt.data(*AUTH_ATTRS) + def test_delete_detail(self, auth_attr): """ Test for deleting a ccx course """ # check that there are overrides self.assertGreater(CcxFieldOverride.objects.filter(ccx=self.ccx).count(), 0) self.assertGreater(CourseEnrollment.objects.filter(course_id=self.ccx_key).count(), 0) - resp = self.client.delete(self.detail_url, {}, HTTP_AUTHORIZATION=self.auth) + resp = self.client.delete(self.detail_url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) self.assertIsNone(resp.data) # pylint: disable=no-member # the CCX does not exist any more @@ -1200,40 +1097,15 @@ class CcxDetailTest(CcxRestApiTest): self.assertEqual(CcxFieldOverride.objects.filter(ccx=self.ccx).count(), 0) self.assertEqual(CourseEnrollment.objects.filter(course_id=self.ccx_key).count(), 0) - def test_delete_detail_oauth2_provider(self): - """ - Test for deleting a ccx course with oauth2_provider header - """ - # check that there are overrides - self.assertGreater(CcxFieldOverride.objects.filter(ccx=self.ccx).count(), 0) - self.assertGreater(CourseEnrollment.objects.filter(course_id=self.ccx_key).count(), 0) - resp = self.client.delete(self.detail_url, {}, HTTP_AUTHORIZATION=self.auth_header_oauth2_provider) - self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) - self.assertIsNone(resp.data) # pylint: disable=no-member - # the CCX does not exist any more - with self.assertRaises(CustomCourseForEdX.DoesNotExist): - CustomCourseForEdX.objects.get(id=self.ccx.id) - # check that there are no overrides - self.assertEqual(CcxFieldOverride.objects.filter(ccx=self.ccx).count(), 0) - self.assertEqual(CourseEnrollment.objects.filter(course_id=self.ccx_key).count(), 0) - - def test_patch_detail_change_master_course(self): + @ddt.data(*AUTH_ATTRS) + def test_patch_detail_change_master_course(self, auth_attr): """ Test to patch a ccx course to change a master course """ data = { 'master_course_id': 'changed_course_id' } - resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) - self.expect_error(status.HTTP_403_FORBIDDEN, 'master_course_id_change_not_allowed', resp) - - # test for oauth2_provider - resp = self.client.patch( - self.detail_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) + resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.expect_error(status.HTTP_403_FORBIDDEN, 'master_course_id_change_not_allowed', resp) @ddt.data( @@ -1247,51 +1119,95 @@ class CcxDetailTest(CcxRestApiTest): 'max_students_allowed': 'null_field_max_students_allowed', 'display_name': 'null_field_display_name', 'coach_email': 'null_field_coach_email' - } + }, + AUTH_ATTRS[0] + ), + ( + { + 'max_students_allowed': None, + 'display_name': None, + 'coach_email': None + }, + { + 'max_students_allowed': 'null_field_max_students_allowed', + 'display_name': 'null_field_display_name', + 'coach_email': 'null_field_coach_email' + }, + AUTH_ATTRS[1] ), ( {'coach_email': 'this is not an email@test.com'}, - {'coach_email': 'invalid_coach_email'} + {'coach_email': 'invalid_coach_email'}, + AUTH_ATTRS[0] + ), + ( + {'coach_email': 'this is not an email@test.com'}, + {'coach_email': 'invalid_coach_email'}, + AUTH_ATTRS[1] ), ( {'display_name': ''}, - {'display_name': 'invalid_display_name'} + {'display_name': 'invalid_display_name'}, + AUTH_ATTRS[0] + ), + ( + {'display_name': ''}, + {'display_name': 'invalid_display_name'}, + AUTH_ATTRS[1] ), ( {'max_students_allowed': 'a'}, - {'max_students_allowed': 'invalid_max_students_allowed'} + {'max_students_allowed': 'invalid_max_students_allowed'}, + AUTH_ATTRS[0] + ), + ( + {'max_students_allowed': 'a'}, + {'max_students_allowed': 'invalid_max_students_allowed'}, + AUTH_ATTRS[1] ), ( {'course_modules': {'foo': 'bar'}}, - {'course_modules': 'invalid_course_module_list'} + {'course_modules': 'invalid_course_module_list'}, + AUTH_ATTRS[0] + ), + ( + {'course_modules': {'foo': 'bar'}}, + {'course_modules': 'invalid_course_module_list'}, + AUTH_ATTRS[1] ), ( {'course_modules': 'block-v1:org.0+course_0+Run_0+type@chapter+block@chapter_1'}, - {'course_modules': 'invalid_course_module_list'} + {'course_modules': 'invalid_course_module_list'}, + AUTH_ATTRS[0] + + ), + ( + {'course_modules': 'block-v1:org.0+course_0+Run_0+type@chapter+block@chapter_1'}, + {'course_modules': 'invalid_course_module_list'}, + AUTH_ATTRS[1] + ), ( {'course_modules': ['foo', 'bar']}, - {'course_modules': 'invalid_course_module_keys'} + {'course_modules': 'invalid_course_module_keys'}, + AUTH_ATTRS[0] + ), + ( + {'course_modules': ['foo', 'bar']}, + {'course_modules': 'invalid_course_module_keys'}, + AUTH_ATTRS[1] ), ) @ddt.unpack - def test_patch_detail_wrong_input_data(self, data, expected_errors): + def test_patch_detail_wrong_input_data(self, data, expected_errors, auth_attr): """ Test for different wrong inputs for the patch method """ - resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) + resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.expect_error_fields(expected_errors, resp) - # test for oauth2_provider - resp = self.client.patch( - self.detail_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) - self.expect_error_fields(expected_errors, resp) - - def test_empty_patch(self): + @ddt.data(*AUTH_ATTRS) + def test_empty_patch(self, auth_attr): """ An empty patch does not modify anything """ @@ -1299,7 +1215,7 @@ class CcxDetailTest(CcxRestApiTest): max_students_allowed = self.ccx.max_student_enrollments_allowed # pylint: disable=no-member coach_email = self.ccx.coach.email # pylint: disable=no-member ccx_structure = self.ccx.structure # pylint: disable=no-member - resp = self.client.patch(self.detail_url, {}, format='json', HTTP_AUTHORIZATION=self.auth) + resp = self.client.patch(self.detail_url, {}, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) ccx = CustomCourseForEdX.objects.get(id=self.ccx.id) self.assertEqual(display_name, ccx.display_name) @@ -1307,21 +1223,8 @@ class CcxDetailTest(CcxRestApiTest): self.assertEqual(coach_email, ccx.coach.email) self.assertEqual(ccx_structure, ccx.structure) - # test for oauth2_provider - resp = self.client.patch( - self.detail_url, - {}, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) - self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) - ccx = CustomCourseForEdX.objects.get(id=self.ccx.id) - self.assertEqual(display_name, ccx.display_name) - self.assertEqual(max_students_allowed, ccx.max_student_enrollments_allowed) - self.assertEqual(coach_email, ccx.coach.email) - self.assertEqual(ccx_structure, ccx.structure) - - def test_patch_detail_coach_does_not_exist(self): + @ddt.data(*AUTH_ATTRS) + def test_patch_detail_coach_does_not_exist(self, auth_attr): """ Specific test for the case when the input data is valid but the coach does not exist. """ @@ -1330,19 +1233,11 @@ class CcxDetailTest(CcxRestApiTest): 'display_name': 'CCX Title', 'coach_email': 'inexisting_email@test.com' } - resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) + resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.expect_error(status.HTTP_404_NOT_FOUND, 'coach_user_does_not_exist', resp) - # test for oauth2_provider - resp = self.client.patch( - self.detail_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) - self.expect_error(status.HTTP_404_NOT_FOUND, 'coach_user_does_not_exist', resp) - - def test_patch_detail_wrong_modules(self): + @ddt.data(*AUTH_ATTRS) + def test_patch_detail_wrong_modules(self, auth_attr): """ Specific test for the case when the input data is valid but the course modules do not belong to the master course @@ -1353,19 +1248,11 @@ class CcxDetailTest(CcxRestApiTest): 'block-v1:org.0+course_0+Run_0+type@chapter+block@chapter_bar' ] } - resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) + resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_module_list_not_belonging_to_master_course', resp) - # test for oauth2_provider - resp = self.client.patch( - self.detail_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) - self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_module_list_not_belonging_to_master_course', resp) - - def test_patch_detail_mixed_wrong_and_valid_modules(self): + @ddt.data(*AUTH_ATTRS) + def test_patch_detail_mixed_wrong_and_valid_modules(self, auth_attr): """ Specific test for the case when the input data is valid but some of the course modules do not belong to the master course @@ -1374,19 +1261,11 @@ class CcxDetailTest(CcxRestApiTest): data = { 'course_modules': modules } - resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) + resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_module_list_not_belonging_to_master_course', resp) - # test for oauth2_provider - resp = self.client.patch( - self.detail_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) - self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_module_list_not_belonging_to_master_course', resp) - - def test_patch_detail(self): + @ddt.data(*AUTH_ATTRS) + def test_patch_detail(self, auth_attr): """ Test for successful patch """ @@ -1398,7 +1277,7 @@ class CcxDetailTest(CcxRestApiTest): 'display_name': 'CCX Title', 'coach_email': new_coach.email } - resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) + resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) self.assertEqual(ccx_from_db.max_student_enrollments_allowed, data['max_students_allowed']) @@ -1416,116 +1295,38 @@ class CcxDetailTest(CcxRestApiTest): self.assertEqual(len(outbox), 1) self.assertIn(new_coach.email, outbox[0].recipients()) # pylint: disable=no-member - # test for oauth2_provider - resp = self.client.patch( - self.detail_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) - self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) - ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) - self.assertEqual(ccx_from_db.max_student_enrollments_allowed, data['max_students_allowed']) - self.assertEqual(ccx_from_db.display_name, data['display_name']) - self.assertEqual(ccx_from_db.coach.email, data['coach_email']) - # check that the coach user has coach role on the master course - coach_role_on_master_course = CourseCcxCoachRole(self.master_course_key) - self.assertTrue(coach_role_on_master_course.has_user(new_coach)) - # check that the coach has been enrolled in the ccx - ccx_course_object = courses.get_course_by_id(self.ccx_key) - self.assertTrue( - CourseEnrollment.objects.filter(course_id=ccx_course_object.id, user=new_coach).exists() - ) - # check that an email has been sent to the coach - self.assertEqual(len(outbox), 1) - self.assertIn(new_coach.email, outbox[0].recipients()) # pylint: disable=no-member - - def test_patch_detail_modules(self): + @ddt.data(*AUTH_ATTRS) + def test_patch_detail_modules(self, auth_attr): """ Specific test for successful patch of the course modules """ data = {'course_modules': self.master_course_chapters[0:1]} - resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) - self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) - ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) - self.assertItemsEqual(ccx_from_db.structure, data['course_modules']) - - # test for oauth2_provider - resp = self.client.patch( - self.detail_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) + resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) self.assertItemsEqual(ccx_from_db.structure, data['course_modules']) data = {'course_modules': []} - resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) - self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) - ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) - self.assertItemsEqual(ccx_from_db.structure, []) - - # test for oauth2_provider - resp = self.client.patch( - self.detail_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) + resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) self.assertItemsEqual(ccx_from_db.structure, []) data = {'course_modules': self.master_course_chapters} - resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) - self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) - ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) - self.assertItemsEqual(ccx_from_db.structure, self.master_course_chapters) - - # test for oauth2_provider - resp = self.client.patch( - self.detail_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) + resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) self.assertItemsEqual(ccx_from_db.structure, self.master_course_chapters) data = {'course_modules': None} - resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) - self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) - ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) - self.assertEqual(ccx_from_db.structure, None) - - # test for oauth2_provider - resp = self.client.patch( - self.detail_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) + resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) self.assertEqual(ccx_from_db.structure, None) chapters = self.master_course_chapters[0:1] data = {'course_modules': chapters * 3} - resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) - self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) - ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) - self.assertItemsEqual(ccx_from_db.structure, chapters) - - # test for oauth2_provider - resp = self.client.patch( - self.detail_url, - data, - format='json', - HTTP_AUTHORIZATION=self.auth_header_oauth2_provider - ) + resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) self.assertItemsEqual(ccx_from_db.structure, chapters) From f09445ed18e6e960a7544dc6be8e2d280f20a344 Mon Sep 17 00:00:00 2001 From: Amir Qayyum Khan Date: Tue, 18 Oct 2016 16:19:42 +0500 Subject: [PATCH 3/3] Added more tests --- lms/djangoapps/ccx/api/v0/tests/test_views.py | 174 ++++++++++++++---- 1 file changed, 135 insertions(+), 39 deletions(-) diff --git a/lms/djangoapps/ccx/api/v0/tests/test_views.py b/lms/djangoapps/ccx/api/v0/tests/test_views.py index 134632b158..47f3da1d31 100644 --- a/lms/djangoapps/ccx/api/v0/tests/test_views.py +++ b/lms/djangoapps/ccx/api/v0/tests/test_views.py @@ -75,7 +75,12 @@ class CcxRestApiTest(CcxTestCase, APITestCase): self.master_course_key_str = unicode(self.master_course_key) # OAUTH2 setup # create a specific user for the application - app_user = UserFactory(username='test_app_user', email='test_app_user@openedx.org', password=USER_PASSWORD) + self.app_user = app_user = UserFactory( + username='test_app_user', + email='test_app_user@openedx.org', + password=USER_PASSWORD + ) + # add staff role to the app user CourseStaffRole(self.master_course_key).add_users(app_user) @@ -83,54 +88,22 @@ class CcxRestApiTest(CcxTestCase, APITestCase): instructor = UserFactory() allow_access(self.course, instructor, 'instructor') - # create an oauth client app entry - self.app_client = Client.objects.create( - user=app_user, - name='test client', - url='http://localhost//', - redirect_uri='http://localhost//', - client_type=CONFIDENTIAL - ) - # create an authorization code - self.app_grant = Grant.objects.create( - user=app_user, - client=self.app_client, - redirect_uri='http://localhost//' - ) - - # create an oauth2 provider client app entry - app_client_oauth2_provider = dot_models.Application.objects.create( - name='test client', - user=app_user, - client_type='confidential', - authorization_grant_type='authorization-code', - redirect_uris='http://localhost:8079/complete/edxorg/' - ) - # create an authorization code - auth_oauth2_provider = dot_models.AccessToken.objects.create( - user=app_user, - application=app_client_oauth2_provider, - expires=datetime.utcnow() + timedelta(weeks=1), - scope='read write', - token='16MGyP3OaQYHmpT1lK7Q6MMNAZsjwF' - ) - self.auth_header_oauth2_provider = "Bearer {0}".format(auth_oauth2_provider) + self.auth, self.auth_header_oauth2_provider = self.prepare_auth_token(app_user) self.course.enable_ccx = True self.mstore.update_item(self.course, self.coach.id) - self.auth = self.get_auth_token() # making the master course chapters easily available self.master_course_chapters = get_course_chapters(self.master_course_key) - def get_auth_token(self): + def get_auth_token(self, app_grant, app_client): """ Helper method to get the oauth token """ token_data = { 'grant_type': 'authorization_code', - 'code': self.app_grant.code, - 'client_id': self.app_client.client_id, - 'client_secret': self.app_client.client_secret + 'code': app_grant.code, + 'client_id': app_client.client_id, + 'client_secret': app_client.client_secret } token_resp = self.client.post('/oauth2/access_token/', data=token_data) self.assertEqual(token_resp.status_code, status.HTTP_200_OK) @@ -140,6 +113,47 @@ class CcxRestApiTest(CcxTestCase, APITestCase): token=token_resp_json['access_token'] ) + def prepare_auth_token(self, user): + """ + creates auth token for users + """ + # create an oauth client app entry + app_client = Client.objects.create( + user=user, + name='test client', + url='http://localhost//', + redirect_uri='http://localhost//', + client_type=CONFIDENTIAL + ) + # create an authorization code + app_grant = Grant.objects.create( + user=user, + client=app_client, + redirect_uri='http://localhost//' + ) + + # create an oauth2 provider client app entry + app_client_oauth2_provider = dot_models.Application.objects.create( + name='test client 2', + user=user, + client_type='confidential', + authorization_grant_type='authorization-code', + redirect_uris='http://localhost:8079/complete/edxorg/' + ) + # create an authorization code + auth_oauth2_provider = dot_models.AccessToken.objects.create( + user=user, + application=app_client_oauth2_provider, + expires=datetime.utcnow() + timedelta(weeks=1), + scope='read write', + token='16MGyP3OaQYHmpT1lK7Q6MMNAZsjwF' + ) + + auth_header_oauth2_provider = "Bearer {0}".format(auth_oauth2_provider) + auth = self.get_auth_token(app_grant, app_client) + + return auth, auth_header_oauth2_provider + def expect_error(self, http_code, error_code_str, resp_obj): """ Helper function that checks that the response object @@ -204,7 +218,6 @@ class CcxListTest(CcxRestApiTest): resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) self.assertEqual(resp.status_code, status.HTTP_200_OK) - def test_authorization_no_oauth_staff(self): """ Check authorization for staff users logged in without oauth @@ -788,6 +801,34 @@ class CcxListTest(CcxRestApiTest): self.assertEqual(len(outbox), 1) self.assertIn(self.coach.email, outbox[0].recipients()) # pylint: disable=no-member + @ddt.data( + ('auth', True), + ('auth', False), + ('auth_header_oauth2_provider', True), + ('auth_header_oauth2_provider', False) + ) + @ddt.unpack + def test_post_list_on_active_state(self, auth_attr, user_is_active): + """ + Test the creation of a CCX on user's active states. + """ + self.app_user.is_active = user_is_active + self.app_user.save() # pylint: disable=no-member + + data = { + 'master_course_id': self.master_course_key_str, + 'max_students_allowed': 111, + 'display_name': 'CCX Test Title', + 'coach_email': self.coach.email, + 'course_modules': self.master_course_chapters[0:1] + } + resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) + + if not user_is_active: + self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) + else: + self.assertEqual(resp.status_code, status.HTTP_201_CREATED) + @ddt.data(*AUTH_ATTRS) def test_post_list_duplicated_modules(self, auth_attr): """ @@ -1330,3 +1371,58 @@ class CcxDetailTest(CcxRestApiTest): self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) self.assertItemsEqual(ccx_from_db.structure, chapters) + + @ddt.data( + ('auth', True), + ('auth', False), + ('auth_header_oauth2_provider', True), + ('auth_header_oauth2_provider', False) + ) + @ddt.unpack + def test_patch_user_on_active_state(self, auth_attr, user_is_active): + """ + Test patch ccx course on user's active state. + """ + self.app_user.is_active = user_is_active + self.app_user.save() # pylint: disable=no-member + + chapters = self.master_course_chapters[0:1] + data = {'course_modules': chapters * 3} + resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=getattr(self, auth_attr)) + if not user_is_active: + self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) + else: + self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) + ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) + self.assertItemsEqual(ccx_from_db.structure, chapters) + + @ddt.data( + ('auth', True), + ('auth', False), + ('auth_header_oauth2_provider', True), + ('auth_header_oauth2_provider', False) + ) + @ddt.unpack + def test_delete_detail_on_active_state(self, auth_attr, user_is_active): + """ + Test for deleting a ccx course on user's active state. + """ + self.app_user.is_active = user_is_active + self.app_user.save() # pylint: disable=no-member + + # check that there are overrides + self.assertGreater(CcxFieldOverride.objects.filter(ccx=self.ccx).count(), 0) + self.assertGreater(CourseEnrollment.objects.filter(course_id=self.ccx_key).count(), 0) + resp = self.client.delete(self.detail_url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr)) + + if not user_is_active: + self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) + else: + self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) + self.assertIsNone(resp.data) # pylint: disable=no-member + # the CCX does not exist any more + with self.assertRaises(CustomCourseForEdX.DoesNotExist): + CustomCourseForEdX.objects.get(id=self.ccx.id) + # check that there are no overrides + self.assertEqual(CcxFieldOverride.objects.filter(ccx=self.ccx).count(), 0) + self.assertEqual(CourseEnrollment.objects.filter(course_id=self.ccx_key).count(), 0)