Files
edx-platform/lms/djangoapps/course_api/blocks/tests/test_views.py
Feanil Patel 2df8b8226b Merge pull request #22643 from edx/feanil/2to3_asserts
Run `2to3 -f asserts . -w` on edx-platform.
2019-12-30 12:13:42 -05:00

269 lines
9.9 KiB
Python

"""
Tests for Blocks Views
"""
from datetime import datetime
import six
from six.moves.urllib.parse import urlencode, urlunparse # pylint: disable=import-error
from django.urls import reverse
from opaque_keys.edx.locator import CourseLocator
from student.models import CourseEnrollment
from student.tests.factories import AdminFactory, CourseEnrollmentFactory, UserFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import ToyCourseFactory
from .helpers import deserialize_usage_key
class TestBlocksView(SharedModuleStoreTestCase):
"""
Test class for BlocksView
"""
requested_fields = ['graded', 'format', 'student_view_multi_device', 'children', 'not_a_field', 'due']
BLOCK_TYPES_WITH_STUDENT_VIEW_DATA = ['video', 'discussion', 'html']
@classmethod
def setUpClass(cls):
super(TestBlocksView, cls).setUpClass()
# create a toy course
cls.course = ToyCourseFactory.create(
modulestore=cls.store,
due=datetime(3013, 9, 18, 11, 30, 00),
)
cls.course_key = cls.course.id
cls.course_usage_key = cls.store.make_course_usage_key(cls.course_key)
cls.non_orphaned_block_usage_keys = set(
six.text_type(item.location)
for item in cls.store.get_items(cls.course_key)
# remove all orphaned items in the course, except for the root 'course' block
if cls.store.get_parent_location(item.location) or item.category == 'course'
)
def setUp(self):
super(TestBlocksView, self).setUp()
# create and enroll user in the toy course
self.user = UserFactory.create()
self.client.login(username=self.user.username, password='test')
CourseEnrollmentFactory.create(user=self.user, course_id=self.course_key)
# default values for url and query_params
self.url = reverse(
'blocks_in_block_tree',
kwargs={'usage_key_string': six.text_type(self.course_usage_key)}
)
self.query_params = {'depth': 'all', 'username': self.user.username}
def verify_response(self, expected_status_code=200, params=None, url=None):
"""
Ensure that sending a GET request to the specified URL returns the
expected status code.
Arguments:
expected_status_code: The status_code that is expected in the
response.
params: Parameters to add to self.query_params to include in the
request.
url: The URL to send the GET request. Default is self.url.
Returns:
response: The HttpResponse returned by the request
"""
if params:
self.query_params.update(params)
response = self.client.get(url or self.url, self.query_params)
self.assertEqual(response.status_code, expected_status_code)
return response
def verify_response_block_list(self, response):
"""
Verify that the response contains only the expected block ids.
"""
self.assertSetEqual(
{block['id'] for block in response.data},
self.non_orphaned_block_usage_keys,
)
def verify_response_block_dict(self, response):
"""
Verify that the response contains the expected blocks
"""
self.assertSetEqual(
set(six.iterkeys(response.data['blocks'])),
self.non_orphaned_block_usage_keys,
)
def verify_response_with_requested_fields(self, response):
"""
Verify the response has the expected structure
"""
self.verify_response_block_dict(response)
for block_key_string, block_data in six.iteritems(response.data['blocks']):
block_key = deserialize_usage_key(block_key_string, self.course_key)
xblock = self.store.get_item(block_key)
self.assert_in_iff('children', block_data, xblock.has_children)
self.assert_in_iff('graded', block_data, xblock.graded is not None)
self.assert_in_iff('format', block_data, xblock.format is not None)
self.assert_in_iff('due', block_data, xblock.due is not None)
self.assert_true_iff(block_data['student_view_multi_device'], block_data['type'] == 'html')
self.assertNotIn('not_a_field', block_data)
if xblock.has_children:
self.assertSetEqual(
set(six.text_type(child.location) for child in xblock.get_children()),
set(block_data['children']),
)
def assert_in_iff(self, member, container, predicate):
"""
Assert that member is in container if and only if predicate is true.
Arguments:
member - any object
container - any container
predicate - an expression, tested for truthiness
"""
if predicate:
self.assertIn(member, container)
else:
self.assertNotIn(member, container)
def assert_true_iff(self, expression, predicate):
"""
Assert that the expression is true if and only if the predicate is true
Arguments:
expression
predicate
"""
if predicate:
self.assertTrue(expression)
else:
self.assertFalse(expression)
def test_not_authenticated(self):
self.client.logout()
self.verify_response(401)
def test_not_enrolled(self):
CourseEnrollment.unenroll(self.user, self.course_key)
self.verify_response(403)
def test_non_existent_course(self):
usage_key = self.store.make_course_usage_key(CourseLocator('non', 'existent', 'course'))
url = reverse(
'blocks_in_block_tree',
kwargs={'usage_key_string': six.text_type(usage_key)}
)
self.verify_response(403, url=url)
def test_no_user_non_staff(self):
self.query_params.pop('username')
self.query_params['all_blocks'] = True
self.verify_response(403)
def test_no_user_staff_not_all_blocks(self):
self.query_params.pop('username')
self.verify_response(400)
def test_no_user_staff_all_blocks(self):
self.client.login(username=AdminFactory.create().username, password='test')
self.query_params.pop('username')
self.query_params['all_blocks'] = True
self.verify_response()
def test_basic(self):
response = self.verify_response()
self.assertEqual(response.data['root'], six.text_type(self.course_usage_key))
self.verify_response_block_dict(response)
for block_key_string, block_data in six.iteritems(response.data['blocks']):
block_key = deserialize_usage_key(block_key_string, self.course_key)
self.assertEqual(block_data['id'], block_key_string)
self.assertEqual(block_data['type'], block_key.block_type)
self.assertEqual(block_data['display_name'], self.store.get_item(block_key).display_name or '')
def test_return_type_param(self):
response = self.verify_response(params={'return_type': 'list'})
self.verify_response_block_list(response)
def test_block_counts_param(self):
response = self.verify_response(params={'block_counts': ['course', 'chapter']})
self.verify_response_block_dict(response)
for block_data in six.itervalues(response.data['blocks']):
self.assertEqual(
block_data['block_counts']['course'],
1 if block_data['type'] == 'course' else 0,
)
self.assertEqual(
block_data['block_counts']['chapter'],
(
1 if block_data['type'] == 'chapter' else
5 if block_data['type'] == 'course' else
0
)
)
def test_student_view_data_param(self):
response = self.verify_response(params={
'student_view_data': self.BLOCK_TYPES_WITH_STUDENT_VIEW_DATA + ['chapter']
})
self.verify_response_block_dict(response)
for block_data in six.itervalues(response.data['blocks']):
self.assert_in_iff(
'student_view_data',
block_data,
block_data['type'] in self.BLOCK_TYPES_WITH_STUDENT_VIEW_DATA
)
def test_navigation_param(self):
response = self.verify_response(params={'nav_depth': 10})
self.verify_response_block_dict(response)
for block_data in six.itervalues(response.data['blocks']):
self.assertIn('descendants', block_data)
def test_requested_fields_param(self):
response = self.verify_response(
params={'requested_fields': self.requested_fields}
)
self.verify_response_with_requested_fields(response)
def test_with_list_field_url(self):
query = urlencode(list(self.query_params.items()) + [
('requested_fields', self.requested_fields[0]),
('requested_fields', self.requested_fields[1]),
('requested_fields', ",".join(self.requested_fields[1:])),
])
self.query_params = None
response = self.verify_response(
url=urlunparse(("", "", self.url, "", query, ""))
)
self.verify_response_with_requested_fields(response)
class TestBlocksInCourseView(TestBlocksView):
"""
Test class for BlocksInCourseView
"""
def setUp(self):
super(TestBlocksInCourseView, self).setUp()
self.url = reverse('blocks_in_course')
self.query_params['course_id'] = six.text_type(self.course_key)
def test_no_course_id(self):
self.query_params.pop('course_id')
self.verify_response(400)
def test_invalid_course_id(self):
self.verify_response(400, params={'course_id': 'invalid_course_id'})
def test_non_existent_course(self):
self.verify_response(403, params={'course_id': six.text_type(CourseLocator('non', 'existent', 'course'))})