Commit e65730b9 by Calen Pennington

Rationalize all cases around direct-only categories (both as blocks and parents) and delete

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