Merge pull request #5759 from edx/dhm/plat-142
Add tests for clone_course
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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
|
||||
# ============================================================================================================
|
||||
|
||||
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user