Commit c8b36a6a by Jeremy Bowman Committed by GitHub

Merge pull request #13161 from mitocw/fix/aq/support_django_oauth_toolkit

Fixed authentication classes to support Django OAUTH Toolkit
parents 6ccb74c7 fc042c9b
""" """
Tests for the CCX REST APIs. Tests for the CCX REST APIs.
""" """
import datetime
import json import json
import math import math
import pytz import pytz
import string import string
import urllib import urllib
import urlparse import urlparse
from datetime import datetime, timedelta
from itertools import izip from itertools import izip
import ddt import ddt
...@@ -20,6 +20,7 @@ from django.core.urlresolvers import ( ...@@ -20,6 +20,7 @@ from django.core.urlresolvers import (
Resolver404 Resolver404
) )
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from oauth2_provider import models as dot_models
from provider.constants import CONFIDENTIAL from provider.constants import CONFIDENTIAL
from provider.oauth2.models import ( from provider.oauth2.models import (
Client, Client,
...@@ -52,6 +53,10 @@ from student.roles import ( ...@@ -52,6 +53,10 @@ from student.roles import (
from student.tests.factories import AdminFactory from student.tests.factories import AdminFactory
USER_PASSWORD = 'test'
AUTH_ATTRS = ('auth', 'auth_header_oauth2_provider')
class CcxRestApiTest(CcxTestCase, APITestCase): class CcxRestApiTest(CcxTestCase, APITestCase):
""" """
Base class with common methods to be used in the test classes of this module Base class with common methods to be used in the test classes of this module
...@@ -70,7 +75,12 @@ class CcxRestApiTest(CcxTestCase, APITestCase): ...@@ -70,7 +75,12 @@ class CcxRestApiTest(CcxTestCase, APITestCase):
self.master_course_key_str = unicode(self.master_course_key) self.master_course_key_str = unicode(self.master_course_key)
# OAUTH2 setup # OAUTH2 setup
# create a specific user for the application # create a specific user for the application
app_user = UserFactory(username='test_app_user', email='test_app_user@openedx.org', password='test') 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 # add staff role to the app user
CourseStaffRole(self.master_course_key).add_users(app_user) CourseStaffRole(self.master_course_key).add_users(app_user)
...@@ -78,35 +88,22 @@ class CcxRestApiTest(CcxTestCase, APITestCase): ...@@ -78,35 +88,22 @@ class CcxRestApiTest(CcxTestCase, APITestCase):
instructor = UserFactory() instructor = UserFactory()
allow_access(self.course, instructor, 'instructor') allow_access(self.course, instructor, 'instructor')
# create an oauth client app entry self.auth, self.auth_header_oauth2_provider = self.prepare_auth_token(app_user)
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//'
)
self.course.enable_ccx = True self.course.enable_ccx = True
self.mstore.update_item(self.course, self.coach.id) self.mstore.update_item(self.course, self.coach.id)
self.auth = self.get_auth_token()
# making the master course chapters easily available # making the master course chapters easily available
self.master_course_chapters = get_course_chapters(self.master_course_key) 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 Helper method to get the oauth token
""" """
token_data = { token_data = {
'grant_type': 'authorization_code', 'grant_type': 'authorization_code',
'code': self.app_grant.code, 'code': app_grant.code,
'client_id': self.app_client.client_id, 'client_id': app_client.client_id,
'client_secret': self.app_client.client_secret 'client_secret': app_client.client_secret
} }
token_resp = self.client.post('/oauth2/access_token/', data=token_data) token_resp = self.client.post('/oauth2/access_token/', data=token_data)
self.assertEqual(token_resp.status_code, status.HTTP_200_OK) self.assertEqual(token_resp.status_code, status.HTTP_200_OK)
...@@ -116,6 +113,47 @@ class CcxRestApiTest(CcxTestCase, APITestCase): ...@@ -116,6 +113,47 @@ class CcxRestApiTest(CcxTestCase, APITestCase):
token=token_resp_json['access_token'] 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): def expect_error(self, http_code, error_code_str, resp_obj):
""" """
Helper function that checks that the response object Helper function that checks that the response object
...@@ -160,7 +198,8 @@ class CcxListTest(CcxRestApiTest): ...@@ -160,7 +198,8 @@ class CcxListTest(CcxRestApiTest):
'?master_course_id={0}'.format(urllib.quote_plus(self.master_course_key_str)) '?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 Test that only the right token is authorized
""" """
...@@ -175,7 +214,8 @@ class CcxListTest(CcxRestApiTest): ...@@ -175,7 +214,8 @@ class CcxListTest(CcxRestApiTest):
for auth in auth_list: for auth in auth_list:
resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=auth) resp = self.client.get(self.list_url_master_course, {}, HTTP_AUTHORIZATION=auth)
self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED) 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) self.assertEqual(resp.status_code, status.HTTP_200_OK)
def test_authorization_no_oauth_staff(self): def test_authorization_no_oauth_staff(self):
...@@ -183,7 +223,11 @@ class CcxListTest(CcxRestApiTest): ...@@ -183,7 +223,11 @@ class CcxListTest(CcxRestApiTest):
Check authorization for staff users logged in without oauth Check authorization for staff users logged in without oauth
""" """
# create a staff user # 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 # add staff role to the staff user
CourseStaffRole(self.master_course_key).add_users(staff_user) CourseStaffRole(self.master_course_key).add_users(staff_user)
...@@ -194,7 +238,7 @@ class CcxListTest(CcxRestApiTest): ...@@ -194,7 +238,7 @@ class CcxListTest(CcxRestApiTest):
'coach_email': self.coach.email 'coach_email': self.coach.email
} }
# the staff user can perform the request # 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) resp = self.client.get(self.list_url_master_course)
self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertEqual(resp.status_code, status.HTTP_200_OK)
resp = self.client.post(self.list_url, data, format='json') resp = self.client.post(self.list_url, data, format='json')
...@@ -206,7 +250,7 @@ class CcxListTest(CcxRestApiTest): ...@@ -206,7 +250,7 @@ class CcxListTest(CcxRestApiTest):
""" """
# create an instructor user # create an instructor user
instructor_user = UserFactory( 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 # add instructor role to the instructor user
CourseInstructorRole(self.master_course_key).add_users(instructor_user) CourseInstructorRole(self.master_course_key).add_users(instructor_user)
...@@ -219,7 +263,7 @@ class CcxListTest(CcxRestApiTest): ...@@ -219,7 +263,7 @@ class CcxListTest(CcxRestApiTest):
} }
# the instructor user can perform the request # 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) resp = self.client.get(self.list_url_master_course)
self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertEqual(resp.status_code, status.HTTP_200_OK)
resp = self.client.post(self.list_url, data, format='json') resp = self.client.post(self.list_url, data, format='json')
...@@ -231,7 +275,7 @@ class CcxListTest(CcxRestApiTest): ...@@ -231,7 +275,7 @@ class CcxListTest(CcxRestApiTest):
""" """
# create an coach user # create an coach user
coach_user = UserFactory( 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 # add coach role to the coach user
CourseCcxCoachRole(self.master_course_key).add_users(coach_user) CourseCcxCoachRole(self.master_course_key).add_users(coach_user)
...@@ -243,13 +287,14 @@ class CcxListTest(CcxRestApiTest): ...@@ -243,13 +287,14 @@ class CcxListTest(CcxRestApiTest):
'coach_email': self.coach.email 'coach_email': self.coach.email
} }
# the coach user cannot perform the request: this type of user can only get her own CCX # 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) resp = self.client.get(self.list_url_master_course)
self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN)
resp = self.client.post(self.list_url, data, format='json') resp = self.client.post(self.list_url, data, format='json')
self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) 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 Test for various get requests with wrong master course string
""" """
...@@ -258,27 +303,31 @@ class CcxListTest(CcxRestApiTest): ...@@ -258,27 +303,31 @@ class CcxListTest(CcxRestApiTest):
with mock.patch(mock_class_str, autospec=True) as mocked_perm_class: with mock.patch(mock_class_str, autospec=True) as mocked_perm_class:
mocked_perm_class.return_value = True mocked_perm_class.return_value = True
# case with no master_course_id provided # case with no master_course_id provided
resp = self.client.get(self.list_url, {}, HTTP_AUTHORIZATION=self.auth) 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) self.expect_error(status.HTTP_400_BAD_REQUEST, 'master_course_id_not_provided', resp)
base_url = urlparse.urljoin(self.list_url, '?master_course_id=') base_url = urlparse.urljoin(self.list_url, '?master_course_id=')
# case with empty master_course_id # case with empty master_course_id
resp = self.client.get(base_url, {}, HTTP_AUTHORIZATION=self.auth) 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) self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_id_not_valid', resp)
# case with invalid master_course_id # case with invalid master_course_id
url = '{0}invalid_master_course_str'.format(base_url) url = '{0}invalid_master_course_str'.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_400_BAD_REQUEST, 'course_id_not_valid', resp) self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_id_not_valid', resp)
# case with inexistent master_course_id # case with inexistent master_course_id
url = '{0}course-v1%3Aorg_foo.0%2Bcourse_bar_0%2BRun_0'.format(base_url) 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) 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 Tests the API to get a list of CCX Courses
""" """
# there are no CCX courses # there are no CCX courses
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.assertIn('count', resp.data) # pylint: disable=no-member self.assertIn('count', resp.data) # pylint: disable=no-member
self.assertEqual(resp.data['count'], 0) # pylint: disable=no-member self.assertEqual(resp.data['count'], 0) # pylint: disable=no-member
...@@ -286,14 +335,15 @@ class CcxListTest(CcxRestApiTest): ...@@ -286,14 +335,15 @@ class CcxListTest(CcxRestApiTest):
num_ccx = 10 num_ccx = 10
for _ in xrange(num_ccx): for _ in xrange(num_ccx):
self.make_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.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertIn('count', resp.data) # pylint: disable=no-member self.assertIn('count', resp.data) # pylint: disable=no-member
self.assertEqual(resp.data['count'], num_ccx) # 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.assertIn('results', resp.data) # pylint: disable=no-member
self.assertEqual(len(resp.data['results']), num_ccx) # 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 Tests the API to get a sorted list of CCX Courses
""" """
...@@ -312,21 +362,23 @@ class CcxListTest(CcxRestApiTest): ...@@ -312,21 +362,23 @@ class CcxListTest(CcxRestApiTest):
# sort by display name # sort by display name
url = '{0}&order_by=display_name'.format(self.list_url_master_course) url = '{0}&order_by=display_name'.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))
self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(len(resp.data['results']), num_ccx) # pylint: disable=no-member 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" # 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 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']) self.assertEqual(title_str.format(string.ascii_lowercase[-(num_ccx - num)]), ccx['display_name'])
# add sort order desc # add sort order desc
url = '{0}&order_by=display_name&sort_order=desc'.format(self.list_url_master_course) 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 # 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 # 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 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']) 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 Tests the API to get a paginated list of CCX Courses
""" """
...@@ -337,7 +389,7 @@ class CcxListTest(CcxRestApiTest): ...@@ -337,7 +389,7 @@ class CcxListTest(CcxRestApiTest):
page_size = settings.REST_FRAMEWORK.get('PAGE_SIZE', 10) page_size = settings.REST_FRAMEWORK.get('PAGE_SIZE', 10)
num_pages = int(math.ceil(num_ccx / float(page_size))) num_pages = int(math.ceil(num_ccx / float(page_size)))
# get first page # get first page
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.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(resp.data['count'], num_ccx) # pylint: disable=no-member 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['num_pages'], num_pages) # pylint: disable=no-member
...@@ -345,9 +397,10 @@ class CcxListTest(CcxRestApiTest): ...@@ -345,9 +397,10 @@ class CcxListTest(CcxRestApiTest):
self.assertEqual(resp.data['start'], 0) # pylint: disable=no-member self.assertEqual(resp.data['start'], 0) # pylint: disable=no-member
self.assertIsNotNone(resp.data['next']) # pylint: disable=no-member self.assertIsNotNone(resp.data['next']) # pylint: disable=no-member
self.assertIsNone(resp.data['previous']) # pylint: disable=no-member self.assertIsNone(resp.data['previous']) # pylint: disable=no-member
# get a page in the middle # get a page in the middle
url = '{0}&page=24'.format(self.list_url_master_course) url = '{0}&page=24'.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))
self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(resp.data['count'], num_ccx) # pylint: disable=no-member 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['num_pages'], num_pages) # pylint: disable=no-member
...@@ -355,9 +408,10 @@ class CcxListTest(CcxRestApiTest): ...@@ -355,9 +408,10 @@ class CcxListTest(CcxRestApiTest):
self.assertEqual(resp.data['start'], (resp.data['current_page'] - 1) * page_size) # 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['next']) # pylint: disable=no-member
self.assertIsNotNone(resp.data['previous']) # pylint: disable=no-member self.assertIsNotNone(resp.data['previous']) # pylint: disable=no-member
# get last page # get last page
url = '{0}&page={1}'.format(self.list_url_master_course, num_pages) url = '{0}&page={1}'.format(self.list_url_master_course, num_pages)
resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth) resp = self.client.get(url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr))
self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(resp.data['count'], num_ccx) # pylint: disable=no-member 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['num_pages'], num_pages) # pylint: disable=no-member
...@@ -365,40 +419,76 @@ class CcxListTest(CcxRestApiTest): ...@@ -365,40 +419,76 @@ class CcxListTest(CcxRestApiTest):
self.assertEqual(resp.data['start'], (resp.data['current_page'] - 1) * page_size) # 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.assertIsNone(resp.data['next']) # pylint: disable=no-member
self.assertIsNotNone(resp.data['previous']) # pylint: disable=no-member self.assertIsNotNone(resp.data['previous']) # pylint: disable=no-member
# last page + 1 # last page + 1
url = '{0}&page={1}'.format(self.list_url_master_course, num_pages + 1) url = '{0}&page={1}'.format(self.list_url_master_course, num_pages + 1)
resp = self.client.get(url, {}, HTTP_AUTHORIZATION=self.auth) resp = self.client.get(url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr))
self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND)
@ddt.data( @ddt.data(
( (
{}, {},
status.HTTP_400_BAD_REQUEST, 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}, {'master_course_id': None},
status.HTTP_400_BAD_REQUEST, 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',
'auth_header_oauth2_provider'
), ),
( (
{'master_course_id': ''}, {'master_course_id': ''},
status.HTTP_400_BAD_REQUEST, status.HTTP_400_BAD_REQUEST,
'course_id_not_valid' 'course_id_not_valid',
'auth'
), ),
( (
{'master_course_id': 'invalid_master_course_str'}, {'master_course_id': 'invalid_master_course_str'},
status.HTTP_400_BAD_REQUEST, 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',
'auth'
), ),
( (
{'master_course_id': 'course-v1:org_foo.0+course_bar_0+Run_0'}, {'master_course_id': 'course-v1:org_foo.0+course_bar_0+Run_0'},
status.HTTP_404_NOT_FOUND, status.HTTP_404_NOT_FOUND,
'course_id_does_not_exist' 'course_id_does_not_exist',
'auth_header_oauth2_provider'
), ),
) )
@ddt.unpack @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 Test for various post requests with wrong master course string
""" """
...@@ -407,10 +497,11 @@ class CcxListTest(CcxRestApiTest): ...@@ -407,10 +497,11 @@ class CcxListTest(CcxRestApiTest):
with mock.patch(mock_class_str, autospec=True) as mocked_perm_class: with mock.patch(mock_class_str, autospec=True) as mocked_perm_class:
mocked_perm_class.return_value = True mocked_perm_class.return_value = True
# case with no master_course_id provided # 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) 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, Same as test_post_list_wrong_master_course,
but different ways to test the wrong master_course_id but different ways to test the wrong master_course_id
...@@ -419,14 +510,17 @@ class CcxListTest(CcxRestApiTest): ...@@ -419,14 +510,17 @@ class CcxListTest(CcxRestApiTest):
self.course.enable_ccx = False self.course.enable_ccx = False
self.mstore.update_item(self.course, self.coach.id) self.mstore.update_item(self.course, self.coach.id)
data = {'master_course_id': self.master_course_key_str} 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.expect_error(status.HTTP_403_FORBIDDEN, 'ccx_not_enabled_for_master_course', resp)
self.course.enable_ccx = True self.course.enable_ccx = True
self.mstore.update_item(self.course, self.coach.id) self.mstore.update_item(self.course, self.coach.id)
# case with deprecated master_course_id # case with deprecated master_course_id
with mock.patch('courseware.courses.get_course_by_id', autospec=True) as mocked: with mock.patch('courseware.courses.get_course_by_id', autospec=True) as mocked:
mocked.return_value.id.deprecated = True 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) self.expect_error(status.HTTP_400_BAD_REQUEST, 'deprecated_master_course_id', resp)
@ddt.data( @ddt.data(
...@@ -436,7 +530,17 @@ class CcxListTest(CcxRestApiTest): ...@@ -436,7 +530,17 @@ class CcxListTest(CcxRestApiTest):
'max_students_allowed': 'missing_field_max_students_allowed', 'max_students_allowed': 'missing_field_max_students_allowed',
'display_name': 'missing_field_display_name', 'display_name': 'missing_field_display_name',
'coach_email': 'missing_field_coach_email' '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'
), ),
( (
{ {
...@@ -445,7 +549,18 @@ class CcxListTest(CcxRestApiTest): ...@@ -445,7 +549,18 @@ class CcxListTest(CcxRestApiTest):
}, },
{ {
'coach_email': 'missing_field_coach_email' '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'
), ),
( (
{ {
...@@ -457,7 +572,21 @@ class CcxListTest(CcxRestApiTest): ...@@ -457,7 +572,21 @@ class CcxListTest(CcxRestApiTest):
'max_students_allowed': 'null_field_max_students_allowed', 'max_students_allowed': 'null_field_max_students_allowed',
'display_name': 'null_field_display_name', 'display_name': 'null_field_display_name',
'coach_email': 'null_field_coach_email' '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'
), ),
( (
{ {
...@@ -465,7 +594,26 @@ class CcxListTest(CcxRestApiTest): ...@@ -465,7 +594,26 @@ class CcxListTest(CcxRestApiTest):
'display_name': 'CCX Title', 'display_name': 'CCX Title',
'coach_email': 'this is not an email@test.com' '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'
),
(
{
'max_students_allowed': 10,
'display_name': '',
'coach_email': 'email@test.com'
},
{'display_name': 'invalid_display_name'},
'auth'
), ),
( (
{ {
...@@ -473,7 +621,17 @@ class CcxListTest(CcxRestApiTest): ...@@ -473,7 +621,17 @@ class CcxListTest(CcxRestApiTest):
'display_name': '', 'display_name': '',
'coach_email': 'email@test.com' 'coach_email': 'email@test.com'
}, },
{'display_name': 'invalid_display_name'} {'display_name': 'invalid_display_name'},
'auth_header_oauth2_provider'
),
(
{
'max_students_allowed': 'a',
'display_name': 'CCX Title',
'coach_email': 'email@test.com'
},
{'max_students_allowed': 'invalid_max_students_allowed'},
'auth'
), ),
( (
{ {
...@@ -481,7 +639,8 @@ class CcxListTest(CcxRestApiTest): ...@@ -481,7 +639,8 @@ class CcxListTest(CcxRestApiTest):
'display_name': 'CCX Title', 'display_name': 'CCX Title',
'coach_email': 'email@test.com' 'coach_email': 'email@test.com'
}, },
{'max_students_allowed': 'invalid_max_students_allowed'} {'max_students_allowed': 'invalid_max_students_allowed'},
'auth_header_oauth2_provider'
), ),
( (
{ {
...@@ -490,7 +649,28 @@ class CcxListTest(CcxRestApiTest): ...@@ -490,7 +649,28 @@ class CcxListTest(CcxRestApiTest):
'coach_email': 'email@test.com', 'coach_email': 'email@test.com',
'course_modules': {'foo': 'bar'} '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'
),
(
{
'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'
), ),
( (
{ {
...@@ -499,7 +679,8 @@ class CcxListTest(CcxRestApiTest): ...@@ -499,7 +679,8 @@ class CcxListTest(CcxRestApiTest):
'coach_email': 'email@test.com', 'coach_email': 'email@test.com',
'course_modules': 'block-v1:org.0+course_0+Run_0+type@chapter+block@chapter_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_header_oauth2_provider'
), ),
( (
{ {
...@@ -508,20 +689,32 @@ class CcxListTest(CcxRestApiTest): ...@@ -508,20 +689,32 @@ class CcxListTest(CcxRestApiTest):
'coach_email': 'email@test.com', 'coach_email': 'email@test.com',
'course_modules': ['foo', 'bar'] '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 @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 Test for various post requests with wrong input data
""" """
# add the master_course_key_str to the request data # add the master_course_key_str to the request data
data['master_course_id'] = self.master_course_key_str 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) 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. Specific test for the case when the input data is valid but the coach does not exist.
""" """
...@@ -531,10 +724,11 @@ class CcxListTest(CcxRestApiTest): ...@@ -531,10 +724,11 @@ class CcxListTest(CcxRestApiTest):
'display_name': 'CCX Title', 'display_name': 'CCX Title',
'coach_email': 'inexisting_email@test.com' '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) 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 Specific test for the case when the input data is valid but the
course modules do not belong to the master course course modules do not belong to the master course
...@@ -549,10 +743,11 @@ class CcxListTest(CcxRestApiTest): ...@@ -549,10 +743,11 @@ class CcxListTest(CcxRestApiTest):
'block-v1:org.0+course_0+Run_0+type@chapter+block@chapter_bar' '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) 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 Specific test for the case when the input data is valid but some of
the course modules do not belong to the master course the course modules do not belong to the master course
...@@ -565,10 +760,11 @@ class CcxListTest(CcxRestApiTest): ...@@ -565,10 +760,11 @@ class CcxListTest(CcxRestApiTest):
'coach_email': self.coach.email, 'coach_email': self.coach.email,
'course_modules': modules '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) 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 Test the creation of a CCX
""" """
...@@ -580,7 +776,7 @@ class CcxListTest(CcxRestApiTest): ...@@ -580,7 +776,7 @@ class CcxListTest(CcxRestApiTest):
'coach_email': self.coach.email, 'coach_email': self.coach.email,
'course_modules': self.master_course_chapters[0:1] '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) self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
# check if the response has at least the same data of the request # check if the response has at least the same data of the request
for key, val in data.iteritems(): for key, val in data.iteritems():
...@@ -605,7 +801,36 @@ class CcxListTest(CcxRestApiTest): ...@@ -605,7 +801,36 @@ class CcxListTest(CcxRestApiTest):
self.assertEqual(len(outbox), 1) self.assertEqual(len(outbox), 1)
self.assertIn(self.coach.email, outbox[0].recipients()) # pylint: disable=no-member self.assertIn(self.coach.email, outbox[0].recipients()) # pylint: disable=no-member
def test_post_list_duplicated_modules(self): @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):
""" """
Test the creation of a CCX, but with duplicated modules Test the creation of a CCX, but with duplicated modules
""" """
...@@ -618,11 +843,12 @@ class CcxListTest(CcxRestApiTest): ...@@ -618,11 +843,12 @@ class CcxListTest(CcxRestApiTest):
'coach_email': self.coach.email, 'coach_email': self.coach.email,
'course_modules': duplicated_chapters '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.status_code, status.HTTP_201_CREATED)
self.assertEqual(resp.data.get('course_modules'), chapters) # pylint: disable=no-member 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 Specific test to check that the staff and instructor of the master
course are assigned to the CCX. course are assigned to the CCX.
...@@ -634,7 +860,7 @@ class CcxListTest(CcxRestApiTest): ...@@ -634,7 +860,7 @@ class CcxListTest(CcxRestApiTest):
'display_name': 'CCX Test Title', 'display_name': 'CCX Test Title',
'coach_email': self.coach.email '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) self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
# check that only one email has been sent and it is to to the coach # check that only one email has been sent and it is to to the coach
self.assertEqual(len(outbox), 1) self.assertEqual(len(outbox), 1)
...@@ -687,7 +913,7 @@ class CcxDetailTest(CcxRestApiTest): ...@@ -687,7 +913,7 @@ class CcxDetailTest(CcxRestApiTest):
ccx.structure_json = json.dumps(self.master_course_chapters) ccx.structure_json = json.dumps(self.master_course_chapters)
ccx.save() ccx.save()
today = datetime.datetime.today() today = datetime.today()
start = today.replace(tzinfo=pytz.UTC) start = today.replace(tzinfo=pytz.UTC)
override_field_for_ccx(ccx, self.course, 'start', start) override_field_for_ccx(ccx, self.course, 'start', start)
override_field_for_ccx(ccx, self.course, 'due', None) override_field_for_ccx(ccx, self.course, 'due', None)
...@@ -716,7 +942,8 @@ class CcxDetailTest(CcxRestApiTest): ...@@ -716,7 +942,8 @@ class CcxDetailTest(CcxRestApiTest):
) )
return ccx return ccx
def test_authorization(self): @ddt.data(*AUTH_ATTRS)
def test_authorization(self, auth_attr):
""" """
Test that only the right token is authorized Test that only the right token is authorized
""" """
...@@ -731,7 +958,8 @@ class CcxDetailTest(CcxRestApiTest): ...@@ -731,7 +958,8 @@ class CcxDetailTest(CcxRestApiTest):
for auth in auth_list: for auth in auth_list:
resp = self.client.get(self.detail_url, {}, HTTP_AUTHORIZATION=auth) resp = self.client.get(self.detail_url, {}, HTTP_AUTHORIZATION=auth)
self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED) self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED)
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.status_code, status.HTTP_200_OK)
def test_authorization_no_oauth_staff(self): def test_authorization_no_oauth_staff(self):
...@@ -745,7 +973,7 @@ class CcxDetailTest(CcxRestApiTest): ...@@ -745,7 +973,7 @@ class CcxDetailTest(CcxRestApiTest):
data = {'display_name': 'CCX Title'} data = {'display_name': 'CCX Title'}
# the staff user can perform the request # 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) resp = self.client.get(self.detail_url)
self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertEqual(resp.status_code, status.HTTP_200_OK)
resp = self.client.patch(self.detail_url, data, format='json') resp = self.client.patch(self.detail_url, data, format='json')
...@@ -762,7 +990,7 @@ class CcxDetailTest(CcxRestApiTest): ...@@ -762,7 +990,7 @@ class CcxDetailTest(CcxRestApiTest):
data = {'display_name': 'CCX Title'} data = {'display_name': 'CCX Title'}
# the instructor user can perform the request # 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) resp = self.client.get(self.detail_url)
self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertEqual(resp.status_code, status.HTTP_200_OK)
resp = self.client.patch(self.detail_url, data, format='json') resp = self.client.patch(self.detail_url, data, format='json')
...@@ -779,7 +1007,7 @@ class CcxDetailTest(CcxRestApiTest): ...@@ -779,7 +1007,7 @@ class CcxDetailTest(CcxRestApiTest):
data = {'display_name': 'CCX Title'} data = {'display_name': 'CCX Title'}
# the coach user cannot perform the request: this type of user can only get her own CCX # 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) resp = self.client.get(self.detail_url)
self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN)
resp = self.client.patch(self.detail_url, data, format='json') resp = self.client.patch(self.detail_url, data, format='json')
...@@ -791,7 +1019,7 @@ class CcxDetailTest(CcxRestApiTest): ...@@ -791,7 +1019,7 @@ class CcxDetailTest(CcxRestApiTest):
""" """
data = {'display_name': 'CCX Title'} data = {'display_name': 'CCX Title'}
# the coach owner of the CCX can perform the request only if it is a get # 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) resp = self.client.get(self.detail_url)
self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertEqual(resp.status_code, status.HTTP_200_OK)
resp = self.client.patch(self.detail_url, data, format='json') resp = self.client.patch(self.detail_url, data, format='json')
...@@ -821,9 +1049,16 @@ class CcxDetailTest(CcxRestApiTest): ...@@ -821,9 +1049,16 @@ class CcxDetailTest(CcxRestApiTest):
self.assertEqual(views.CCXDetailView.__name__, resolver.func.__name__) self.assertEqual(views.CCXDetailView.__name__, resolver.func.__name__)
self.assertEqual(views.CCXDetailView.__module__, resolver.func.__module__) 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 @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. Test for different methods for detail of a ccx course.
All check the validity of the ccx course id All check the validity of the ccx course id
...@@ -834,40 +1069,46 @@ class CcxDetailTest(CcxRestApiTest): ...@@ -834,40 +1069,46 @@ class CcxDetailTest(CcxRestApiTest):
url = reverse('ccx_api:v0:ccx:detail', kwargs={'ccx_course_id': self.master_course_key_str}) 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 # the permission class will give a 403 error because will not find the CCX
resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth) resp = client_request(url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr))
self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN)
# bypassing the permission class we get another kind of error # bypassing the permission class we get another kind of error
with mock.patch(mock_class_str, autospec=True) as mocked_perm_class: with mock.patch(mock_class_str, autospec=True) as mocked_perm_class:
mocked_perm_class.return_value = True 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_400_BAD_REQUEST, 'course_id_not_valid_ccx_id', resp) self.expect_error(status.HTTP_400_BAD_REQUEST, 'course_id_not_valid_ccx_id', resp)
# use an non existing ccx id # 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'}) 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 # the permission class will give a 403 error because will not find the CCX
resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth) resp = client_request(url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr))
self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN)
# bypassing the permission class we get another kind of error # bypassing the permission class we get another kind of error
with mock.patch(mock_class_str, autospec=True) as mocked_perm_class: with mock.patch(mock_class_str, autospec=True) as mocked_perm_class:
mocked_perm_class.return_value = True 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) 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 # 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) ccx_key_str = '{0}000000'.format(self.ccx_key_str)
url = reverse('ccx_api:v0:ccx:detail', kwargs={'ccx_course_id': 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 # the permission class will give a 403 error because will not find the CCX
resp = client_request(url, {}, HTTP_AUTHORIZATION=self.auth) resp = client_request(url, {}, HTTP_AUTHORIZATION=getattr(self, auth_attr))
self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN)
# bypassing the permission class we get another kind of error # bypassing the permission class we get another kind of error
with mock.patch(mock_class_str, autospec=True) as mocked_perm_class: with mock.patch(mock_class_str, autospec=True) as mocked_perm_class:
mocked_perm_class.return_value = True 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) 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 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.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('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('display_name'), self.ccx.display_name) # pylint: disable=no-member
...@@ -879,14 +1120,15 @@ class CcxDetailTest(CcxRestApiTest): ...@@ -879,14 +1120,15 @@ class CcxDetailTest(CcxRestApiTest):
self.assertEqual(resp.data.get('master_course_id'), unicode(self.ccx.course_id)) # 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 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 Test for deleting a ccx course
""" """
# check that there are overrides # check that there are overrides
self.assertGreater(CcxFieldOverride.objects.filter(ccx=self.ccx).count(), 0) self.assertGreater(CcxFieldOverride.objects.filter(ccx=self.ccx).count(), 0)
self.assertGreater(CourseEnrollment.objects.filter(course_id=self.ccx_key).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.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT)
self.assertIsNone(resp.data) # pylint: disable=no-member self.assertIsNone(resp.data) # pylint: disable=no-member
# the CCX does not exist any more # the CCX does not exist any more
...@@ -896,14 +1138,15 @@ class CcxDetailTest(CcxRestApiTest): ...@@ -896,14 +1138,15 @@ class CcxDetailTest(CcxRestApiTest):
self.assertEqual(CcxFieldOverride.objects.filter(ccx=self.ccx).count(), 0) self.assertEqual(CcxFieldOverride.objects.filter(ccx=self.ccx).count(), 0)
self.assertEqual(CourseEnrollment.objects.filter(course_id=self.ccx_key).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 Test to patch a ccx course to change a master course
""" """
data = { data = {
'master_course_id': 'changed_course_id' 'master_course_id': 'changed_course_id'
} }
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_403_FORBIDDEN, 'master_course_id_change_not_allowed', resp) self.expect_error(status.HTTP_403_FORBIDDEN, 'master_course_id_change_not_allowed', resp)
@ddt.data( @ddt.data(
...@@ -917,42 +1160,95 @@ class CcxDetailTest(CcxRestApiTest): ...@@ -917,42 +1160,95 @@ class CcxDetailTest(CcxRestApiTest):
'max_students_allowed': 'null_field_max_students_allowed', 'max_students_allowed': 'null_field_max_students_allowed',
'display_name': 'null_field_display_name', 'display_name': 'null_field_display_name',
'coach_email': 'null_field_coach_email' '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': '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'},
AUTH_ATTRS[0]
), ),
( (
{'display_name': ''}, {'display_name': ''},
{'display_name': 'invalid_display_name'} {'display_name': 'invalid_display_name'},
AUTH_ATTRS[1]
),
(
{'max_students_allowed': 'a'},
{'max_students_allowed': 'invalid_max_students_allowed'},
AUTH_ATTRS[0]
), ),
( (
{'max_students_allowed': 'a'}, {'max_students_allowed': 'a'},
{'max_students_allowed': 'invalid_max_students_allowed'} {'max_students_allowed': 'invalid_max_students_allowed'},
AUTH_ATTRS[1]
), ),
( (
{'course_modules': {'foo': 'bar'}}, {'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'},
AUTH_ATTRS[0]
), ),
( (
{'course_modules': 'block-v1:org.0+course_0+Run_0+type@chapter+block@chapter_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[1]
), ),
( (
{'course_modules': ['foo', 'bar']}, {'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 @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 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) 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 An empty patch does not modify anything
""" """
...@@ -960,7 +1256,7 @@ class CcxDetailTest(CcxRestApiTest): ...@@ -960,7 +1256,7 @@ class CcxDetailTest(CcxRestApiTest):
max_students_allowed = self.ccx.max_student_enrollments_allowed # pylint: disable=no-member max_students_allowed = self.ccx.max_student_enrollments_allowed # pylint: disable=no-member
coach_email = self.ccx.coach.email # pylint: disable=no-member coach_email = self.ccx.coach.email # pylint: disable=no-member
ccx_structure = self.ccx.structure # 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) self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT)
ccx = CustomCourseForEdX.objects.get(id=self.ccx.id) ccx = CustomCourseForEdX.objects.get(id=self.ccx.id)
self.assertEqual(display_name, ccx.display_name) self.assertEqual(display_name, ccx.display_name)
...@@ -968,7 +1264,8 @@ class CcxDetailTest(CcxRestApiTest): ...@@ -968,7 +1264,8 @@ class CcxDetailTest(CcxRestApiTest):
self.assertEqual(coach_email, ccx.coach.email) self.assertEqual(coach_email, ccx.coach.email)
self.assertEqual(ccx_structure, ccx.structure) 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. Specific test for the case when the input data is valid but the coach does not exist.
""" """
...@@ -977,10 +1274,11 @@ class CcxDetailTest(CcxRestApiTest): ...@@ -977,10 +1274,11 @@ class CcxDetailTest(CcxRestApiTest):
'display_name': 'CCX Title', 'display_name': 'CCX Title',
'coach_email': 'inexisting_email@test.com' '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) 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 Specific test for the case when the input data is valid but the
course modules do not belong to the master course course modules do not belong to the master course
...@@ -991,10 +1289,11 @@ class CcxDetailTest(CcxRestApiTest): ...@@ -991,10 +1289,11 @@ class CcxDetailTest(CcxRestApiTest):
'block-v1:org.0+course_0+Run_0+type@chapter+block@chapter_bar' '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) 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 Specific test for the case when the input data is valid but some of
the course modules do not belong to the master course the course modules do not belong to the master course
...@@ -1003,10 +1302,11 @@ class CcxDetailTest(CcxRestApiTest): ...@@ -1003,10 +1302,11 @@ class CcxDetailTest(CcxRestApiTest):
data = { data = {
'course_modules': modules '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) 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 Test for successful patch
""" """
...@@ -1018,7 +1318,7 @@ class CcxDetailTest(CcxRestApiTest): ...@@ -1018,7 +1318,7 @@ class CcxDetailTest(CcxRestApiTest):
'display_name': 'CCX Title', 'display_name': 'CCX Title',
'coach_email': new_coach.email '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) self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT)
ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) 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.max_student_enrollments_allowed, data['max_students_allowed'])
...@@ -1036,37 +1336,93 @@ class CcxDetailTest(CcxRestApiTest): ...@@ -1036,37 +1336,93 @@ class CcxDetailTest(CcxRestApiTest):
self.assertEqual(len(outbox), 1) self.assertEqual(len(outbox), 1)
self.assertIn(new_coach.email, outbox[0].recipients()) # pylint: disable=no-member 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 Specific test for successful patch of the course modules
""" """
data = {'course_modules': self.master_course_chapters[0:1]} data = {'course_modules': self.master_course_chapters[0:1]}
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) self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT)
ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id)
self.assertItemsEqual(ccx_from_db.structure, data['course_modules']) self.assertItemsEqual(ccx_from_db.structure, data['course_modules'])
data = {'course_modules': []} data = {'course_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.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT)
ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id)
self.assertItemsEqual(ccx_from_db.structure, []) self.assertItemsEqual(ccx_from_db.structure, [])
data = {'course_modules': self.master_course_chapters} data = {'course_modules': self.master_course_chapters}
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) self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT)
ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id)
self.assertItemsEqual(ccx_from_db.structure, self.master_course_chapters) self.assertItemsEqual(ccx_from_db.structure, self.master_course_chapters)
data = {'course_modules': None} data = {'course_modules': None}
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) self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT)
ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id)
self.assertEqual(ccx_from_db.structure, None) self.assertEqual(ccx_from_db.structure, None)
chapters = self.master_course_chapters[0:1] chapters = self.master_course_chapters[0:1]
data = {'course_modules': chapters * 3} data = {'course_modules': chapters * 3}
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) self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT)
ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id)
self.assertItemsEqual(ccx_from_db.structure, chapters) 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)
...@@ -9,11 +9,9 @@ from django.contrib.auth.models import User ...@@ -9,11 +9,9 @@ from django.contrib.auth.models import User
from django.db import transaction from django.db import transaction
from django.http import Http404 from django.http import Http404
from rest_framework import status from rest_framework import status
from rest_framework.authentication import SessionAuthentication
from rest_framework.generics import GenericAPIView from rest_framework.generics import GenericAPIView
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework_oauth.authentication import OAuth2Authentication
from ccx_keys.locator import CCXLocator from ccx_keys.locator import CCXLocator
from courseware import courses from courseware import courses
...@@ -26,7 +24,10 @@ from lms.djangoapps.instructor.enrollment import ( ...@@ -26,7 +24,10 @@ from lms.djangoapps.instructor.enrollment import (
from opaque_keys import InvalidKeyError from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey, UsageKey from opaque_keys.edx.keys import CourseKey, UsageKey
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview 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.models import CourseEnrollment
from student.roles import CourseCcxCoachRole from student.roles import CourseCcxCoachRole
...@@ -363,7 +364,11 @@ class CCXListView(GenericAPIView): ...@@ -363,7 +364,11 @@ class CCXListView(GenericAPIView):
] ]
} }
""" """
authentication_classes = (JwtAuthentication, OAuth2Authentication, SessionAuthentication,) authentication_classes = (
JwtAuthentication,
authentication.OAuth2AuthenticationAllowInactiveUser,
authentication.SessionAuthenticationAllowInactiveUser,
)
permission_classes = (IsAuthenticated, permissions.IsMasterCourseStaffInstructor) permission_classes = (IsAuthenticated, permissions.IsMasterCourseStaffInstructor)
serializer_class = CCXCourseSerializer serializer_class = CCXCourseSerializer
pagination_class = CCXAPIPagination pagination_class = CCXAPIPagination
...@@ -609,7 +614,11 @@ class CCXDetailView(GenericAPIView): ...@@ -609,7 +614,11 @@ class CCXDetailView(GenericAPIView):
response is returned. response is returned.
""" """
authentication_classes = (JwtAuthentication, OAuth2Authentication, SessionAuthentication,) authentication_classes = (
JwtAuthentication,
authentication.OAuth2AuthenticationAllowInactiveUser,
authentication.SessionAuthenticationAllowInactiveUser,
)
permission_classes = (IsAuthenticated, permissions.IsCourseStaffInstructor) permission_classes = (IsAuthenticated, permissions.IsCourseStaffInstructor)
serializer_class = CCXCourseSerializer 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