diff --git a/cms/djangoapps/contentstore/tests/test_crud.py b/cms/djangoapps/contentstore/tests/test_crud.py index 92d6c88c77..512667fa02 100644 --- a/cms/djangoapps/contentstore/tests/test_crud.py +++ b/cms/djangoapps/contentstore/tests/test_crud.py @@ -1,42 +1,21 @@ import unittest -from opaque_keys.edx.locator import LocalId - from xmodule import templates from xmodule.modulestore import ModuleStoreEnum -from xmodule.modulestore.tests import persistent_factories +from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, TEST_DATA_SPLIT_MODULESTORE from xmodule.course_module import CourseDescriptor -from xmodule.modulestore.django import modulestore, clear_existing_modulestores from xmodule.seq_module import SequenceDescriptor from xmodule.capa_module import CapaDescriptor -from xmodule.contentstore.django import _CONTENTSTORE -from xmodule.modulestore.exceptions import ItemNotFoundError, DuplicateCourseError from xmodule.html_module import HtmlDescriptor +from xmodule.modulestore.exceptions import DuplicateCourseError -class TemplateTests(unittest.TestCase): +class TemplateTests(ModuleStoreTestCase): """ Test finding and using the templates (boilerplates) for xblocks. """ - - def setUp(self): - super(TemplateTests, self).setUp() - clear_existing_modulestores() # redundant w/ cleanup but someone was getting errors - self.addCleanup(self._drop_mongo_collections) - self.addCleanup(clear_existing_modulestores) - self.split_store = modulestore()._get_modulestore_by_type(ModuleStoreEnum.Type.split) - - @staticmethod - def _drop_mongo_collections(): - """ - If using a Mongo-backed modulestore & contentstore, drop the collections. - """ - module_store = modulestore() - if hasattr(module_store, '_drop_database'): - module_store._drop_database() # pylint: disable=protected-access - _CONTENTSTORE.clear() - if hasattr(module_store, 'close_connections'): - module_store.close_connections() + MODULESTORE = TEST_DATA_SPLIT_MODULESTORE def test_get_templates(self): found = templates.all_templates() @@ -69,42 +48,49 @@ class TemplateTests(unittest.TestCase): self.assertIsNotNone(HtmlDescriptor.get_template('announcement.yaml')) def test_factories(self): - test_course = persistent_factories.PersistentCourseFactory.create( - course='course', run='2014', org='testx', - display_name='fun test course', user_id='testbot' + test_course = CourseFactory.create( + org='testx', + course='course', + run='2014', + display_name='fun test course', + user_id='testbot' ) self.assertIsInstance(test_course, CourseDescriptor) self.assertEqual(test_course.display_name, 'fun test course') - index_info = self.split_store.get_course_index_info(test_course.id) - self.assertEqual(index_info['org'], 'testx') - self.assertEqual(index_info['course'], 'course') - self.assertEqual(index_info['run'], '2014') + course_from_store = self.store.get_course(test_course.id) + self.assertEqual(course_from_store.id.org, 'testx') + self.assertEqual(course_from_store.id.course, 'course') + self.assertEqual(course_from_store.id.run, '2014') - test_chapter = persistent_factories.ItemFactory.create( - display_name='chapter 1', - parent_location=test_course.location + test_chapter = ItemFactory.create( + parent_location=test_course.location, + category='chapter', + display_name='chapter 1' ) self.assertIsInstance(test_chapter, SequenceDescriptor) # refetch parent which should now point to child - test_course = self.split_store.get_course(test_course.id.version_agnostic()) + test_course = self.store.get_course(test_course.id.version_agnostic()) self.assertIn(test_chapter.location, test_course.children) with self.assertRaises(DuplicateCourseError): - persistent_factories.PersistentCourseFactory.create( - course='course', run='2014', org='testx', - display_name='fun test course', user_id='testbot' + CourseFactory.create( + org='testx', + course='course', + run='2014', + display_name='fun test course', + user_id='testbot' ) def test_temporary_xblocks(self): """ Test create_xblock to create non persisted xblocks """ - test_course = persistent_factories.PersistentCourseFactory.create( + test_course = CourseFactory.create( course='course', run='2014', org='testx', display_name='fun test course', user_id='testbot' ) - test_chapter = self.split_store.create_xblock( + test_chapter = self.store.create_xblock( test_course.system, test_course.id, 'chapter', fields={'display_name': 'chapter n'}, parent_xblock=test_course ) @@ -114,7 +100,7 @@ class TemplateTests(unittest.TestCase): # test w/ a definition (e.g., a problem) test_def_content = 'boo' - test_problem = self.split_store.create_xblock( + test_problem = self.store.create_xblock( test_course.system, test_course.id, 'problem', fields={'data': test_def_content}, parent_xblock=test_chapter ) @@ -124,131 +110,28 @@ class TemplateTests(unittest.TestCase): test_problem.display_name = 'test problem' self.assertEqual(test_problem.display_name, 'test problem') - def test_persist_dag(self): - """ - try saving temporary xblocks - """ - test_course = persistent_factories.PersistentCourseFactory.create( - course='course', run='2014', org='testx', - display_name='fun test course', user_id='testbot' - ) - test_chapter = self.split_store.create_xblock( - test_course.system, test_course.id, 'chapter', fields={'display_name': 'chapter n'}, - parent_xblock=test_course - ) - self.assertEqual(test_chapter.display_name, 'chapter n') - test_def_content = 'boo' - # create child - new_block = self.split_store.create_xblock( - test_course.system, test_course.id, - 'problem', - fields={ - 'data': test_def_content, - 'display_name': 'problem' - }, - parent_xblock=test_chapter - ) - self.assertIsNotNone(new_block.definition_locator) - self.assertTrue(isinstance(new_block.definition_locator.definition_id, LocalId)) - # better to pass in persisted parent over the subdag so - # subdag gets the parent pointer (otherwise 2 ops, persist dag, update parent children, - # persist parent - persisted_course = self.split_store.persist_xblock_dag(test_course, 'testbot') - self.assertEqual(len(persisted_course.children), 1) - persisted_chapter = persisted_course.get_children()[0] - self.assertEqual(persisted_chapter.category, 'chapter') - self.assertEqual(persisted_chapter.display_name, 'chapter n') - self.assertEqual(len(persisted_chapter.children), 1) - persisted_problem = persisted_chapter.get_children()[0] - self.assertEqual(persisted_problem.category, 'problem') - self.assertEqual(persisted_problem.data, test_def_content) - # update it - persisted_problem.display_name = 'altered problem' - persisted_problem = self.split_store.persist_xblock_dag(persisted_problem, 'testbot') - self.assertEqual(persisted_problem.display_name, 'altered problem') - def test_delete_course(self): - test_course = persistent_factories.PersistentCourseFactory.create( - course='history', run='doomed', org='edu.harvard', + test_course = CourseFactory.create( + org='edu.harvard', + course='history', + run='doomed', display_name='doomed test course', user_id='testbot') - persistent_factories.ItemFactory.create( - display_name='chapter 1', - parent_location=test_course.location + ItemFactory.create( + parent_location=test_course.location, + category='chapter', + display_name='chapter 1' ) id_locator = test_course.id.for_branch(ModuleStoreEnum.BranchName.draft) - guid_locator = test_course.location.course_agnostic() # verify it can be retrieved by id - self.assertIsInstance(self.split_store.get_course(id_locator), CourseDescriptor) - # and by guid -- TODO reenable when split_draft supports getting specific versions -# self.assertIsInstance(self.split_store.get_item(guid_locator), CourseDescriptor) - self.split_store.delete_course(id_locator, 'testbot') - # test can no longer retrieve by id - self.assertRaises(ItemNotFoundError, self.split_store.get_course, id_locator) - # but can by guid -- same TODO as above -# self.assertIsInstance(self.split_store.get_item(guid_locator), CourseDescriptor) - - def test_block_generations(self): - """ - Test get_block_generations - """ - test_course = persistent_factories.PersistentCourseFactory.create( - course='history', run='hist101', org='edu.harvard', - display_name='history test course', - user_id='testbot' - ) - chapter = persistent_factories.ItemFactory.create( - display_name='chapter 1', - parent_location=test_course.location, - user_id='testbot' - ) - sub = persistent_factories.ItemFactory.create( - display_name='subsection 1', - parent_location=chapter.location, - user_id='testbot', - category='vertical' - ) - first_problem = persistent_factories.ItemFactory.create( - display_name='problem 1', parent_location=sub.location, user_id='testbot', category='problem', - data="" - ) - first_problem.max_attempts = 3 - first_problem.save() # decache the above into the kvs - updated_problem = self.split_store.update_item(first_problem, 'testbot') - self.assertIsNotNone(updated_problem.previous_version) - self.assertEqual(updated_problem.previous_version, first_problem.update_version) - self.assertNotEqual(updated_problem.update_version, first_problem.update_version) - self.split_store.delete_item(updated_problem.location, 'testbot') - - second_problem = persistent_factories.ItemFactory.create( - display_name='problem 2', - parent_location=sub.location.version_agnostic(), - user_id='testbot', category='problem', - data="" - ) - - # The draft course root has 2 revisions: the published revision, and then the subsequent - # changes to the draft revision - version_history = self.split_store.get_block_generations(test_course.location) - self.assertIsNotNone(version_history) - self.assertEqual(version_history.locator.version_guid, test_course.location.version_guid) - self.assertEqual(len(version_history.children), 1) - self.assertEqual(version_history.children[0].children, []) - self.assertEqual(version_history.children[0].locator.version_guid, chapter.location.version_guid) - - # sub changed on add, add problem, delete problem, add problem in strict linear seq - version_history = self.split_store.get_block_generations(sub.location) - self.assertEqual(len(version_history.children), 1) - self.assertEqual(len(version_history.children[0].children), 1) - self.assertEqual(len(version_history.children[0].children[0].children), 1) - self.assertEqual(len(version_history.children[0].children[0].children[0].children), 0) - - # first and second problem may show as same usage_id; so, need to ensure their histories are right - version_history = self.split_store.get_block_generations(updated_problem.location) - self.assertEqual(version_history.locator.version_guid, first_problem.location.version_guid) - self.assertEqual(len(version_history.children), 1) # updated max_attempts - self.assertEqual(len(version_history.children[0].children), 0) - - version_history = self.split_store.get_block_generations(second_problem.location) - self.assertNotEqual(version_history.locator.version_guid, first_problem.location.version_guid) + self.assertIsInstance(self.store.get_course(id_locator), CourseDescriptor) + # TODO reenable when split_draft supports getting specific versions + # guid_locator = test_course.location.course_agnostic() + # Verify it can be retrieved by guid + # self.assertIsInstance(self.store.get_item(guid_locator), CourseDescriptor) + self.store.delete_course(id_locator, 'testbot') + # Test can no longer retrieve by id. + self.assertIsNone(self.store.get_course(id_locator)) + # But can retrieve by guid -- same TODO as above + # self.assertIsInstance(self.store.get_item(guid_locator), CourseDescriptor) diff --git a/common/lib/xmodule/xmodule/modulestore/tests/persistent_factories.py b/common/lib/xmodule/xmodule/modulestore/tests/persistent_factories.py deleted file mode 100644 index 2eee09740f..0000000000 --- a/common/lib/xmodule/xmodule/modulestore/tests/persistent_factories.py +++ /dev/null @@ -1,91 +0,0 @@ -"""Provides factories for Split.""" -from xmodule.modulestore import ModuleStoreEnum -from xmodule.course_module import CourseDescriptor -from xmodule.x_module import XModuleDescriptor -import factory -from factory.helpers import lazy_attribute -from opaque_keys.edx.keys import UsageKey -# Factories are self documenting -# pylint: disable=missing-docstring - - -class SplitFactory(factory.Factory): - """ - Abstracted superclass which defines modulestore so that there's no dependency on django - if the caller passes modulestore in kwargs - """ - @lazy_attribute - def modulestore(self): - # Delayed import so that we only depend on django if the caller - # hasn't provided their own modulestore - from xmodule.modulestore.django import modulestore - return modulestore()._get_modulestore_by_type(ModuleStoreEnum.Type.split) - - -class PersistentCourseFactory(SplitFactory): - """ - Create a new course (not a new version of a course, but a whole new index entry). - - keywords: any xblock field plus (note, the below are filtered out; so, if they - become legitimate xblock fields, they won't be settable via this factory) - * org: defaults to textX - * master_branch: (optional) defaults to ModuleStoreEnum.BranchName.draft - * user_id: (optional) defaults to 'test_user' - * display_name (xblock field): will default to 'Robot Super Course' unless provided - """ - FACTORY_FOR = CourseDescriptor - - # pylint: disable=unused-argument - @classmethod - def _create(cls, target_class, course='999', run='run', org='testX', user_id=ModuleStoreEnum.UserID.test, - master_branch=ModuleStoreEnum.BranchName.draft, **kwargs): - - modulestore = kwargs.pop('modulestore') - root_block_id = kwargs.pop('root_block_id', 'course') - # Write the data to the mongo datastore - new_course = modulestore.create_course( - org, course, run, user_id, fields=kwargs, - master_branch=master_branch, root_block_id=root_block_id - ) - - return new_course - - @classmethod - def _build(cls, target_class, *args, **kwargs): - raise NotImplementedError() - - -class ItemFactory(SplitFactory): - FACTORY_FOR = XModuleDescriptor - - display_name = factory.LazyAttributeSequence(lambda o, n: "{} {}".format(o.category, n)) - - # pylint: disable=unused-argument - @classmethod - def _create(cls, target_class, parent_location, category='chapter', - user_id=ModuleStoreEnum.UserID.test, definition_locator=None, force=False, - continue_version=False, **kwargs): - """ - passes *kwargs* as the new item's field values: - - :param parent_location: (required) the location of the course & possibly parent - - :param category: (defaults to 'chapter') - - :param definition_locator (optional): the DescriptorLocator for the definition this uses or branches - """ - modulestore = kwargs.pop('modulestore') - if isinstance(parent_location, UsageKey): - return modulestore.create_child( - user_id, parent_location, category, defintion_locator=definition_locator, - force=force, continue_version=continue_version, **kwargs - ) - else: - return modulestore.create_item( - user_id, parent_location, category, defintion_locator=definition_locator, - force=force, continue_version=continue_version, **kwargs - ) - - @classmethod - def _build(cls, target_class, *args, **kwargs): - raise NotImplementedError() diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py b/common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py index 25e2dc7741..88da3c71aa 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py @@ -772,6 +772,122 @@ class SplitModuleCourseTests(SplitModuleTest): self.assertEqual(len(result.children[0].children), 1) self.assertEqual(result.children[0].children[0].locator.version_guid, versions[0]) + @patch('xmodule.tabs.CourseTab.from_json', side_effect=mock_tab_from_json) + def test_persist_dag(self, _from_json): + """ + try saving temporary xblocks + """ + test_course = modulestore().create_course( + course='course', run='2014', org='testx', + display_name='fun test course', user_id='testbot', + master_branch=ModuleStoreEnum.BranchName.draft + ) + test_chapter = modulestore().create_xblock( + test_course.system, test_course.id, 'chapter', fields={'display_name': 'chapter n'}, + parent_xblock=test_course + ) + self.assertEqual(test_chapter.display_name, 'chapter n') + test_def_content = 'boo' + # create child + new_block = modulestore().create_xblock( + test_course.system, test_course.id, + 'problem', + fields={ + 'data': test_def_content, + 'display_name': 'problem' + }, + parent_xblock=test_chapter + ) + self.assertIsNotNone(new_block.definition_locator) + self.assertTrue(isinstance(new_block.definition_locator.definition_id, LocalId)) + # better to pass in persisted parent over the subdag so + # subdag gets the parent pointer (otherwise 2 ops, persist dag, update parent children, + # persist parent + persisted_course = modulestore().persist_xblock_dag(test_course, 'testbot') + self.assertEqual(len(persisted_course.children), 1) + persisted_chapter = persisted_course.get_children()[0] + self.assertEqual(persisted_chapter.category, 'chapter') + self.assertEqual(persisted_chapter.display_name, 'chapter n') + self.assertEqual(len(persisted_chapter.children), 1) + persisted_problem = persisted_chapter.get_children()[0] + self.assertEqual(persisted_problem.category, 'problem') + self.assertEqual(persisted_problem.data, test_def_content) + # update it + persisted_problem.display_name = 'altered problem' + persisted_problem = modulestore().update_item(persisted_problem, 'testbot') + self.assertEqual(persisted_problem.display_name, 'altered problem') + + @patch('xmodule.tabs.CourseTab.from_json', side_effect=mock_tab_from_json) + def test_block_generations(self, _from_json): + """ + Test get_block_generations + """ + test_course = modulestore().create_course( + org='edu.harvard', + course='history', + run='hist101', + display_name='history test course', + user_id='testbot', + master_branch=ModuleStoreEnum.BranchName.draft + ) + chapter = modulestore().create_child( + None, test_course.location, + block_type='chapter', + block_id='chapter1', + fields={'display_name': 'chapter 1'} + ) + sub = modulestore().create_child( + None, chapter.location, + block_type='vertical', + block_id='subsection1', + fields={'display_name': 'subsection 1'} + ) + first_problem = modulestore().create_child( + None, sub.location, + block_type='problem', + block_id='problem1', + fields={'display_name': 'problem 1', 'data': ''} + ) + first_problem.max_attempts = 3 + first_problem.save() # decache the above into the kvs + updated_problem = modulestore().update_item(first_problem, 'testbot') + self.assertIsNotNone(updated_problem.previous_version) + self.assertEqual(updated_problem.previous_version, first_problem.update_version) + self.assertNotEqual(updated_problem.update_version, first_problem.update_version) + modulestore().delete_item(updated_problem.location, 'testbot') + + second_problem = modulestore().create_child( + None, sub.location.version_agnostic(), + block_type='problem', + block_id='problem2', + fields={'display_name': 'problem 2', 'data': ''} + ) + + # The draft course root has 2 revisions: the published revision, and then the subsequent + # changes to the draft revision + version_history = modulestore().get_block_generations(test_course.location) + self.assertIsNotNone(version_history) + self.assertEqual(version_history.locator.version_guid, test_course.location.version_guid) + self.assertEqual(len(version_history.children), 1) + self.assertEqual(version_history.children[0].children, []) + self.assertEqual(version_history.children[0].locator.version_guid, chapter.location.version_guid) + + # sub changed on add, add problem, delete problem, add problem in strict linear seq + version_history = modulestore().get_block_generations(sub.location) + self.assertEqual(len(version_history.children), 1) + self.assertEqual(len(version_history.children[0].children), 1) + self.assertEqual(len(version_history.children[0].children[0].children), 1) + self.assertEqual(len(version_history.children[0].children[0].children[0].children), 0) + + # first and second problem may show as same usage_id; so, need to ensure their histories are right + version_history = modulestore().get_block_generations(updated_problem.location) + self.assertEqual(version_history.locator.version_guid, first_problem.location.version_guid) + self.assertEqual(len(version_history.children), 1) # updated max_attempts + self.assertEqual(len(version_history.children[0].children), 0) + + version_history = modulestore().get_block_generations(second_problem.location) + self.assertNotEqual(version_history.locator.version_guid, first_problem.location.version_guid) + class TestCourseStructureCache(SplitModuleTest): """Tests for the CourseStructureCache"""