Unit and integration tests of content libraries
This commit is contained in:
committed by
E. Kolpakov
parent
eddf44d853
commit
e1f6ca93ec
256
cms/djangoapps/contentstore/tests/test_libraries.py
Normal file
256
cms/djangoapps/contentstore/tests/test_libraries.py
Normal file
@@ -0,0 +1,256 @@
|
||||
"""
|
||||
Content library unit tests that require the CMS runtime.
|
||||
"""
|
||||
from contentstore.tests.utils import AjaxEnabledTestClient, parse_json
|
||||
from contentstore.utils import reverse_usage_url
|
||||
from contentstore.views.preview import _load_preview_module
|
||||
from contentstore.views.tests.test_library import LIBRARY_REST_URL
|
||||
import ddt
|
||||
from xmodule.library_content_module import LibraryVersionReference
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
from xmodule.tests import get_test_system
|
||||
from mock import Mock
|
||||
from opaque_keys.edx.locator import CourseKey, LibraryLocator
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestLibraries(ModuleStoreTestCase):
|
||||
"""
|
||||
High-level tests for libraries
|
||||
"""
|
||||
def setUp(self):
|
||||
user_password = super(TestLibraries, self).setUp()
|
||||
|
||||
self.client = AjaxEnabledTestClient()
|
||||
self.client.login(username=self.user.username, password=user_password)
|
||||
|
||||
self.lib_key = self._create_library()
|
||||
self.library = modulestore().get_library(self.lib_key)
|
||||
|
||||
def _create_library(self, org="org", library="lib", display_name="Test Library"):
|
||||
"""
|
||||
Helper method used to create a library. Uses the REST API.
|
||||
"""
|
||||
response = self.client.ajax_post(LIBRARY_REST_URL, {
|
||||
'org': org,
|
||||
'library': library,
|
||||
'display_name': display_name,
|
||||
})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
lib_info = parse_json(response)
|
||||
lib_key = CourseKey.from_string(lib_info['library_key'])
|
||||
self.assertIsInstance(lib_key, LibraryLocator)
|
||||
return lib_key
|
||||
|
||||
def _add_library_content_block(self, course, library_key, other_settings=None):
|
||||
"""
|
||||
Helper method to add a LibraryContent block to a course.
|
||||
The block will be configured to select content from the library
|
||||
specified by library_key.
|
||||
other_settings can be a dict of Scope.settings fields to set on the block.
|
||||
"""
|
||||
return ItemFactory.create(
|
||||
category='library_content',
|
||||
parent_location=course.location,
|
||||
user_id=self.user.id,
|
||||
publish_item=False,
|
||||
source_libraries=[LibraryVersionReference(library_key)],
|
||||
**(other_settings or {})
|
||||
)
|
||||
|
||||
def _refresh_children(self, lib_content_block):
|
||||
"""
|
||||
Helper method: Uses the REST API to call the 'refresh_children' handler
|
||||
of a LibraryContent block
|
||||
"""
|
||||
if 'user' not in lib_content_block.runtime._services: # pylint: disable=protected-access
|
||||
lib_content_block.runtime._services['user'] = Mock(user_id=self.user.id) # pylint: disable=protected-access
|
||||
handler_url = reverse_usage_url('component_handler', lib_content_block.location, kwargs={'handler': 'refresh_children'})
|
||||
response = self.client.ajax_post(handler_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
return modulestore().get_item(lib_content_block.location)
|
||||
|
||||
@ddt.data(
|
||||
(2, 1, 1),
|
||||
(2, 2, 2),
|
||||
(2, 20, 2),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_max_items(self, num_to_create, num_to_select, num_expected):
|
||||
"""
|
||||
Test the 'max_count' property of LibraryContent blocks.
|
||||
"""
|
||||
for _ in range(0, num_to_create):
|
||||
ItemFactory.create(category="html", parent_location=self.library.location, user_id=self.user.id, publish_item=False)
|
||||
|
||||
with modulestore().default_store(ModuleStoreEnum.Type.split):
|
||||
course = CourseFactory.create()
|
||||
|
||||
lc_block = self._add_library_content_block(course, self.lib_key, {'max_count': num_to_select})
|
||||
self.assertEqual(len(lc_block.children), 0)
|
||||
lc_block = self._refresh_children(lc_block)
|
||||
|
||||
# Now, we want to make sure that .children has the total # of potential
|
||||
# children, and that get_child_descriptors() returns the actual children
|
||||
# chosen for a given student.
|
||||
# In order to be able to call get_child_descriptors(), we must first
|
||||
# call bind_for_student:
|
||||
lc_block.bind_for_student(get_test_system(), lc_block._field_data) # pylint: disable=protected-access
|
||||
self.assertEqual(len(lc_block.children), num_to_create)
|
||||
self.assertEqual(len(lc_block.get_child_descriptors()), num_expected)
|
||||
|
||||
def test_consistent_children(self):
|
||||
"""
|
||||
Test that the same student will always see the same selected child block
|
||||
"""
|
||||
session_data = {}
|
||||
|
||||
def bind_module(descriptor):
|
||||
"""
|
||||
Helper to use the CMS's module system so we can access student-specific fields.
|
||||
"""
|
||||
request = Mock(user=self.user, session=session_data)
|
||||
return _load_preview_module(request, descriptor) # pylint: disable=protected-access
|
||||
|
||||
# Create many blocks in the library and add them to a course:
|
||||
for num in range(0, 8):
|
||||
ItemFactory.create(
|
||||
data="This is #{}".format(num + 1),
|
||||
category="html", parent_location=self.library.location, user_id=self.user.id, publish_item=False
|
||||
)
|
||||
|
||||
with modulestore().default_store(ModuleStoreEnum.Type.split):
|
||||
course = CourseFactory.create()
|
||||
|
||||
lc_block = self._add_library_content_block(course, self.lib_key, {'max_count': 1})
|
||||
lc_block_key = lc_block.location
|
||||
lc_block = self._refresh_children(lc_block)
|
||||
|
||||
def get_child_of_lc_block(block):
|
||||
"""
|
||||
Fetch the child shown to the current user.
|
||||
"""
|
||||
children = block.get_child_descriptors()
|
||||
self.assertEqual(len(children), 1)
|
||||
return children[0]
|
||||
|
||||
# Check which child a student will see:
|
||||
bind_module(lc_block)
|
||||
chosen_child = get_child_of_lc_block(lc_block)
|
||||
chosen_child_defn_id = chosen_child.definition_locator.definition_id
|
||||
lc_block.save()
|
||||
|
||||
modulestore().update_item(lc_block, self.user.id)
|
||||
|
||||
# Now re-load the block and try again:
|
||||
def check():
|
||||
"""
|
||||
Confirm that chosen_child is still the child seen by the test student
|
||||
"""
|
||||
for _ in range(0, 6): # Repeat many times b/c blocks are randomized
|
||||
lc_block = modulestore().get_item(lc_block_key) # Reload block from the database
|
||||
bind_module(lc_block)
|
||||
current_child = get_child_of_lc_block(lc_block)
|
||||
self.assertEqual(current_child.location, chosen_child.location)
|
||||
self.assertEqual(current_child.data, chosen_child.data)
|
||||
self.assertEqual(current_child.definition_locator.definition_id, chosen_child_defn_id)
|
||||
|
||||
check()
|
||||
# Refresh the children:
|
||||
lc_block = self._refresh_children(lc_block)
|
||||
# Now re-load the block and try yet again, in case refreshing the children changed anything:
|
||||
check()
|
||||
|
||||
def test_definition_shared_with_library(self):
|
||||
"""
|
||||
Test that the same block definition is used for the library and course[s]
|
||||
"""
|
||||
block1 = ItemFactory.create(category="html", parent_location=self.library.location, user_id=self.user.id, publish_item=False)
|
||||
def_id1 = block1.definition_locator.definition_id
|
||||
block2 = ItemFactory.create(category="html", parent_location=self.library.location, user_id=self.user.id, publish_item=False)
|
||||
def_id2 = block2.definition_locator.definition_id
|
||||
self.assertNotEqual(def_id1, def_id2)
|
||||
|
||||
# Next, create a course:
|
||||
with modulestore().default_store(ModuleStoreEnum.Type.split):
|
||||
course = CourseFactory.create()
|
||||
|
||||
# Add a LibraryContent block to the course:
|
||||
lc_block = self._add_library_content_block(course, self.lib_key)
|
||||
lc_block = self._refresh_children(lc_block)
|
||||
for child_key in lc_block.children:
|
||||
child = modulestore().get_item(child_key)
|
||||
def_id = child.definition_locator.definition_id
|
||||
self.assertIn(def_id, (def_id1, def_id2))
|
||||
|
||||
def test_fields(self):
|
||||
"""
|
||||
Test that blocks used from a library have the same field values as
|
||||
defined by the library author.
|
||||
"""
|
||||
data_value = "A Scope.content value"
|
||||
name_value = "A Scope.settings value"
|
||||
lib_block = ItemFactory.create(
|
||||
category="html",
|
||||
parent_location=self.library.location,
|
||||
user_id=self.user.id,
|
||||
publish_item=False,
|
||||
display_name=name_value,
|
||||
data=data_value,
|
||||
)
|
||||
self.assertEqual(lib_block.data, data_value)
|
||||
self.assertEqual(lib_block.display_name, name_value)
|
||||
|
||||
# Next, create a course:
|
||||
with modulestore().default_store(ModuleStoreEnum.Type.split):
|
||||
course = CourseFactory.create()
|
||||
|
||||
# Add a LibraryContent block to the course:
|
||||
lc_block = self._add_library_content_block(course, self.lib_key)
|
||||
lc_block = self._refresh_children(lc_block)
|
||||
course_block = modulestore().get_item(lc_block.children[0])
|
||||
|
||||
self.assertEqual(course_block.data, data_value)
|
||||
self.assertEqual(course_block.display_name, name_value)
|
||||
|
||||
def test_block_with_children(self):
|
||||
"""
|
||||
Test that blocks used from a library can have children.
|
||||
"""
|
||||
data_value = "A Scope.content value"
|
||||
name_value = "A Scope.settings value"
|
||||
# In the library, create a vertical block with a child:
|
||||
vert_block = ItemFactory.create(
|
||||
category="vertical",
|
||||
parent_location=self.library.location,
|
||||
user_id=self.user.id,
|
||||
publish_item=False,
|
||||
)
|
||||
child_block = ItemFactory.create(
|
||||
category="html",
|
||||
parent_location=vert_block.location,
|
||||
user_id=self.user.id,
|
||||
publish_item=False,
|
||||
display_name=name_value,
|
||||
data=data_value,
|
||||
)
|
||||
self.assertEqual(child_block.data, data_value)
|
||||
self.assertEqual(child_block.display_name, name_value)
|
||||
|
||||
# Next, create a course:
|
||||
with modulestore().default_store(ModuleStoreEnum.Type.split):
|
||||
course = CourseFactory.create()
|
||||
|
||||
# Add a LibraryContent block to the course:
|
||||
lc_block = self._add_library_content_block(course, self.lib_key)
|
||||
lc_block = self._refresh_children(lc_block)
|
||||
self.assertEqual(len(lc_block.children), 1)
|
||||
course_vert_block = modulestore().get_item(lc_block.children[0])
|
||||
self.assertEqual(len(course_vert_block.children), 1)
|
||||
course_child_block = modulestore().get_item(course_vert_block.children[0])
|
||||
|
||||
self.assertEqual(course_child_block.data, data_value)
|
||||
self.assertEqual(course_child_block.display_name, name_value)
|
||||
142
common/lib/xmodule/xmodule/tests/test_library_content.py
Normal file
142
common/lib/xmodule/xmodule/tests/test_library_content.py
Normal file
@@ -0,0 +1,142 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Basic unit tests for LibraryContentModule
|
||||
|
||||
Higher-level tests are in `cms/djangoapps/contentstore/tests/test_libraries.py`.
|
||||
"""
|
||||
import ddt
|
||||
from xmodule.library_content_module import LibraryVersionReference
|
||||
from xmodule.modulestore.tests.factories import LibraryFactory, CourseFactory, ItemFactory
|
||||
from xmodule.modulestore.tests.utils import MixedSplitTestCase
|
||||
from xmodule.tests import get_test_system
|
||||
from xmodule.validation import StudioValidationMessage
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestLibraries(MixedSplitTestCase):
|
||||
"""
|
||||
Basic unit tests for LibraryContentModule (library_content_module.py)
|
||||
"""
|
||||
def setUp(self):
|
||||
super(TestLibraries, self).setUp()
|
||||
|
||||
self.library = LibraryFactory.create(modulestore=self.store)
|
||||
self.lib_blocks = [
|
||||
ItemFactory.create(
|
||||
category="html",
|
||||
parent_location=self.library.location,
|
||||
user_id=self.user_id,
|
||||
publish_item=False,
|
||||
metadata={"data": "Hello world from block {}".format(i), },
|
||||
modulestore=self.store,
|
||||
)
|
||||
for i in range(1, 5)
|
||||
]
|
||||
self.course = CourseFactory.create(modulestore=self.store)
|
||||
self.chapter = ItemFactory.create(
|
||||
category="chapter",
|
||||
parent_location=self.course.location,
|
||||
user_id=self.user_id,
|
||||
modulestore=self.store,
|
||||
)
|
||||
self.sequential = ItemFactory.create(
|
||||
category="sequential",
|
||||
parent_location=self.chapter.location,
|
||||
user_id=self.user_id,
|
||||
modulestore=self.store,
|
||||
)
|
||||
self.vertical = ItemFactory.create(
|
||||
category="vertical",
|
||||
parent_location=self.sequential.location,
|
||||
user_id=self.user_id,
|
||||
modulestore=self.store,
|
||||
)
|
||||
self.lc_block = ItemFactory.create(
|
||||
category="library_content",
|
||||
parent_location=self.vertical.location,
|
||||
user_id=self.user_id,
|
||||
modulestore=self.store,
|
||||
metadata={
|
||||
'max_count': 1,
|
||||
'source_libraries': [LibraryVersionReference(self.library.location.library_key)]
|
||||
}
|
||||
)
|
||||
|
||||
def _bind_course_module(self, module):
|
||||
"""
|
||||
Bind a module (part of self.course) so we can access student-specific data.
|
||||
"""
|
||||
module_system = get_test_system(course_id=self.course.location.course_key)
|
||||
module_system.descriptor_runtime = module.runtime
|
||||
|
||||
def get_module(descriptor):
|
||||
"""Mocks module_system get_module function"""
|
||||
sub_module_system = get_test_system(course_id=self.course.location.course_key)
|
||||
sub_module_system.get_module = get_module
|
||||
sub_module_system.descriptor_runtime = descriptor.runtime
|
||||
descriptor.bind_for_student(sub_module_system, descriptor._field_data) # pylint: disable=protected-access
|
||||
return descriptor
|
||||
|
||||
module_system.get_module = get_module
|
||||
module.xmodule_runtime = module_system
|
||||
|
||||
def test_lib_content_block(self):
|
||||
"""
|
||||
Test that blocks from a library are copied and added as children
|
||||
"""
|
||||
# Check that the LibraryContent block has no children initially
|
||||
# Normally the children get added when the "source_libraries" setting
|
||||
# is updated, but the way we do it through a factory doesn't do that.
|
||||
self.assertEqual(len(self.lc_block.children), 0)
|
||||
# Update the LibraryContent module:
|
||||
self.lc_block.refresh_children(None, None)
|
||||
# Check that all blocks from the library are now children of the block:
|
||||
self.assertEqual(len(self.lc_block.children), len(self.lib_blocks))
|
||||
|
||||
def test_children_seen_by_a_user(self):
|
||||
"""
|
||||
Test that each student sees only one block as a child of the LibraryContent block.
|
||||
"""
|
||||
self.lc_block.refresh_children(None, None)
|
||||
self.lc_block = self.store.get_item(self.lc_block.location)
|
||||
self._bind_course_module(self.lc_block)
|
||||
# Make sure the runtime knows that the block's children vary per-user:
|
||||
self.assertTrue(self.lc_block.has_dynamic_children())
|
||||
|
||||
self.assertEqual(len(self.lc_block.children), len(self.lib_blocks))
|
||||
|
||||
# Check how many children each user will see:
|
||||
self.assertEqual(len(self.lc_block.get_child_descriptors()), 1)
|
||||
# Check that get_content_titles() doesn't return titles for hidden/unused children
|
||||
self.assertEqual(len(self.lc_block.get_content_titles()), 1)
|
||||
|
||||
def test_validation(self):
|
||||
"""
|
||||
Test that the validation method of LibraryContent blocks is working.
|
||||
"""
|
||||
# When source_libraries is blank, the validation summary should say this block needs to be configured:
|
||||
self.lc_block.source_libraries = []
|
||||
result = self.lc_block.validate()
|
||||
self.assertFalse(result) # Validation fails due to at least one warning/message
|
||||
self.assertTrue(result.summary)
|
||||
self.assertEqual(StudioValidationMessage.NOT_CONFIGURED, result.summary.type)
|
||||
|
||||
# When source_libraries references a non-existent library, we should get an error:
|
||||
self.lc_block.source_libraries = [LibraryVersionReference("library-v1:BAD+WOLF")]
|
||||
result = self.lc_block.validate()
|
||||
self.assertFalse(result) # Validation fails due to at least one warning/message
|
||||
self.assertTrue(result.summary)
|
||||
self.assertEqual(StudioValidationMessage.ERROR, result.summary.type)
|
||||
self.assertIn("invalid", result.summary.text)
|
||||
|
||||
# When source_libraries is set but the block needs to be updated, the summary should say so:
|
||||
self.lc_block.source_libraries = [LibraryVersionReference(self.library.location.library_key)]
|
||||
result = self.lc_block.validate()
|
||||
self.assertFalse(result) # Validation fails due to at least one warning/message
|
||||
self.assertTrue(result.summary)
|
||||
self.assertEqual(StudioValidationMessage.WARNING, result.summary.type)
|
||||
self.assertIn("out of date", result.summary.text)
|
||||
|
||||
# Now if we update the block, all validation should pass:
|
||||
self.lc_block.refresh_children(None, None)
|
||||
self.assertTrue(self.lc_block.validate())
|
||||
Reference in New Issue
Block a user