Unit and integration tests of content libraries

This commit is contained in:
Braden MacDonald
2014-11-01 19:47:28 -07:00
committed by E. Kolpakov
parent eddf44d853
commit e1f6ca93ec
2 changed files with 398 additions and 0 deletions

View 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)

View 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())