diff --git a/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py b/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py index 5e51d384de..2ede3bb3e7 100644 --- a/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py +++ b/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py @@ -3036,9 +3036,9 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase): Delete the orphan and any of its descendants which no longer have parents. """ if len(self._get_parents_from_structure(orphan, structure)) == 0: - for child in structure['blocks'][orphan].fields.get('children', []): + orphan_data = structure['blocks'].pop(orphan) + for child in orphan_data.fields.get('children', []): self._delete_if_true_orphan(BlockKey(*child), structure) - del structure['blocks'][orphan] @contract(returns=BlockData) def _new_block(self, user_id, category, block_fields, definition_id, new_id, raw=False, block_defaults=None): diff --git a/common/lib/xmodule/xmodule/modulestore/split_mongo/split_draft.py b/common/lib/xmodule/xmodule/modulestore/split_mongo/split_draft.py index c31c481611..b9f8dc85d4 100644 --- a/common/lib/xmodule/xmodule/modulestore/split_mongo/split_draft.py +++ b/common/lib/xmodule/xmodule/modulestore/split_mongo/split_draft.py @@ -189,42 +189,41 @@ class DraftVersioningModuleStore(SplitMongoModuleStore, ModuleStoreDraftAndPubli currently only provided by contentstore.views.item.orphan_handler Otherwise, raises a ValueError. """ + allowed_revisions = [ + None, + ModuleStoreEnum.RevisionOption.published_only, + ModuleStoreEnum.RevisionOption.all + ] + if revision not in allowed_revisions: + raise UnsupportedRevisionError(allowed_revisions) + + autopublish_parent = False with self.bulk_operations(location.course_key): if isinstance(location, LibraryUsageLocator): branches_to_delete = [ModuleStoreEnum.BranchName.library] # Libraries don't yet have draft/publish support - elif revision == ModuleStoreEnum.RevisionOption.published_only: - branches_to_delete = [ModuleStoreEnum.BranchName.published] + elif location.category in DIRECT_ONLY_CATEGORIES: + branches_to_delete = [ModuleStoreEnum.BranchName.published, ModuleStoreEnum.BranchName.draft] elif revision == ModuleStoreEnum.RevisionOption.all: branches_to_delete = [ModuleStoreEnum.BranchName.published, ModuleStoreEnum.BranchName.draft] - elif revision is None: - branches_to_delete = [ModuleStoreEnum.BranchName.draft] - - if location.category in DIRECT_ONLY_CATEGORIES: - branches_to_delete.append(ModuleStoreEnum.BranchName.published) else: - raise UnsupportedRevisionError( - [ - None, - ModuleStoreEnum.RevisionOption.published_only, - ModuleStoreEnum.RevisionOption.all - ] - ) + if revision == ModuleStoreEnum.RevisionOption.published_only: + branches_to_delete = [ModuleStoreEnum.BranchName.published] + elif revision is None: + branches_to_delete = [ModuleStoreEnum.BranchName.draft] + parent_loc = self.get_parent_location(location.for_branch(ModuleStoreEnum.BranchName.draft)) + autopublish_parent = ( + not skip_auto_publish and + parent_loc is not None and + parent_loc.block_type in DIRECT_ONLY_CATEGORIES + ) self._flag_publish_event(location.course_key) for branch in branches_to_delete: branched_location = location.for_branch(branch) - parent_loc = self.get_parent_location(branched_location) super(DraftVersioningModuleStore, self).delete_item(branched_location, user_id) - # publish parent w/o child if deleted element is direct only (not based on type of parent) - # publish vertical to behave more like the old mongo/draft modulestore - TNL-2593 - if ( - branch == ModuleStoreEnum.BranchName.draft and - branched_location.block_type in (DIRECT_ONLY_CATEGORIES + ['vertical']) and - parent_loc and - not skip_auto_publish - ): - # will publish if its not an orphan - self.publish(parent_loc.version_agnostic(), user_id, blacklist=EXCLUDE_ALL, **kwargs) + + if autopublish_parent: + self.publish(parent_loc.version_agnostic(), user_id, blacklist=EXCLUDE_ALL, **kwargs) def _map_revision_to_branch(self, key, revision=None): """ diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py b/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py index 96f85a1b00..9685a5eb3e 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py @@ -91,19 +91,19 @@ class CommonMixedModuleStoreSetup(CourseComparisonTest): OPTIONS = { 'stores': [ { - 'NAME': 'draft', + 'NAME': ModuleStoreEnum.Type.mongo, 'ENGINE': 'xmodule.modulestore.mongo.draft.DraftModuleStore', 'DOC_STORE_CONFIG': DOC_STORE_CONFIG, 'OPTIONS': modulestore_options }, { - 'NAME': 'split', + 'NAME': ModuleStoreEnum.Type.split, 'ENGINE': 'xmodule.modulestore.split_mongo.split_draft.DraftVersioningModuleStore', 'DOC_STORE_CONFIG': DOC_STORE_CONFIG, 'OPTIONS': modulestore_options }, { - 'NAME': 'xml', + 'NAME': ModuleStoreEnum.Type.xml, 'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore', 'OPTIONS': { 'data_dir': DATA_DIR, @@ -289,8 +289,11 @@ class CommonMixedModuleStoreSetup(CourseComparisonTest): self.xml_chapter_location = self.course_locations[self.XML_COURSEID1].replace( category='chapter', name='Overview' ) + self._create_course(self.course_locations[self.MONGO_COURSEID].course_key) + self.assertEquals(default, self.store.get_modulestore_type(self.course.id)) + @ddt.ddt @attr('mongo') @@ -298,7 +301,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): """ Tests of the MixedModulestore interface methods. """ - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_get_modulestore_type(self, default_ms): """ Make sure we get back the store type we expect for given mappings @@ -310,16 +313,15 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): self.assertEqual(self.store.get_modulestore_type( self._course_key_from_string(self.XML_COURSEID2)), ModuleStoreEnum.Type.xml ) - mongo_ms_type = ModuleStoreEnum.Type.mongo if default_ms == 'draft' else ModuleStoreEnum.Type.split self.assertEqual(self.store.get_modulestore_type( - self._course_key_from_string(self.MONGO_COURSEID)), mongo_ms_type + self._course_key_from_string(self.MONGO_COURSEID)), default_ms ) # try an unknown mapping, it should be the 'default' store self.assertEqual(self.store.get_modulestore_type( - SlashSeparatedCourseKey('foo', 'bar', '2012_Fall')), mongo_ms_type + SlashSeparatedCourseKey('foo', 'bar', '2012_Fall')), default_ms ) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_get_modulestore_cache(self, default_ms): """ Make sure we cache discovered course mappings @@ -354,7 +356,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): # problem: One lookup to locate an item that exists # fake: one w/ wildcard version # split has one lookup for the course and then one for the course items - @ddt.data(('draft', [1, 1], 0), ('split', [2, 2], 0)) + @ddt.data((ModuleStoreEnum.Type.mongo, [1, 1], 0), (ModuleStoreEnum.Type.split, [2, 2], 0)) @ddt.unpack def test_has_item(self, default_ms, max_find, max_send): self.initdb(default_ms) @@ -382,7 +384,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): # split: # problem: active_versions, structure # non-existent problem: ditto - @ddt.data(('draft', [3, 2], 0), ('split', [2, 2], 0)) + @ddt.data((ModuleStoreEnum.Type.mongo, [3, 2], 0), (ModuleStoreEnum.Type.split, [2, 2], 0)) @ddt.unpack def test_get_item(self, default_ms, max_find, max_send): self.initdb(default_ms) @@ -410,7 +412,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): # wildcard query, 6! load pertinent items for inheritance calls, load parents, course root fetch (why) # Split: # active_versions (with regex), structure, and spurious active_versions refetch - @ddt.data(('draft', 14, 0), ('split', 3, 0)) + @ddt.data((ModuleStoreEnum.Type.mongo, 14, 0), (ModuleStoreEnum.Type.split, 3, 0)) @ddt.unpack def test_get_items(self, default_ms, max_find, max_send): self.initdb(default_ms) @@ -438,7 +440,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): # sends: update problem and then each ancestor up to course (edit info) # split: active_versions, definitions (calculator field), structures # 2 sends to update index & structure (note, it would also be definition if a content field changed) - @ddt.data(('draft', 7, 5), ('split', 3, 2)) + @ddt.data((ModuleStoreEnum.Type.mongo, 7, 5), (ModuleStoreEnum.Type.split, 3, 2)) @ddt.unpack def test_update_item(self, default_ms, max_find, max_send): """ @@ -463,7 +465,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): self.assertEqual(problem.max_attempts, 2, "Update didn't persist") - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_has_changes_direct_only(self, default_ms): """ Tests that has_changes() returns false when a new xblock in a direct only category is checked @@ -484,7 +486,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): self.assertFalse(self.store.has_changes(test_course)) self.assertFalse(self.store.has_changes(chapter)) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_has_changes(self, default_ms): """ Tests that has_changes() only returns true when changes are present @@ -519,7 +521,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): component = self.store.publish(component.location, self.user_id) self.assertFalse(self.store.has_changes(component)) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_unit_stuck_in_draft_mode(self, default_ms): """ After revert_to_published() the has_changes() should return false if draft has no changes @@ -551,7 +553,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): component = self.store.publish(component.location, self.user_id) self.assertFalse(self.store.has_changes(component)) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_unit_stuck_in_published_mode(self, default_ms): """ After revert_to_published() the has_changes() should return true if draft has changes @@ -588,7 +590,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): # Verify that changes are present self.assertTrue(self.store.has_changes(component)) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_unit_stuck_in_published_mode_after_delete(self, default_ms): """ Test that a unit does not get stuck in published mode @@ -631,7 +633,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): vertical = self.store.get_item(vertical.location) self.assertTrue(self._has_changes(vertical.location)) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_publish_automatically_after_delete_unit(self, default_ms): """ Check that sequential publishes automatically after deleting a unit @@ -674,7 +676,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): return locations - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_has_changes_ancestors(self, default_ms): """ Tests that has_changes() returns true on ancestors when a child is changed @@ -704,7 +706,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): for key in locations: self.assertFalse(self._has_changes(locations[key])) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_has_changes_publish_ancestors(self, default_ms): """ Tests that has_changes() returns false after a child is published only if all children are unchanged @@ -741,7 +743,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): self.assertFalse(self._has_changes(locations['grandparent'])) self.assertFalse(self._has_changes(locations['parent'])) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_has_changes_add_remove_child(self, default_ms): """ Tests that has_changes() returns true for the parent when a child with changes is added @@ -774,7 +776,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): self.assertFalse(self._has_changes(locations['grandparent'])) self.assertFalse(self._has_changes(locations['parent'])) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_has_changes_non_direct_only_children(self, default_ms): """ Tests that has_changes() returns true after editing the child of a vertical (both not direct only categories). @@ -808,7 +810,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): self.assertTrue(self._has_changes(child.location)) @ddt.data(*itertools.product( - ('draft', 'split'), + (ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split), (ModuleStoreEnum.Branch.draft_preferred, ModuleStoreEnum.Branch.published_only) )) @ddt.unpack @@ -840,14 +842,14 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): # Split # Find: active_versions, 2 structures (published & draft), definition (unnecessary) # Sends: updated draft and published structures and active_versions - @ddt.data(('draft', 7, 2), ('split', 4, 3)) + @ddt.data((ModuleStoreEnum.Type.mongo, 7, 2), (ModuleStoreEnum.Type.split, 3, 3)) @ddt.unpack def test_delete_item(self, default_ms, max_find, max_send): """ Delete should reject on r/o db and work on r/w one """ self.initdb(default_ms) - if default_ms == 'draft' and mongo_uses_error_check(self.store): + if default_ms == ModuleStoreEnum.Type.mongo and mongo_uses_error_check(self.store): max_find += 1 # r/o try deleting the chapter (is here to ensure it can't be deleted) @@ -872,7 +874,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): # Split: # queries: active_versions, draft and published structures, definition (unnecessary) # sends: update published (why?), draft, and active_versions - @ddt.data(('draft', 9, 2), ('split', 4, 3)) + @ddt.data((ModuleStoreEnum.Type.mongo, 9, 2), (ModuleStoreEnum.Type.split, 4, 3)) @ddt.unpack def test_delete_private_vertical(self, default_ms, max_find, max_send): """ @@ -880,7 +882,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): behavioral properties which this deletion test gets at. """ self.initdb(default_ms) - if default_ms == 'draft' and mongo_uses_error_check(self.store): + if default_ms == ModuleStoreEnum.Type.mongo and mongo_uses_error_check(self.store): max_find += 1 # create and delete a private vertical with private children private_vert = self.store.create_child( @@ -925,7 +927,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): # Split: # find: active_version & structure (cached) # send: update structure and active_versions - @ddt.data(('draft', 4, 1), ('split', 2, 2)) + @ddt.data((ModuleStoreEnum.Type.mongo, 4, 1), (ModuleStoreEnum.Type.split, 2, 2)) @ddt.unpack def test_delete_draft_vertical(self, default_ms, max_find, max_send): """ @@ -955,7 +957,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): private_leaf.display_name = 'change me' private_leaf = self.store.update_item(private_leaf, self.user_id) # test succeeds if delete succeeds w/o error - if default_ms == 'draft' and mongo_uses_error_check(self.store): + if default_ms == ModuleStoreEnum.Type.mongo and mongo_uses_error_check(self.store): max_find += 1 with check_mongo_calls(max_find, max_send): self.store.delete_item(private_leaf.location, self.user_id) @@ -968,7 +970,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): # 1) wildcard split search, # 2-4) active_versions, structure, definition (s/b lazy; so, unnecessary) # 5) wildcard draft mongo which has none - @ddt.data(('draft', 3, 0), ('split', 5, 0)) + @ddt.data((ModuleStoreEnum.Type.mongo, 3, 0), (ModuleStoreEnum.Type.split, 5, 0)) @ddt.unpack def test_get_courses(self, default_ms, max_find, max_send): self.initdb(default_ms) @@ -987,7 +989,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): published_courses = self.store.get_courses(remove_branch=True) self.assertEquals([c.id for c in draft_courses], [c.id for c in published_courses]) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_create_child_detached_tabs(self, default_ms): """ test 'create_child' method with a detached category ('static_tab') @@ -1012,7 +1014,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): """ Test that the xml modulestore only loaded the courses from the maps. """ - self.initdb('draft') + self.initdb(ModuleStoreEnum.Type.mongo) xml_store = self.store._get_modulestore_by_type(ModuleStoreEnum.Type.xml) # pylint: disable=protected-access courses = xml_store.get_courses() self.assertEqual(len(courses), 2) @@ -1026,7 +1028,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): """ Test that the xml modulestore doesn't allow write ops. """ - self.initdb('draft') + self.initdb(ModuleStoreEnum.Type.mongo) xml_store = self.store._get_modulestore_by_type(ModuleStoreEnum.Type.xml) # pylint: disable=protected-access # the important thing is not which exception it raises but that it raises an exception with self.assertRaises(AttributeError): @@ -1034,7 +1036,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): # draft is 2: find out which ms owns course, get item # split: active_versions, structure, definition (to load course wiki string) - @ddt.data(('draft', 2, 0), ('split', 3, 0)) + @ddt.data((ModuleStoreEnum.Type.mongo, 2, 0), (ModuleStoreEnum.Type.split, 3, 0)) @ddt.unpack def test_get_course(self, default_ms, max_find, max_send): """ @@ -1049,7 +1051,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): course = self.store.get_item(self.course_locations[self.XML_COURSEID1]) self.assertEqual(course.id, self.course_locations[self.XML_COURSEID1].course_key) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_get_library(self, default_ms): """ Test that create_library and get_library work regardless of the default modulestore. @@ -1074,7 +1076,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): # still only 2) # Draft: get_parent # Split: active_versions, structure - @ddt.data(('draft', 1, 0), ('split', 2, 0)) + @ddt.data((ModuleStoreEnum.Type.mongo, 1, 0), (ModuleStoreEnum.Type.split, 2, 0)) @ddt.unpack def test_get_parent_locations(self, default_ms, max_find, max_send): """ @@ -1100,7 +1102,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): self.store.get_parent_location(child_location, revision=revision) ) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_get_parent_locations_moved_child(self, default_ms): self.initdb(default_ms) self._create_block_hierarchy() @@ -1151,7 +1153,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): (child_to_move_location, new_parent_published_location, ModuleStoreEnum.RevisionOption.published_only), ]) - @ddt.data('draft') + @ddt.data(ModuleStoreEnum.Type.mongo) def test_get_parent_locations_deleted_child(self, default_ms): self.initdb(default_ms) self._create_block_hierarchy() @@ -1182,7 +1184,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): (child_to_delete_location, None, ModuleStoreEnum.RevisionOption.published_only), ]) - @ddt.data('draft') + @ddt.data(ModuleStoreEnum.Type.mongo) def test_get_parent_location_draft(self, default_ms): """ Test that "get_parent_location" method returns first published parent @@ -1225,7 +1227,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): # 8-9. get vertical, compute inheritance # 10-11. get other vertical_x1b (why?) and compute inheritance # Split: active_versions & structure - @ddt.data(('draft', [12, 3], 0), ('split', [2, 2], 0)) + @ddt.data((ModuleStoreEnum.Type.mongo, [12, 3], 0), (ModuleStoreEnum.Type.split, [2, 2], 0)) @ddt.unpack def test_path_to_location(self, default_ms, num_finds, num_sends): """ @@ -1276,7 +1278,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): with the toy and simple courses loaded. """ # only needs course_locations set - self.initdb('draft') + self.initdb(ModuleStoreEnum.Type.mongo) course_key = self.course_locations[self.XML_COURSEID1].course_key video_key = course_key.make_usage_key('video', 'Welcome') chapter_key = course_key.make_usage_key('chapter', 'Overview') @@ -1310,7 +1312,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): self.assertEqual(5, navigation_index("5_2")) self.assertEqual(7, navigation_index("7_3_5_6_")) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_revert_to_published_root_draft(self, default_ms): """ Test calling revert_to_published on draft vertical. @@ -1342,7 +1344,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): self.assertBlocksEqualByFields(reverted_parent, published_parent) self.assertFalse(self._has_changes(self.vertical_x1a)) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_revert_to_published_root_published(self, default_ms): """ Test calling revert_to_published on a published vertical with a draft child. @@ -1362,7 +1364,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): reverted_problem = self.store.get_item(self.problem_x1a_1) self.assertEqual(orig_display_name, reverted_problem.display_name) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_revert_to_published_no_draft(self, default_ms): """ Test calling revert_to_published on vertical with no draft content does nothing. @@ -1377,7 +1379,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): self.assertBlocksEqualByFields(orig_vertical, reverted_vertical) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_revert_to_published_no_published(self, default_ms): """ Test calling revert_to_published on vertical with no published version errors. @@ -1387,7 +1389,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): with self.assertRaises(InvalidVersionError): self.store.revert_to_published(self.vertical_x1a, self.user_id) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_revert_to_published_direct_only(self, default_ms): """ Test calling revert_to_published on a direct-only item is a no-op. @@ -1402,7 +1404,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): # Draft: get all items which can be or should have parents # Split: active_versions, structure - @ddt.data(('draft', 1, 0), ('split', 2, 0)) + @ddt.data((ModuleStoreEnum.Type.mongo, 1, 0), (ModuleStoreEnum.Type.split, 2, 0)) @ddt.unpack def test_get_orphans(self, default_ms, max_find, max_send): """ @@ -1440,7 +1442,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): found_orphans = self.store.get_orphans(self.course_locations[self.MONGO_COURSEID].course_key) self.assertItemsEqual(found_orphans, orphan_locations) - @ddt.data('draft') + @ddt.data(ModuleStoreEnum.Type.mongo) def test_get_non_orphan_parents(self, default_ms): """ Test finding non orphan parents from many possible parents. @@ -1502,7 +1504,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): with self.assertRaises(ReferentialIntegrityError): self.store.get_parent_location(self.problem_x1a_1) - @ddt.data('draft') + @ddt.data(ModuleStoreEnum.Type.mongo) def test_create_item_from_parent_location(self, default_ms): """ Test a code path missed by the above: passing an old-style location as parent but no @@ -1518,7 +1520,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): orphans = self.store.get_orphans(self.course_locations[self.MONGO_COURSEID].course_key) self.assertEqual(len(orphans), 0, "unexpected orphans: {}".format(orphans)) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_create_item_populates_edited_info(self, default_ms): self.initdb(default_ms) block = self.store.create_item( @@ -1529,7 +1531,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): self.assertEqual(self.user_id, block.edited_by) self.assertGreater(datetime.datetime.now(UTC), block.edited_on) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_create_item_populates_subtree_edited_info(self, default_ms): self.initdb(default_ms) block = self.store.create_item( @@ -1542,7 +1544,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): # Draft: wildcard search of draft and split # Split: wildcard search of draft and split - @ddt.data(('draft', 2, 0), ('split', 2, 0)) + @ddt.data((ModuleStoreEnum.Type.mongo, 2, 0), (ModuleStoreEnum.Type.split, 2, 0)) @ddt.unpack def test_get_courses_for_wiki(self, default_ms, max_find, max_send): """ @@ -1580,14 +1582,14 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): # Sends: # - insert structure # - write index entry - @ddt.data(('draft', 2, 6), ('split', 3, 2)) + @ddt.data((ModuleStoreEnum.Type.mongo, 2, 6), (ModuleStoreEnum.Type.split, 3, 2)) @ddt.unpack def test_unpublish(self, default_ms, max_find, max_send): """ Test calling unpublish """ self.initdb(default_ms) - if default_ms == 'draft' and mongo_uses_error_check(self.store): + if default_ms == ModuleStoreEnum.Type.mongo and mongo_uses_error_check(self.store): max_find += 1 self._create_block_hierarchy() @@ -1618,7 +1620,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): # Draft: specific query for revision None # Split: active_versions, structure - @ddt.data(('draft', 1, 0), ('split', 2, 0)) + @ddt.data((ModuleStoreEnum.Type.mongo, 1, 0), (ModuleStoreEnum.Type.split, 2, 0)) @ddt.unpack def test_has_published_version(self, default_ms, max_find, max_send): """ @@ -1659,7 +1661,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): self.assertTrue(self.store.has_changes(item)) self.assertTrue(self.store.has_published_version(item)) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_update_edit_info_ancestors(self, default_ms): """ Tests that edited_on, edited_by, subtree_edited_on, and subtree_edited_by are set correctly during update @@ -1735,7 +1737,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): # Verify that others have unchanged edit info check_node(sibling.location, None, after_create, self.user_id, None, after_create, self.user_id) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_update_edit_info(self, default_ms): """ Tests that edited_on and edited_by are set correctly during an update @@ -1765,7 +1767,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): self.assertLess(old_edited_on, updated_component.edited_on) self.assertEqual(updated_component.edited_by, edit_user) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_update_published_info(self, default_ms): """ Tests that published_on and published_by are set correctly @@ -1799,7 +1801,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): self.assertLessEqual(old_time, updated_component.published_on) self.assertEqual(updated_component.published_by, publish_user) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_auto_publish(self, default_ms): """ Test that the correct things have been published automatically @@ -1869,7 +1871,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): chapter = self.store.get_item(chapter.location.for_branch(None)) self.assertTrue(self.store.has_published_version(chapter)) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_get_courses_for_wiki_shared(self, default_ms): """ Test two courses sharing the same wiki @@ -1924,7 +1926,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): wiki_courses ) - @ddt.data('draft', 'split') + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_branch_setting(self, default_ms): """ Test the branch_setting context manager @@ -2473,6 +2475,119 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): # Verify that the signal was emitted signal_handler.send.assert_called_with('course_deleted', course_key=course_key) + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) + def test_delete_published_item_orphans(self, default_store): + """ + Tests delete published item dont create any oprhans in course + """ + self.initdb(default_store) + course_locator = self.course.id + + chapter = self.store.create_child( + self.user_id, self.course.location, 'chapter', block_id='section_one' + ) + + sequential = self.store.create_child( + self.user_id, chapter.location, 'sequential', block_id='subsection_one' + ) + + vertical = self.store.create_child( + self.user_id, sequential.location, 'vertical', block_id='moon_unit' + ) + + problem = self.store.create_child( + self.user_id, vertical.location, 'problem', block_id='problem' + ) + + self.store.publish(chapter.location, self.user_id) + # Verify that there are no changes + self.assertFalse(self._has_changes(chapter.location)) + self.assertFalse(self._has_changes(sequential.location)) + self.assertFalse(self._has_changes(vertical.location)) + self.assertFalse(self._has_changes(problem.location)) + + # No orphans in course + course_orphans = self.store.get_orphans(course_locator) + self.assertEqual(len(course_orphans), 0) + self.store.delete_item(vertical.location, self.user_id) + + # No orphans in course after delete, except + # in old mongo, which still creates orphans + course_orphans = self.store.get_orphans(course_locator) + if default_store == ModuleStoreEnum.Type.mongo: + self.assertEqual(len(course_orphans), 1) + else: + self.assertEqual(len(course_orphans), 0) + + course_locator_publish = course_locator.for_branch(ModuleStoreEnum.BranchName.published) + # No published oprhans after delete, except + # in old mongo, which still creates orphans + course_publish_orphans = self.store.get_orphans(course_locator_publish) + + if default_store == ModuleStoreEnum.Type.mongo: + self.assertEqual(len(course_publish_orphans), 1) + else: + self.assertEqual(len(course_publish_orphans), 0) + + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) + def test_delete_draft_item_orphans(self, default_store): + """ + Tests delete draft item create no orphans in course + """ + self.initdb(default_store) + course_locator = self.course.id + + chapter = self.store.create_child( + self.user_id, self.course.location, 'chapter', block_id='section_one' + ) + + sequential = self.store.create_child( + self.user_id, chapter.location, 'sequential', block_id='subsection_one' + ) + + vertical = self.store.create_child( + self.user_id, sequential.location, 'vertical', block_id='moon_unit' + ) + + problem = self.store.create_child( + self.user_id, vertical.location, 'problem', block_id='problem' + ) + + self.store.publish(chapter.location, self.user_id) + # Verify that there are no changes + self.assertFalse(self._has_changes(chapter.location)) + self.assertFalse(self._has_changes(sequential.location)) + self.assertFalse(self._has_changes(vertical.location)) + self.assertFalse(self._has_changes(problem.location)) + + # No orphans in course + course_orphans = self.store.get_orphans(course_locator) + self.assertEqual(len(course_orphans), 0) + + problem.display_name = 'changed' + problem = self.store.update_item(problem, self.user_id) + self.assertTrue(self._has_changes(vertical.location)) + self.assertTrue(self._has_changes(problem.location)) + + self.store.delete_item(vertical.location, self.user_id) + # No orphans in course after delete, except + # in old mongo, which still creates them + course_orphans = self.store.get_orphans(course_locator) + if default_store == ModuleStoreEnum.Type.mongo: + self.assertEqual(len(course_orphans), 1) + else: + self.assertEqual(len(course_orphans), 0) + + course_locator_publish = course_locator.for_branch(ModuleStoreEnum.BranchName.published) + # No published orphans after delete, except + # in old mongo, which still creates them + course_publish_orphans = self.store.get_orphans(course_locator_publish) + + if default_store == ModuleStoreEnum.Type.mongo: + self.assertEqual(len(course_publish_orphans), 1) + else: + self.assertEqual(len(course_publish_orphans), 0) + @ddt.ddt @attr('mongo') diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_publish.py b/common/lib/xmodule/xmodule/modulestore/tests/test_publish.py index 02f24623fb..d35eafabc6 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_publish.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_publish.py @@ -1122,20 +1122,7 @@ class ElementalDeleteItemTests(DraftPublishedOpBaseTestSetup): self.assertOLXIsPublishedOnly(block_list_to_delete) self.delete_item(block_list_to_delete, revision=revision) self._check_for_item_deletion(block_list_to_delete, result) - # MODULESTORE_DIFFERENCE - if self.is_split_modulestore: - # Split: - if revision == ModuleStoreEnum.RevisionOption.published_only: - # If deleting published_only items, the children that are drafts remain. - self.assertOLXIsDraftOnly(block_list_children) - else: - self.assertOLXIsDeleted(block_list_children) - elif self.is_old_mongo_modulestore: - # Old Mongo: - # If deleting draft_only or both items, the drafts will be deleted. - self.assertOLXIsDeleted(block_list_children) - else: - raise Exception("Must test either Old Mongo or Split modulestore!") + self.assertOLXIsDeleted(block_list_children) @ddt.data(*itertools.product( MODULESTORE_SETUPS, @@ -1176,20 +1163,7 @@ class ElementalDeleteItemTests(DraftPublishedOpBaseTestSetup): self.delete_item(block_list_to_delete, revision=revision) self._check_for_item_deletion(block_list_to_delete, result) self.assertOLXIsDeleted(autopublished_children) - # MODULESTORE_DIFFERENCE - if self.is_split_modulestore: - # Split: - if revision == ModuleStoreEnum.RevisionOption.published_only: - # If deleting published_only items, the children that are drafts remain. - self.assertOLXIsDraftOnly(block_list_draft_children) - else: - self.assertOLXIsDeleted(block_list_draft_children) - elif self.is_old_mongo_modulestore: - # Old Mongo: - # If deleting draft_only or both items, the drafts will be deleted. - self.assertOLXIsDeleted(block_list_draft_children) - else: - raise Exception("Must test either Old Mongo or Split modulestore!") + self.assertOLXIsDeleted(block_list_draft_children) @ddt.ddt diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_semantics.py b/common/lib/xmodule/xmodule/modulestore/tests/test_semantics.py index ed354d8d57..5a3fd41689 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_semantics.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_semantics.py @@ -3,6 +3,7 @@ Tests of modulestore semantics: How do the interfaces methods of ModuleStore rel """ import ddt +import itertools from collections import namedtuple from xmodule.modulestore.tests.utils import ( @@ -51,45 +52,130 @@ class DirectOnlyCategorySemantics(PureModulestoreTestCase): modulestore=self.store ) - def assertBlockDoesntExist(self): - with self.assertRaises(ItemNotFoundError): - self.store.get_item(self.block_usage_key, revision=ModuleStoreEnum.RevisionOption.published_only) - with self.assertRaises(ItemNotFoundError): - self.store.get_item(self.block_usage_key, revision=ModuleStoreEnum.RevisionOption.draft_only) + def assertBlockDoesntExist(self, block_usage_key, draft=None): + """ + Verify that loading ``block_usage_key`` raises an ItemNotFoundError. - with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred): - with self.assertRaises(ItemNotFoundError): - self.store.get_item(self.block_usage_key) + Arguments: + block_usage_key: The xblock to check. + draft (optional): If omitted, verify both published and draft branches. + If True, verify only the draft branch. If False, verify only the + published branch. + """ + if draft is None or draft: + with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred): + with self.assertRaises(ItemNotFoundError): + self.store.get_item(block_usage_key) - with self.store.branch_setting(ModuleStoreEnum.Branch.published_only): - with self.assertRaises(ItemNotFoundError): - self.store.get_item(self.block_usage_key) + if draft is None or not draft: + with self.store.branch_setting(ModuleStoreEnum.Branch.published_only): + with self.assertRaises(ItemNotFoundError): + self.store.get_item(block_usage_key) - def assertBlockHasContent(self, test_data, content): - target_block = self.store.get_item( - self.block_usage_key, - revision=ModuleStoreEnum.RevisionOption.published_only - ) + def assertBlockHasContent(self, block_usage_key, field_name, content, draft=None): + """ + Assert that the block ``block_usage_key`` has the value ``content`` for ``field_name`` + when it is loaded. - self.assertEquals(content, target_block.fields[test_data.field_name].read_from(target_block)) + Arguments: + block_usage_key: The xblock to check. + field_name (string): The name of the field to check. + content: The value to assert is in the field. + draft (optional): If omitted, verify both published and draft branches. + If True, verify only the draft branch. If False, verify only the + published branch. + """ + if draft is None or not draft: + with self.store.branch_setting(ModuleStoreEnum.Branch.published_only): + target_block = self.store.get_item( + block_usage_key, + ) + self.assertEquals(content, target_block.fields[field_name].read_from(target_block)) - target_block = self.store.get_item( - self.block_usage_key, - revision=ModuleStoreEnum.RevisionOption.draft_only - ) - self.assertEquals(content, target_block.fields[test_data.field_name].read_from(target_block)) + if draft is None or draft: + with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred): + target_block = self.store.get_item( + block_usage_key, + ) + self.assertEquals(content, target_block.fields[field_name].read_from(target_block)) - with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred): - target_block = self.store.get_item( - self.block_usage_key, - ) - self.assertEquals(content, target_block.fields[test_data.field_name].read_from(target_block)) + def assertParentOf(self, parent_usage_key, child_usage_key, draft=None): + """ + Assert that the block ``parent_usage_key`` has ``child_usage_key`` listed + as one of its ``.children``. - with self.store.branch_setting(ModuleStoreEnum.Branch.published_only): - target_block = self.store.get_item( - self.block_usage_key, - ) - self.assertEquals(content, target_block.fields[test_data.field_name].read_from(target_block)) + Arguments: + parent_usage_key: The xblock to check as a parent. + child_usage_key: The xblock to check as a child. + draft (optional): If omitted, verify both published and draft branches. + If True, verify only the draft branch. If False, verify only the + published branch. + """ + if draft is None or not draft: + with self.store.branch_setting(ModuleStoreEnum.Branch.published_only): + parent_block = self.store.get_item( + parent_usage_key, + ) + self.assertIn(child_usage_key, parent_block.children) + + if draft is None or draft: + with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred): + parent_block = self.store.get_item( + parent_usage_key, + ) + self.assertIn(child_usage_key, parent_block.children) + + def assertNotParentOf(self, parent_usage_key, child_usage_key, draft=None): + """ + Assert that the block ``parent_usage_key`` does not have ``child_usage_key`` listed + as one of its ``.children``. + + Arguments: + parent_usage_key: The xblock to check as a parent. + child_usage_key: The xblock to check as a child. + draft (optional): If omitted, verify both published and draft branches. + If True, verify only the draft branch. If False, verify only the + published branch. + """ + if draft is None or not draft: + with self.store.branch_setting(ModuleStoreEnum.Branch.published_only): + parent_block = self.store.get_item( + parent_usage_key, + ) + self.assertNotIn(child_usage_key, parent_block.children) + + if draft is None or draft: + with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred): + parent_block = self.store.get_item( + parent_usage_key, + ) + self.assertNotIn(child_usage_key, parent_block.children) + + def assertCoursePointsToBlock(self, block_usage_key, draft=None): + """ + Assert that the context course for the test has ``block_usage_key`` listed + as one of its ``.children``. + + Arguments: + block_usage_key: The xblock to check. + draft (optional): If omitted, verify both published and draft branches. + If True, verify only the draft branch. If False, verify only the + published branch. + """ + self.assertParentOf(self.course.scope_ids.usage_id, block_usage_key, draft=draft) + + def assertCourseDoesntPointToBlock(self, block_usage_key, draft=None): + """ + Assert that the context course for the test does not have ``block_usage_key`` listed + as one of its ``.children``. + + Arguments: + block_usage_key: The xblock to check. + draft (optional): If omitted, verify both published and draft branches. + If True, verify only the draft branch. If False, verify only the + published branch. + """ + self.assertNotParentOf(self.course.scope_ids.usage_id, block_usage_key, draft=draft) def is_detached(self, block_type): """ @@ -109,9 +195,10 @@ class DirectOnlyCategorySemantics(PureModulestoreTestCase): and then verify that it was created successfully, and is visible in both published and draft branches. """ - self.block_usage_key = self.course.id.make_usage_key(block_type, 'test_block') + block_usage_key = self.course.id.make_usage_key(block_type, 'test_block') - self.assertBlockDoesntExist() + self.assertBlockDoesntExist(block_usage_key) + self.assertCourseDoesntPointToBlock(block_usage_key) test_data = self.DATA_FIELDS[block_type] @@ -122,8 +209,8 @@ class DirectOnlyCategorySemantics(PureModulestoreTestCase): block = self.store.create_xblock( self.course.runtime, self.course.id, - self.block_usage_key.block_type, - block_id=self.block_usage_key.block_id + block_usage_key.block_type, + block_id=block_usage_key.block_id ) block.fields[test_data.field_name].write_to(block, initial_field_value) self.store.update_item(block, ModuleStoreEnum.UserID.test, allow_not_found=True) @@ -132,18 +219,24 @@ class DirectOnlyCategorySemantics(PureModulestoreTestCase): user_id=ModuleStoreEnum.UserID.test, parent_usage_key=self.course.scope_ids.usage_id, block_type=block_type, - block_id=self.block_usage_key.block_id, + block_id=block_usage_key.block_id, fields={test_data.field_name: initial_field_value}, ) - self.assertBlockHasContent(test_data, initial_field_value) + if self.is_detached(block_type): + self.assertCourseDoesntPointToBlock(block_usage_key) + else: + self.assertCoursePointsToBlock(block_usage_key) + self.assertBlockHasContent(block_usage_key, test_data.field_name, initial_field_value) + + return block_usage_key @ddt.data(*TESTABLE_BLOCK_TYPES) def test_update(self, block_type): - self._do_create(block_type) + block_usage_key = self._do_create(block_type) with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred): - block = self.store.get_item(self.block_usage_key) + block = self.store.get_item(block_usage_key) test_data = self.DATA_FIELDS[block_type] @@ -154,16 +247,81 @@ class DirectOnlyCategorySemantics(PureModulestoreTestCase): self.store.update_item(block, ModuleStoreEnum.UserID.test, allow_not_found=True) - self.assertBlockHasContent(test_data, updated_field_value) + self.assertBlockHasContent(block_usage_key, test_data.field_name, updated_field_value) @ddt.data(*TESTABLE_BLOCK_TYPES) def test_delete(self, block_type): - self._do_create(block_type) + block_usage_key = self._do_create(block_type) with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred): - self.store.delete_item(self.block_usage_key, ModuleStoreEnum.UserID.test) + self.store.delete_item(block_usage_key, ModuleStoreEnum.UserID.test) - self.assertBlockDoesntExist() + self.assertCourseDoesntPointToBlock(block_usage_key) + self.assertBlockDoesntExist(block_usage_key) + + @ddt.data(*itertools.product(['chapter', 'sequential'], [True, False])) + @ddt.unpack + def test_delete_child(self, block_type, child_published): + block_usage_key = self.course.id.make_usage_key(block_type, 'test_block') + child_usage_key = self.course.id.make_usage_key('html', 'test_child') + + self.assertCourseDoesntPointToBlock(block_usage_key) + self.assertBlockDoesntExist(block_usage_key) + self.assertBlockDoesntExist(child_usage_key) + + test_data = self.DATA_FIELDS[block_type] + child_data = '
child value
' + + with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred): + self.store.create_child( + user_id=ModuleStoreEnum.UserID.test, + parent_usage_key=self.course.scope_ids.usage_id, + block_type=block_type, + block_id=block_usage_key.block_id, + fields={test_data.field_name: test_data.initial}, + ) + + self.store.create_child( + user_id=ModuleStoreEnum.UserID.test, + parent_usage_key=block_usage_key, + block_type=child_usage_key.block_type, + block_id=child_usage_key.block_id, + fields={'data': child_data}, + ) + + if child_published: + self.store.publish(child_usage_key, ModuleStoreEnum.UserID.test) + + self.assertCoursePointsToBlock(block_usage_key) + + if child_published: + self.assertParentOf(block_usage_key, child_usage_key) + else: + self.assertParentOf(block_usage_key, child_usage_key, draft=True) + # N.B. whether the direct-only parent block points to the child in the publish branch + # is left as undefined behavior + + self.assertBlockHasContent(block_usage_key, test_data.field_name, test_data.initial) + + if child_published: + self.assertBlockHasContent(child_usage_key, 'data', child_data) + else: + self.assertBlockHasContent(child_usage_key, 'data', child_data, draft=True) + self.assertBlockDoesntExist(child_usage_key, draft=False) + + with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred): + self.store.delete_item(child_usage_key, ModuleStoreEnum.UserID.test) + + self.assertCoursePointsToBlock(block_usage_key) + self.assertNotParentOf(block_usage_key, child_usage_key) + + if child_published and self.store.get_modulestore_type(self.course.id) == ModuleStoreEnum.Type.mongo: + # N.B. This block is being left as an orphan in old-mongo. This test will + # fail when that is fixed. At that time, this condition should just be removed, + # as SplitMongo and OldMongo will have the same semantics. + self.assertBlockHasContent(child_usage_key, 'data', child_data) + else: + self.assertBlockDoesntExist(child_usage_key) class TestSplitDirectOnlyCategorySemantics(DirectOnlyCategorySemantics):