diff --git a/cms/djangoapps/contentstore/views/tests/test_item.py b/cms/djangoapps/contentstore/views/tests/test_item.py index ec4822d46e..bb55cb39aa 100644 --- a/cms/djangoapps/contentstore/views/tests/test_item.py +++ b/cms/djangoapps/contentstore/views/tests/test_item.py @@ -25,7 +25,7 @@ from student.tests.factories import UserFactory from xmodule.capa_module import CapaDescriptor from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, TEST_DATA_SPLIT_MODULESTORE from xmodule.modulestore.tests.factories import ItemFactory, LibraryFactory, check_mongo_calls from xmodule.x_module import STUDIO_VIEW, STUDENT_VIEW from xblock.exceptions import NoSuchHandlerError @@ -558,13 +558,13 @@ class TestDuplicateItem(ItemTest): return self.response_usage_key(resp) -class TestEditItem(ItemTest): +class TestEditItemSetup(ItemTest): """ - Test xblock update. + Setup for xblock update tests. """ def setUp(self): """ Creates the test course structure and a couple problems to 'edit'. """ - super(TestEditItem, self).setUp() + super(TestEditItemSetup, self).setUp() # create a chapter display_name = 'chapter created' resp = self.create_xblock(display_name=display_name, category='chapter') @@ -587,6 +587,11 @@ class TestEditItem(ItemTest): self.course_update_url = reverse_usage_url("xblock_handler", self.usage_key) + +class TestEditItem(TestEditItemSetup): + """ + Test xblock update. + """ def test_delete_field(self): """ Sending null in for a field 'deletes' it @@ -1004,6 +1009,26 @@ class TestEditItem(ItemTest): self.assertIn("Incorrect RelativeTime value", parsed["error"]) # See xmodule/fields.py +class TestEditItemSplitMongo(TestEditItemSetup): + """ + Tests for EditItem running on top of the SplitMongoModuleStore. + """ + MODULESTORE = TEST_DATA_SPLIT_MODULESTORE + + def test_editing_view_wrappers(self): + """ + Verify that the editing view only generates a single wrapper, no matter how many times it's loaded + + Exposes: PLAT-417 + """ + view_url = reverse_usage_url("xblock_view_handler", self.problem_usage_key, {"view_name": STUDIO_VIEW}) + + for __ in xrange(3): + resp = self.client.get(view_url, HTTP_ACCEPT='application/json') + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.content.count('xblock-{}'.format(STUDIO_VIEW)), 1) + + class TestEditSplitModule(ItemTest): """ Tests around editing instances of the split_test module. diff --git a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py index 7418c86b07..ab9cc62b6e 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py @@ -24,7 +24,12 @@ from xmodule.modulestore.tests.factories import XMODULE_FACTORY_LOCK from xmodule.tabs import CoursewareTab, CourseInfoTab, StaticTab, DiscussionTab, ProgressTab, WikiTab -def mixed_store_config(data_dir, mappings, include_xml=False, xml_course_dirs=None): +class StoreConstructors(object): + """Enumeration of store constructor types.""" + draft, split, xml = range(3) + + +def mixed_store_config(data_dir, mappings, include_xml=False, xml_course_dirs=None, store_order=None): """ Return a `MixedModuleStore` configuration, which provides access to both Mongo- and XML-backed courses. @@ -53,20 +58,24 @@ def mixed_store_config(data_dir, mappings, include_xml=False, xml_course_dirs=No * mappings should be configured, pointing the xml courses to the xml modulestore """ - stores = [ - draft_mongo_store_config(data_dir)['default'], - split_mongo_store_config(data_dir)['default'] - ] + if store_order is None: + store_order = [StoreConstructors.draft, StoreConstructors.split] - if include_xml: - stores.append(xml_store_config(data_dir, course_dirs=xml_course_dirs)['default']) + if include_xml and StoreConstructors.xml not in store_order: + store_order.append(StoreConstructors.xml) + + store_constructors = { + StoreConstructors.split: split_mongo_store_config(data_dir)['default'], + StoreConstructors.draft: draft_mongo_store_config(data_dir)['default'], + StoreConstructors.xml: xml_store_config(data_dir, course_dirs=xml_course_dirs)['default'], + } store = { 'default': { 'ENGINE': 'xmodule.modulestore.mixed.MixedModuleStore', 'OPTIONS': { 'mappings': mappings, - 'stores': stores, + 'stores': [store_constructors[store] for store in store_order], } } } @@ -177,6 +186,16 @@ TEST_DATA_MIXED_GRADED_MODULESTORE = mixed_store_config( # This modulestore definition below will not load any xml courses. TEST_DATA_MONGO_MODULESTORE = mixed_store_config(mkdtemp(), {}, include_xml=False) +# All store requests now go through mixed +# Use this modulestore if you specifically want to test split-mongo and not a mocked modulestore. +# This modulestore definition below will not load any xml courses. +TEST_DATA_SPLIT_MODULESTORE = mixed_store_config( + mkdtemp(), + {}, + include_xml=False, + store_order=[StoreConstructors.split, StoreConstructors.draft] +) + # Unit tests that are not specifically testing the modulestore implementation but just need course context can use a mocked modulestore. # Use this modulestore if you do not care about the underlying implementation. # TODO: acutally mock out the modulestore for this in a subsequent PR.