From 6989999d8af63700570d12963f79cb2055285013 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Tue, 28 Oct 2014 09:44:33 -0400 Subject: [PATCH] Add tests for clone_course PLAT-142 --- .../lib/xmodule/xmodule/modulestore/mixed.py | 4 + .../tests/test_mixed_modulestore.py | 108 +++++++++++++++--- common/lib/xmodule/xmodule/tests/__init__.py | 33 +++--- 3 files changed, 115 insertions(+), 30 deletions(-) diff --git a/common/lib/xmodule/xmodule/modulestore/mixed.py b/common/lib/xmodule/xmodule/modulestore/mixed.py index 419d7a4dfd..652d09a52c 100644 --- a/common/lib/xmodule/xmodule/modulestore/mixed.py +++ b/common/lib/xmodule/xmodule/modulestore/mixed.py @@ -590,6 +590,10 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase): ) # the super handles assets and any other necessities super(MixedModuleStore, self).clone_course(source_course_id, dest_course_id, user_id, fields, **kwargs) + else: + raise NotImplementedError("No code for cloning from {} to {}".format( + source_modulestore, dest_modulestore + )) @strip_key def create_item(self, user_id, course_key, block_type, block_id=None, fields=None, **kwargs): 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 fc17af92b9..152d7cf26a 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py @@ -6,7 +6,6 @@ import datetime import ddt import itertools import pymongo -import unittest from collections import namedtuple from importlib import import_module @@ -19,6 +18,10 @@ from uuid import uuid4 from django.conf import settings from xmodule.modulestore.edit_info import EditInfoMixin from xmodule.modulestore.inheritance import InheritanceMixin +from xmodule.modulestore.tests.test_cross_modulestore_import_export import MongoContentstoreBuilder +from xmodule.contentstore.content import StaticContent +import mimetypes +from opaque_keys.edx.keys import CourseKey if not settings.configured: settings.configure() @@ -70,12 +73,12 @@ class TestMixedModuleStore(CourseComparisonTest): 'collection': COLLECTION, 'asset_collection': ASSET_COLLECTION, } + MAPPINGS = { + XML_COURSEID1: 'xml', + XML_COURSEID2: 'xml', + BAD_COURSE_ID: 'xml', + } OPTIONS = { - 'mappings': { - XML_COURSEID1: 'xml', - XML_COURSEID2: 'xml', - BAD_COURSE_ID: 'xml', - }, 'stores': [ { 'NAME': 'draft', @@ -95,6 +98,7 @@ class TestMixedModuleStore(CourseComparisonTest): 'OPTIONS': { 'data_dir': DATA_DIR, 'default_class': 'xmodule.hidden_module.HiddenDescriptor', + 'xblock_mixins': (EditInfoMixin, InheritanceMixin), } }, ] @@ -111,6 +115,16 @@ class TestMixedModuleStore(CourseComparisonTest): """ Set up the database for testing """ + super(TestMixedModuleStore, self).setUp() + + self.exclude_field(None, 'wiki_slug') + self.exclude_field(None, 'xml_attributes') + self.exclude_field(None, 'parent') + self.ignore_asset_key('_id') + self.ignore_asset_key('uploadDate') + self.ignore_asset_key('content_son') + self.ignore_asset_key('thumbnail_location') + self.options = getattr(self, 'options', self.OPTIONS) self.connection = pymongo.MongoClient( host=self.HOST, @@ -120,13 +134,12 @@ class TestMixedModuleStore(CourseComparisonTest): self.connection.drop_database(self.DB) self.addCleanup(self.connection.drop_database, self.DB) self.addCleanup(self.connection.close) - super(TestMixedModuleStore, self).setUp() self.addTypeEqualityFunc(BlockUsageLocator, '_compare_ignore_version') self.addTypeEqualityFunc(CourseLocator, '_compare_ignore_version') # define attrs which get set in initdb to quell pylint self.writable_chapter_location = self.store = self.fake_location = self.xml_chapter_location = None - self.course_locations = [] + self.course_locations = {} self.user_id = ModuleStoreEnum.UserID.test @@ -217,11 +230,16 @@ class TestMixedModuleStore(CourseComparisonTest): """ return self.course_locations[string].course_key - def _initialize_mixed(self): + # pylint: disable=dangerous-default-value + def _initialize_mixed(self, mappings=MAPPINGS, contentstore=None): """ - initializes the mixed modulestore + initializes the mixed modulestore. """ - self.store = MixedModuleStore(None, create_modulestore_instance=create_modulestore_instance, **self.options) + self.store = MixedModuleStore( + contentstore, create_modulestore_instance=create_modulestore_instance, + mappings=mappings, + **self.options + ) self.addCleanup(self.store.close_all_connections) def initdb(self, default): @@ -300,7 +318,7 @@ class TestMixedModuleStore(CourseComparisonTest): """ Make sure we get back the store type we expect for given mappings """ - self._initialize_mixed() + self._initialize_mixed(mappings={}) with self.store.default_store(default_ms): self.store.create_course('org_x', 'course_y', 'run_z', self.user_id) if reset_mixed_mappings: @@ -1759,7 +1777,7 @@ class TestMixedModuleStore(CourseComparisonTest): Test the default store context manager """ # initialize the mixed modulestore - self._initialize_mixed() + self._initialize_mixed(mappings={}) with self.store.default_store(default_ms): self.verify_default_store(default_ms) @@ -1769,7 +1787,7 @@ class TestMixedModuleStore(CourseComparisonTest): Test the default store context manager, nested within one another """ # initialize the mixed modulestore - self._initialize_mixed() + self._initialize_mixed(mappings={}) with self.store.default_store(ModuleStoreEnum.Type.mongo): self.verify_default_store(ModuleStoreEnum.Type.mongo) @@ -1785,13 +1803,73 @@ class TestMixedModuleStore(CourseComparisonTest): Test the default store context manager, asking for a fake store """ # initialize the mixed modulestore - self._initialize_mixed() + self._initialize_mixed(mappings={}) fake_store = "fake" with self.assertRaisesRegexp(Exception, "Cannot find store of type {}".format(fake_store)): with self.store.default_store(fake_store): pass # pragma: no cover + def save_asset(self, asset_key): + """ + Load and save the given file. (taken from test_contentstore) + """ + with open("{}/static/{}".format(DATA_DIR, asset_key.block_id), "rb") as f: + content = StaticContent( + asset_key, "Funky Pix", mimetypes.guess_type(asset_key.block_id)[0], f.read(), + ) + self.store.contentstore.save(content) + + @ddt.data( + [ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.mongo], + [ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split], + [ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.split] + ) + @ddt.unpack + def test_clone_course(self, source_modulestore, destination_modulestore): + """ + Test clone course + """ + + with MongoContentstoreBuilder().build() as contentstore: + # initialize the mixed modulestore + self._initialize_mixed(contentstore=contentstore, mappings={}) + + with self.store.default_store(source_modulestore): + + source_course_key = self.store.make_course_key("org.source", "course.source", "run.source") + self._create_course(source_course_key) + self.save_asset(source_course_key.make_asset_key('asset', 'picture1.jpg')) + + with self.store.default_store(destination_modulestore): + dest_course_id = self.store.make_course_key("org.other", "course.other", "run.other") + self.store.clone_course(source_course_key, dest_course_id, self.user_id) + + # pylint: disable=protected-access + source_store = self.store._get_modulestore_by_type(source_modulestore) + dest_store = self.store._get_modulestore_by_type(destination_modulestore) + self.assertCoursesEqual(source_store, source_course_key, dest_store, dest_course_id) + + def test_clone_xml_split(self): + """ + Can clone xml courses to split; so, test it. + """ + with MongoContentstoreBuilder().build() as contentstore: + # initialize the mixed modulestore + self._initialize_mixed(contentstore=contentstore, mappings={self.XML_COURSEID2: 'xml', }) + source_course_key = CourseKey.from_string(self.XML_COURSEID2) + with self.store.default_store(ModuleStoreEnum.Type.split): + dest_course_id = CourseLocator("org.other", "course.other", "run.other") + self.store.clone_course( + source_course_key, dest_course_id, ModuleStoreEnum.UserID.test + ) + + # pylint: disable=protected-access + source_store = self.store._get_modulestore_by_type(ModuleStoreEnum.Type.xml) + dest_store = self.store._get_modulestore_by_type(ModuleStoreEnum.Type.split) + self.assertCoursesEqual(source_store, source_course_key, dest_store, dest_course_id) + + # ============================================================================================================ # General utils for not using django settings # ============================================================================================================ diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py index 86abf9fd43..960f57c16f 100644 --- a/common/lib/xmodule/xmodule/tests/__init__.py +++ b/common/lib/xmodule/xmodule/tests/__init__.py @@ -28,7 +28,7 @@ from xmodule.mako_module import MakoDescriptorSystem from xmodule.error_module import ErrorDescriptor from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.mongo.draft import DraftModuleStore -from xmodule.modulestore.draft_and_published import DIRECT_ONLY_CATEGORIES +from xmodule.modulestore.draft_and_published import DIRECT_ONLY_CATEGORIES, ModuleStoreDraftAndPublished MODULE_DIR = path(__file__).dirname() @@ -249,6 +249,7 @@ class LazyFormat(object): def __repr__(self): return unicode(self) + class CourseComparisonTest(BulkAssertionTest): """ Mixin that has methods for comparing courses for equality. @@ -354,20 +355,22 @@ class CourseComparisonTest(BulkAssertionTest): self.assertGreater(len(expected_items), 0) self._assertCoursesEqual(expected_items, actual_items, actual_course_key) - with expected_store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, expected_course_key): - with actual_store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, actual_course_key): - # compare draft - if expected_store.get_modulestore_type(None) == ModuleStoreEnum.Type.split: - revision = ModuleStoreEnum.RevisionOption.draft_only - else: - revision = None - expected_items = expected_store.get_items(expected_course_key, revision=revision) - if actual_store.get_modulestore_type(None) == ModuleStoreEnum.Type.split: - revision = ModuleStoreEnum.RevisionOption.draft_only - else: - revision = None - actual_items = actual_store.get_items(actual_course_key, revision=revision) - self._assertCoursesEqual(expected_items, actual_items, actual_course_key, expect_drafts=True) + # if the modulestore supports having a draft branch + if isinstance(expected_store, ModuleStoreDraftAndPublished): + with expected_store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, expected_course_key): + with actual_store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, actual_course_key): + # compare draft + if expected_store.get_modulestore_type(None) == ModuleStoreEnum.Type.split: + revision = ModuleStoreEnum.RevisionOption.draft_only + else: + revision = None + expected_items = expected_store.get_items(expected_course_key, revision=revision) + if actual_store.get_modulestore_type(None) == ModuleStoreEnum.Type.split: + revision = ModuleStoreEnum.RevisionOption.draft_only + else: + revision = None + actual_items = actual_store.get_items(actual_course_key, revision=revision) + self._assertCoursesEqual(expected_items, actual_items, actual_course_key, expect_drafts=True) def _assertCoursesEqual(self, expected_items, actual_items, actual_course_key, expect_drafts=False): with self.bulk_assertions():