Commit 25d8085b by Amir Qayyum Khan

Fixed authentication classes to support Django OAUTH Toolkit

parent 046ee7bd
"""
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)
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment