def initdb(cls): # connect to the db doc_store_config = { 'host': HOST, 'db': DB, 'collection': COLLECTION, } # since MongoModuleStore and MongoContentStore are basically assumed to be together, create this class # as well content_store = MongoContentStore(HOST, DB) # # Also test draft store imports # draft_store = DraftModuleStore( content_store, doc_store_config, FS_ROOT, RENDER_TEMPLATE, default_class=DEFAULT_CLASS, branch_setting_func=lambda: ModuleStoreEnum.Branch.draft_preferred) import_from_xml(draft_store, 999, DATA_DIR, cls.courses, static_content_store=content_store) # also test a course with no importing of static content import_from_xml(draft_store, 999, DATA_DIR, ['test_import_course'], static_content_store=content_store, do_import_static=False, verbose=True) return content_store, draft_store
def load_test_import_course(self, target_course_id=None, create_new_course_if_not_present=False): ''' Load the standard course used to test imports (for do_import_static=False behavior). ''' content_store = contentstore() module_store = modulestore() import_from_xml( module_store, self.user.id, TEST_DATA_DIR, ['test_import_course'], static_content_store=content_store, do_import_static=False, verbose=True, target_course_id=target_course_id, create_new_course_if_not_present=create_new_course_if_not_present, ) course_id = module_store.make_course_key('edX', 'test_import_course', '2012_Fall') course = module_store.get_course(course_id) self.assertIsNotNone(course) return module_store, content_store, course
def setUp(self): super(TestMongoCoursesLoad, self).setUp() self.setup_user() # Import the toy course into a Mongo-backed modulestore self.store = modulestore() import_from_xml(self.store, TEST_DATA_DIR, ['toy'])
def setUp(self): """ Create user and login. """ self.staff_pwd = super(ContentStoreToyCourseTest, self).setUp() self.staff_usr = self.user self.non_staff_usr, self.non_staff_pwd = self.create_non_staff_user() self.client = Client() self.contentstore = contentstore() store = modulestore()._get_modulestore_by_type(ModuleStoreEnum.Type.mongo) # pylint: disable=protected-access self.course_key = store.make_course_key('edX', 'toy', '2012_Fall') import_from_xml( store, self.user.id, TEST_DATA_DIR, ['toy'], static_content_store=self.contentstore, verbose=True ) # A locked asset self.locked_asset = self.course_key.make_asset_key('asset', 'sample_static.txt') self.url_locked = unicode(self.locked_asset) self.contentstore.set_attr(self.locked_asset, 'locked', True) # An unlocked asset self.unlocked_asset = self.course_key.make_asset_key('asset', 'another_static.txt') self.url_unlocked = unicode(self.unlocked_asset) self.length_unlocked = self.contentstore.get_attr(self.unlocked_asset, 'length')
def check_components_on_page(self, component_types, expected_types): """ Ensure that the right types end up on the page. component_types is the list of advanced components. expected_types is the list of elements that should appear on the page. expected_types and component_types should be similar, but not exactly the same -- for example, 'videoalpha' in component_types should cause 'Video Alpha' to be present. """ store = modulestore('direct') import_from_xml(store, 'common/test/data/', ['simple']) course = store.get_item(Location( ['i4x', 'edX', 'simple', 'course', '2012_Fall', None]), depth=None) course.advanced_modules = component_types store.update_metadata(course.location, own_metadata(course)) # just pick one vertical descriptor = store.get_items( Location('i4x', 'edX', 'simple', 'vertical', None, None))[0] resp = self.client.get( reverse('edit_unit', kwargs={'location': descriptor.location.url()})) self.assertEqual(resp.status_code, 200) for expected in expected_types: self.assertIn(expected, resp.content)
def test_toy_textbooks_loads(self): module_store = modulestore() import_from_xml(module_store, TEST_DATA_DIR, ['toy']) course = module_store.get_item(Location(['i4x', 'edX', 'toy', 'course', '2012_Fall', None])) self.assertGreater(len(course.textbooks), 0)
def test_prefetch_children(self): module_store = modulestore('direct') import_from_xml(module_store, 'common/test/data/', ['full']) location = CourseDescriptor.id_to_location( 'edX/full/6.002_Spring_2012') wrapper = MongoCollectionFindWrapper(module_store.collection.find) module_store.collection.find = wrapper.find course = module_store.get_item(location, depth=2) # make sure we haven't done too many round trips to DB # note we say 4 round trips here for 1) the course, 2 & 3) for the chapters and sequentials, and # 4) because of the RT due to calculating the inherited metadata self.assertEqual(wrapper.counter, 4) # make sure we pre-fetched a known sequential which should be at depth=2 self.assertTrue( Location([ 'i4x', 'edX', 'full', 'sequential', 'Administrivia_and_Circuit_Elements', None ]) in course.system.module_data) # make sure we don't have a specific vertical which should be at depth=3 self.assertFalse( Location(['i4x', 'edX', 'full', 'vertical', 'vertical_58', None]) in course.system.module_data)
def test_static_tab_reordering(self): module_store = modulestore('direct') import_from_xml(module_store, 'common/test/data/', ['full']) course = module_store.get_item( Location( ['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None])) # reverse the ordering reverse_tabs = [] for tab in course.tabs: if tab['type'] == 'static_tab': reverse_tabs.insert( 0, 'i4x://edX/full/static_tab/{0}'.format(tab['url_slug'])) self.client.post(reverse('reorder_static_tabs'), json.dumps({'tabs': reverse_tabs}), "application/json") course = module_store.get_item( Location( ['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None])) # compare to make sure that the tabs information is in the expected order after the server call course_tabs = [] for tab in course.tabs: if tab['type'] == 'static_tab': course_tabs.append('i4x://edX/full/static_tab/{0}'.format( tab['url_slug'])) self.assertEqual(reverse_tabs, course_tabs)
def test_get_depth_with_drafts(self): import_from_xml(modulestore('direct'), 'common/test/data/', ['simple']) course = modulestore('draft').get_item(Location( ['i4x', 'edX', 'simple', 'course', '2012_Fall', None]), depth=None) # make sure no draft items have been returned num_drafts = self._get_draft_counts(course) self.assertEqual(num_drafts, 0) problem = modulestore('draft').get_item( Location(['i4x', 'edX', 'simple', 'problem', 'ps01-simple', None])) # put into draft modulestore('draft').clone_item(problem.location, problem.location) # make sure we can query that item and verify that it is a draft draft_problem = modulestore('draft').get_item( Location(['i4x', 'edX', 'simple', 'problem', 'ps01-simple', None])) self.assertTrue(getattr(draft_problem, 'is_draft', False)) #now requery with depth course = modulestore('draft').get_item(Location( ['i4x', 'edX', 'simple', 'course', '2012_Fall', None]), depth=None) # make sure just one draft item have been returned num_drafts = self._get_draft_counts(course) self.assertEqual(num_drafts, 1)
def setUp(self): """ Create user and login. """ self.staff_pwd = super(ContentStoreToyCourseTest, self).setUp() self.staff_usr = self.user self.non_staff_usr, self.non_staff_pwd = self.create_non_staff_user() self.client = Client() self.contentstore = contentstore() self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') import_from_xml( modulestore(), self.user.id, 'common/test/data/', ['toy'], static_content_store=self.contentstore, verbose=True ) # A locked asset self.locked_asset = self.course_key.make_asset_key('asset', 'sample_static.txt') self.url_locked = self.locked_asset.to_deprecated_string() self.contentstore.set_attr(self.locked_asset, 'locked', True) # An unlocked asset self.unlocked_asset = self.course_key.make_asset_key('asset', 'another_static.txt') self.url_unlocked = self.unlocked_asset.to_deprecated_string() self.length_unlocked = self.contentstore.get_attr(self.unlocked_asset, 'length')
def initdb(): # connect to the db doc_store_config = { 'host': HOST, 'db': DB, 'collection': COLLECTION, } store = MongoModuleStore(doc_store_config, FS_ROOT, RENDER_TEMPLATE, default_class=DEFAULT_CLASS) # since MongoModuleStore and MongoContentStore are basically assumed to be together, create this class # as well content_store = MongoContentStore(HOST, DB) # # Also test draft store imports # draft_store = DraftModuleStore(doc_store_config, FS_ROOT, RENDER_TEMPLATE, default_class=DEFAULT_CLASS) # Explicitly list the courses to load (don't want the big one) courses = ['toy', 'simple', 'simple_with_draft', 'test_unicode'] import_from_xml(store, DATA_DIR, courses, draft_store=draft_store, static_content_store=content_store) # also test a course with no importing of static content import_from_xml( store, DATA_DIR, ['test_import_course'], static_content_store=content_store, do_import_static=False, verbose=True ) return store, content_store, draft_store
def test_update_modulestore_signal_did_fire(self): module_store = modulestore('direct') import_from_xml(module_store, 'common/test/data/', ['full']) try: module_store.modulestore_update_signal = Signal( providing_args=['modulestore', 'course_id', 'location']) self.got_signal = False def _signal_hander(modulestore=None, course_id=None, location=None, **kwargs): self.got_signal = True module_store.modulestore_update_signal.connect(_signal_hander) new_component_location = Location('i4x', 'edX', 'full', 'html', 'new_component') source_template_location = Location('i4x', 'edx', 'templates', 'html', 'Blank_HTML_Page') # crate a new module module_store.clone_item(source_template_location, new_component_location) finally: module_store.modulestore_update_signal = None self.assertTrue(self.got_signal)
def initdb(): # connect to the db doc_store_config = { 'host': HOST, 'db': DB, 'collection': COLLECTION, } store = MongoModuleStore(doc_store_config, FS_ROOT, RENDER_TEMPLATE, default_class=DEFAULT_CLASS) # since MongoModuleStore and MongoContentStore are basically assumed to be together, create this class # as well content_store = MongoContentStore(HOST, DB) # # Also test draft store imports # draft_store = DraftModuleStore(doc_store_config, FS_ROOT, RENDER_TEMPLATE, default_class=DEFAULT_CLASS) import_from_xml(store, DATA_DIR, TestMongoModuleStore.courses, draft_store=draft_store, static_content_store=content_store) # also test a course with no importing of static content import_from_xml(store, DATA_DIR, ['test_import_course'], static_content_store=content_store, do_import_static=False, verbose=True) return store, content_store, draft_store
def _assert_import(self, course_dir, expected_xblock_loc, expected_field_val, has_draft=False): """ Import a course from XML, then verify that the XBlock was loaded with the correct field value. Args: course_dir (str): The name of the course directory (relative to the test data directory) expected_xblock_loc (str): The location of the XBlock in the course. expected_field_val (str): The expected value of the XBlock's test field. Kwargs: has_draft (bool): If true, check that a draft of the XBlock exists with the expected field value set. """ import_from_xml( self.store, 'common/test/data', [course_dir], draft_store=self.draft_store ) xblock = self.store.get_item(expected_xblock_loc) self.assertTrue(isinstance(xblock, StubXBlock)) self.assertEqual(xblock.test_field, expected_field_val) if has_draft: draft_xblock = self.draft_store.get_item(expected_xblock_loc) self.assertTrue(isinstance(draft_xblock, StubXBlock)) self.assertEqual(draft_xblock.test_field, expected_field_val)
def test_get_depth_with_drafts(self): import_from_xml(modulestore('direct'), 'common/test/data/', ['simple']) course = modulestore('draft').get_item( Location(['i4x', 'edX', 'simple', 'course', '2012_Fall', None]), depth=None ) # make sure no draft items have been returned num_drafts = self._get_draft_counts(course) self.assertEqual(num_drafts, 0) problem = modulestore('draft').get_item( Location(['i4x', 'edX', 'simple', 'problem', 'ps01-simple', None]) ) # put into draft modulestore('draft').clone_item(problem.location, problem.location) # make sure we can query that item and verify that it is a draft draft_problem = modulestore('draft').get_item( Location(['i4x', 'edX', 'simple', 'problem', 'ps01-simple', None]) ) self.assertTrue(getattr(draft_problem, 'is_draft', False)) #now requery with depth course = modulestore('draft').get_item( Location(['i4x', 'edX', 'simple', 'course', '2012_Fall', None]), depth=None ) # make sure just one draft item have been returned num_drafts = self._get_draft_counts(course) self.assertEqual(num_drafts, 1)
def check_components_on_page(self, component_types, expected_types): """ Ensure that the right types end up on the page. component_types is the list of advanced components. expected_types is the list of elements that should appear on the page. expected_types and component_types should be similar, but not exactly the same -- for example, 'videoalpha' in component_types should cause 'Video Alpha' to be present. """ store = modulestore('direct') import_from_xml(store, 'common/test/data/', ['simple']) course = store.get_item(Location(['i4x', 'edX', 'simple', 'course', '2012_Fall', None]), depth=None) course.advanced_modules = component_types store.update_metadata(course.location, own_metadata(course)) # just pick one vertical descriptor = store.get_items(Location('i4x', 'edX', 'simple', 'vertical', None, None))[0] resp = self.client.get(reverse('edit_unit', kwargs={'location': descriptor.location.url()})) self.assertEqual(resp.status_code, 200) for expected in expected_types: self.assertIn(expected, resp.content)
def initdb(cls): # connect to the db doc_store_config = {"host": HOST, "port": PORT, "db": DB, "collection": COLLECTION} cls.add_asset_collection(doc_store_config) # since MongoModuleStore and MongoContentStore are basically assumed to be together, create this class # as well content_store = MongoContentStore(HOST, DB, port=PORT) # # Also test draft store imports # draft_store = DraftModuleStore( content_store, doc_store_config, FS_ROOT, RENDER_TEMPLATE, default_class=DEFAULT_CLASS, branch_setting_func=lambda: ModuleStoreEnum.Branch.draft_preferred, xblock_mixins=(EditInfoMixin,), ) import_from_xml(draft_store, 999, DATA_DIR, cls.courses, static_content_store=content_store) # also test a course with no importing of static content import_from_xml( draft_store, 999, DATA_DIR, ["test_import_course"], static_content_store=content_store, do_import_static=False, verbose=True, ) return content_store, draft_store
def test_delete(self): direct_store = modulestore('direct') import_from_xml(direct_store, 'common/test/data/', ['full']) sequential = direct_store.get_item(Location(['i4x', 'edX', 'full', 'sequential', 'Administrivia_and_Circuit_Elements', None])) chapter = direct_store.get_item(Location(['i4x', 'edX', 'full', 'chapter', 'Week_1', None])) # make sure the parent points to the child object which is to be deleted self.assertTrue(sequential.location.url() in chapter.children) self.client.post( reverse('delete_item'), json.dumps({'id': sequential.location.url(), 'delete_children': 'true', 'delete_all_versions': 'true'}), "application/json" ) found = False try: direct_store.get_item(Location(['i4x', 'edX', 'full', 'sequential', 'Administrivia_and_Circuit_Elements', None])) found = True except ItemNotFoundError: pass self.assertFalse(found) chapter = direct_store.get_item(Location(['i4x', 'edX', 'full', 'chapter', 'Week_1', None])) # make sure the parent no longer points to the child object which was deleted self.assertFalse(sequential.location.url() in chapter.children)
def test_toy_textbooks_loads(self): module_store = modulestore() import_from_xml(module_store, TEST_DATA_DIR, ["toy"]) course = module_store.get_item(Location(["i4x", "edX", "toy", "course", "2012_Fall", None])) self.assertGreater(len(course.textbooks), 0)
def test_get_items(self): ''' This verifies a bug we had where the None setting in get_items() meant 'wildcard' Unfortunately, None = published for the revision field, so get_items() would return both draft and non-draft copies. ''' store = modulestore('direct') draft_store = modulestore('draft') import_from_xml(store, 'common/test/data/', ['simple']) html_module = draft_store.get_item( ['i4x', 'edX', 'simple', 'html', 'test_html', None]) draft_store.clone_item(html_module.location, html_module.location) # now query get_items() to get this location with revision=None, this should just # return back a single item (not 2) items = store.get_items( ['i4x', 'edX', 'simple', 'html', 'test_html', None]) self.assertEqual(len(items), 1) self.assertFalse(getattr(items[0], 'is_draft', False)) # now refetch from the draft store. Note that even though we pass # None in the revision field, the draft store will replace that with 'draft' items = draft_store.get_items( ['i4x', 'edX', 'simple', 'html', 'test_html', None]) self.assertEqual(len(items), 1) self.assertTrue(getattr(items[0], 'is_draft', False))
def initdb(): # connect to the db doc_store_config = { 'host': HOST, 'db': DB, 'collection': COLLECTION, } store = MongoModuleStore( doc_store_config, FS_ROOT, RENDER_TEMPLATE, default_class=DEFAULT_CLASS, xblock_mixins=(XModuleMixin,) ) # since MongoModuleStore and MongoContentStore are basically assumed to be together, create this class # as well content_store = MongoContentStore(HOST, DB) # # Also test draft store imports # draft_store = DraftModuleStore(doc_store_config, FS_ROOT, RENDER_TEMPLATE, default_class=DEFAULT_CLASS) import_from_xml(store, DATA_DIR, TestMongoModuleStore.courses, draft_store=draft_store, static_content_store=content_store) # also test a course with no importing of static content import_from_xml( store, DATA_DIR, ['test_import_course'], static_content_store=content_store, do_import_static=False, verbose=True ) return store, content_store, draft_store
def test_update_modulestore_signal_did_fire(self): module_store = modulestore('direct') import_from_xml(module_store, 'common/test/data/', ['full']) try: module_store.modulestore_update_signal = Signal( providing_args=['modulestore', 'course_id', 'location']) self.got_signal = False def _signal_hander(modulestore=None, course_id=None, location=None, **kwargs): self.got_signal = True module_store.modulestore_update_signal.connect(_signal_hander) new_component_location = Location( 'i4x', 'edX', 'full', 'html', 'new_component') source_template_location = Location( 'i4x', 'edx', 'templates', 'html', 'Blank_HTML_Page') # crate a new module module_store.clone_item( source_template_location, new_component_location) finally: module_store.modulestore_update_signal = None self.assertTrue(self.got_signal)
def test_get_items(self): ''' This verifies a bug we had where the None setting in get_items() meant 'wildcard' Unfortunately, None = published for the revision field, so get_items() would return both draft and non-draft copies. ''' store = modulestore('direct') draft_store = modulestore('draft') import_from_xml(store, 'common/test/data/', ['simple']) html_module = draft_store.get_item(['i4x', 'edX', 'simple', 'html', 'test_html', None]) draft_store.clone_item(html_module.location, html_module.location) # now query get_items() to get this location with revision=None, this should just # return back a single item (not 2) items = store.get_items(['i4x', 'edX', 'simple', 'html', 'test_html', None]) self.assertEqual(len(items), 1) self.assertFalse(getattr(items[0], 'is_draft', False)) # now refetch from the draft store. Note that even though we pass # None in the revision field, the draft store will replace that with 'draft' items = draft_store.get_items(['i4x', 'edX', 'simple', 'html', 'test_html', None]) self.assertEqual(len(items), 1) self.assertTrue(getattr(items[0], 'is_draft', False))
def setUp(self): """ Create user and login. """ self.staff_pwd = super(ContentStoreToyCourseTest, self).setUp() self.staff_usr = self.user self.non_staff_usr, self.non_staff_pwd = self.create_non_staff_user() self.client = Client() self.contentstore = contentstore() self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') import_from_xml(modulestore(), self.user.id, 'common/test/data/', ['toy'], static_content_store=self.contentstore, verbose=True) # A locked asset self.locked_asset = self.course_key.make_asset_key('asset', 'sample_static.txt') self.url_locked = self.locked_asset.to_deprecated_string() # An unlocked asset self.unlocked_asset = self.course_key.make_asset_key('asset', 'another_static.txt') self.url_unlocked = self.unlocked_asset.to_deprecated_string() self.contentstore.set_attr(self.locked_asset, 'locked', True)
def test_round_trip(self, source_builder, dest_builder, source_content_builder, dest_content_builder, course_data_name): # Construct the contentstore for storing the first import with source_content_builder.build() as source_content: # Construct the modulestore for storing the first import (using the previously created contentstore) with source_builder.build(source_content) as source_store: # Construct the contentstore for storing the second import with dest_content_builder.build() as dest_content: # Construct the modulestore for storing the second import (using the second contentstore) with dest_builder.build(dest_content) as dest_store: source_course_key = source_store.make_course_key('source', 'course', 'key') dest_course_key = dest_store.make_course_key('dest', 'course', 'key') import_from_xml( source_store, 'test_user', 'common/test/data', course_dirs=[course_data_name], static_content_store=source_content, target_course_id=source_course_key, create_new_course_if_not_present=True, ) export_to_xml( source_store, source_content, source_course_key, self.export_dir, 'exported_course', ) import_from_xml( dest_store, 'test_user', self.export_dir, static_content_store=dest_content, target_course_id=dest_course_key, create_new_course_if_not_present=True, ) self.exclude_field(None, 'wiki_slug') self.exclude_field(None, 'xml_attributes') self.ignore_asset_key('_id') self.ignore_asset_key('uploadDate') self.ignore_asset_key('content_son') self.ignore_asset_key('thumbnail_location') self.assertCoursesEqual( source_store, source_course_key, dest_store, dest_course_key, ) self.assertAssetsEqual( source_content, source_course_key, dest_content, dest_course_key, )
def test_full_textbooks_loads(self): module_store = modulestore() import_from_xml(module_store, TEST_DATA_DIR, ['full']) course = module_store.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None])) self.assertGreater(len(course.textbooks), 0)
def setUp(self): CourseTestCase.setUp(self) # add in the full class too import_from_xml(get_modulestore( self.course_location), 'common/test/data/', ['full']) self.fullcourse_location = Location( ['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None])
def initdb(): # connect to the db store = MongoModuleStore(HOST, DB, COLLECTION, FS_ROOT, RENDER_TEMPLATE, default_class=DEFAULT_CLASS) # Explicitly list the courses to load (don't want the big one) courses = ['toy', 'simple'] import_from_xml(store, DATA_DIR, courses) return store
def test_generate_find_timings(self, source_ms, num_assets): """ Generate timings for different amounts of asset metadata and different modulestores. """ if CodeBlockTimer is None: raise SkipTest("CodeBlockTimer undefined.") desc = "FindAssetTest:{}:{}".format( SHORT_NAME_MAP[source_ms], num_assets, ) with CodeBlockTimer(desc): with CodeBlockTimer("fake_assets"): # First, make the fake asset metadata. make_asset_xml(num_assets, ASSET_XML_PATH) validate_xml(ASSET_XSD_PATH, ASSET_XML_PATH) # Construct the contentstore for storing the first import with MongoContentstoreBuilder().build() as source_content: # Construct the modulestore for storing the first import (using the previously created contentstore) with source_ms.build(source_content) as source_store: source_course_key = source_store.make_course_key('a', 'course', 'course') asset_key = source_course_key.make_asset_key( AssetMetadata.GENERAL_ASSET_TYPE, 'silly_cat_picture.gif' ) with CodeBlockTimer("initial_import"): import_from_xml( source_store, 'test_user', TEST_DATA_ROOT, course_dirs=TEST_COURSE, static_content_store=source_content, target_course_id=source_course_key, create_course_if_not_present=True, raise_on_failure=True, ) with CodeBlockTimer("find_nonexistent_asset"): # More correct would be using the AssetManager.find() - but since the test # has created its own test modulestore, the AssetManager can't be used. __ = source_store.find_asset_metadata(asset_key) # Perform get_all_asset_metadata for each sort. for sort in ALL_SORTS: with CodeBlockTimer("get_asset_list:{}-{}".format( sort[0], 'asc' if sort[1] == ModuleStoreEnum.SortOrder.ascending else 'desc' )): # Grab two ranges of 50 assets using different sorts. # Why 50? That's how many are displayed on the current Studio "Files & Uploads" page. start_middle = num_assets / 2 __ = source_store.get_all_asset_metadata( source_course_key, 'asset', start=0, sort=sort, maxresults=50 ) __ = source_store.get_all_asset_metadata( source_course_key, 'asset', start=start_middle, sort=sort, maxresults=50 )
def test_rewrite_reference_list(self): # This test fails with split modulestore (the HTML component is not in "different_course_id" namespace). # More investigation needs to be done. module_store = modulestore()._get_modulestore_by_type(ModuleStoreEnum.Type.mongo) target_course_id = module_store.make_course_key('testX', 'conditional_copy', 'copy_run') import_from_xml( module_store, self.user.id, TEST_DATA_DIR, ['conditional'], target_course_id=target_course_id ) conditional_module = module_store.get_item( target_course_id.make_usage_key('conditional', 'condone') ) self.assertIsNotNone(conditional_module) different_course_id = module_store.make_course_key('edX', 'different_course', None) self.assertListEqual( [ target_course_id.make_usage_key('problem', 'choiceprob'), different_course_id.make_usage_key('html', 'for_testing_import_rewrites') ], conditional_module.sources_list ) self.assertListEqual( [ target_course_id.make_usage_key('html', 'congrats'), target_course_id.make_usage_key('html', 'secret_page') ], conditional_module.show_tag_list )
def test_clone_course(self): course_data = { 'template': 'i4x://edx/templates/course/Empty', 'org': 'MITx', 'number': '999', 'display_name': 'Robot Super Course', } module_store = modulestore('direct') import_from_xml(module_store, 'common/test/data/', ['full']) resp = self.client.post(reverse('create_new_course'), course_data) self.assertEqual(resp.status_code, 200) data = parse_json(resp) self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course') content_store = contentstore() source_location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012') dest_location = CourseDescriptor.id_to_location('MITx/999/Robot_Super_Course') clone_course(module_store, content_store, source_location, dest_location) # now loop through all the units in the course and verify that the clone can render them, which # means the objects are at least present items = module_store.get_items(Location(['i4x', 'edX', 'full', 'vertical', None])) self.assertGreater(len(items), 0) clone_items = module_store.get_items(Location(['i4x', 'MITx', '999', 'vertical', None])) self.assertGreater(len(clone_items), 0) for descriptor in items: new_loc = descriptor.location.replace(org='MITx', course='999') print "Checking {0} should now also be at {1}".format(descriptor.location.url(), new_loc.url()) resp = self.client.get(reverse('edit_unit', kwargs={'location': new_loc.url()})) self.assertEqual(resp.status_code, 200)
def test_import_textbook_as_content_element(self): module_store = modulestore('direct') import_from_xml(module_store, 'common/test/data/', ['full']) course = module_store.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None])) self.assertGreater(len(course.textbooks), 0)
def test_clone_course(self): course_data = { "template": "i4x://edx/templates/course/Empty", "org": "MITx", "number": "999", "display_name": "Robot Super Course", } module_store = modulestore("direct") import_from_xml(module_store, "common/test/data/", ["full"]) resp = self.client.post(reverse("create_new_course"), course_data) self.assertEqual(resp.status_code, 200) data = parse_json(resp) self.assertEqual(data["id"], "i4x://MITx/999/course/Robot_Super_Course") content_store = contentstore() source_location = CourseDescriptor.id_to_location("edX/full/6.002_Spring_2012") dest_location = CourseDescriptor.id_to_location("MITx/999/Robot_Super_Course") clone_course(module_store, content_store, source_location, dest_location) # now loop through all the units in the course and verify that the clone can render them, which # means the objects are at least present items = module_store.get_items(Location(["i4x", "edX", "full", "vertical", None])) self.assertGreater(len(items), 0) clone_items = module_store.get_items(Location(["i4x", "MITx", "999", "vertical", None])) self.assertGreater(len(clone_items), 0) for descriptor in items: new_loc = descriptor.location.replace(org="MITx", course="999") print "Checking {0} should now also be at {1}".format(descriptor.location.url(), new_loc.url()) resp = self.client.get(reverse("edit_unit", kwargs={"location": new_loc.url()})) self.assertEqual(resp.status_code, 200)
def test_rewrite_reference_list(self): module_store = modulestore('direct') target_location = Location(['i4x', 'testX', 'conditional_copy', 'course', 'copy_run']) import_from_xml( module_store, 'common/test/data/', ['conditional'], target_location_namespace=target_location ) conditional_module = module_store.get_item( Location(['i4x', 'testX', 'conditional_copy', 'conditional', 'condone']) ) self.assertIsNotNone(conditional_module) self.assertListEqual( [ u'i4x://testX/conditional_copy/problem/choiceprob', u'i4x://edX/different_course/html/for_testing_import_rewrites' ], conditional_module.sources_list ) self.assertListEqual( [ u'i4x://testX/conditional_copy/html/congrats', u'i4x://testX/conditional_copy/html/secret_page' ], conditional_module.show_tag_list )
def test_delete(self): direct_store = modulestore("direct") import_from_xml(direct_store, "common/test/data/", ["full"]) sequential = direct_store.get_item( Location(["i4x", "edX", "full", "sequential", "Administrivia_and_Circuit_Elements", None]) ) chapter = direct_store.get_item(Location(["i4x", "edX", "full", "chapter", "Week_1", None])) # make sure the parent points to the child object which is to be deleted self.assertTrue(sequential.location.url() in chapter.children) self.client.post( reverse("delete_item"), json.dumps({"id": sequential.location.url(), "delete_children": "true", "delete_all_versions": "true"}), "application/json", ) found = False try: direct_store.get_item( Location(["i4x", "edX", "full", "sequential", "Administrivia_and_Circuit_Elements", None]) ) found = True except ItemNotFoundError: pass self.assertFalse(found) chapter = direct_store.get_item(Location(["i4x", "edX", "full", "chapter", "Week_1", None])) # make sure the parent no longer points to the child object which was deleted self.assertFalse(sequential.location.url() in chapter.children)
def test_remove_hide_progress_tab(self): module_store = modulestore("direct") import_from_xml(module_store, "common/test/data/", ["full"]) source_location = CourseDescriptor.id_to_location("edX/full/6.002_Spring_2012") course = module_store.get_item(source_location) self.assertFalse(course.hide_progress_tab)
def test_get_depth_with_drafts(self): import_from_xml(modulestore("direct"), "common/test/data/", ["simple"]) course = modulestore("draft").get_item( Location(["i4x", "edX", "simple", "course", "2012_Fall", None]), depth=None ) # make sure no draft items have been returned num_drafts = self._get_draft_counts(course) self.assertEqual(num_drafts, 0) problem = modulestore("draft").get_item(Location(["i4x", "edX", "simple", "problem", "ps01-simple", None])) # put into draft modulestore("draft").clone_item(problem.location, problem.location) # make sure we can query that item and verify that it is a draft draft_problem = modulestore("draft").get_item( Location(["i4x", "edX", "simple", "problem", "ps01-simple", None]) ) self.assertTrue(getattr(draft_problem, "is_draft", False)) # now requery with depth course = modulestore("draft").get_item( Location(["i4x", "edX", "simple", "course", "2012_Fall", None]), depth=None ) # make sure just one draft item have been returned num_drafts = self._get_draft_counts(course) self.assertEqual(num_drafts, 1)
def test_import_textbook_as_content_element(self): module_store = modulestore("direct") import_from_xml(module_store, "common/test/data/", ["full"]) course = module_store.get_item(Location(["i4x", "edX", "full", "course", "6.002_Spring_2012", None])) self.assertGreater(len(course.textbooks), 0)
def test_locking(self): """ Tests a simple locking and unlocking of an asset in the toy course. """ def verify_asset_locked_state(locked): """ Helper method to verify lock state in the contentstore """ asset_location = StaticContent.get_location_from_path('/c4x/edX/toy/asset/sample_static.txt') content = contentstore().find(asset_location) self.assertEqual(content.locked, locked) def post_asset_update(lock): """ Helper method for posting asset update. """ upload_date = datetime(2013, 6, 1, 10, 30, tzinfo=UTC) location = Location(['c4x', 'edX', 'toy', 'asset', 'sample_static.txt']) url = reverse('update_asset', kwargs={'org': 'edX', 'course': 'toy', 'name': '2012_Fall'}) resp = self.client.post(url, json.dumps(assets._get_asset_json("sample_static.txt", upload_date, location, None, lock)), "application/json") self.assertEqual(resp.status_code, 201) return json.loads(resp.content) # Load the toy course. module_store = modulestore('direct') import_from_xml(module_store, 'common/test/data/', ['toy'], static_content_store=contentstore(), verbose=True) verify_asset_locked_state(False) # Lock the asset resp_asset = post_asset_update(True) self.assertTrue(resp_asset['locked']) verify_asset_locked_state(True) # Unlock the asset resp_asset = post_asset_update(False) self.assertFalse(resp_asset['locked']) verify_asset_locked_state(False)
def test_rewrite_reference_list(self): module_store = modulestore() target_course_id = SlashSeparatedCourseKey('testX', 'conditional_copy', 'copy_run') import_from_xml( module_store, self.user.id, 'common/test/data/', ['conditional'], target_course_id=target_course_id ) conditional_module = module_store.get_item( target_course_id.make_usage_key('conditional', 'condone') ) self.assertIsNotNone(conditional_module) different_course_id = SlashSeparatedCourseKey('edX', 'different_course', None) self.assertListEqual( [ target_course_id.make_usage_key('problem', 'choiceprob'), different_course_id.make_usage_key('html', 'for_testing_import_rewrites') ], conditional_module.sources_list ) self.assertListEqual( [ target_course_id.make_usage_key('html', 'congrats'), target_course_id.make_usage_key('html', 'secret_page') ], conditional_module.show_tag_list )
def setUp(self): CourseTestCase.setUp(self) # add in the full class too import_from_xml(get_modulestore(self.course_location), 'common/test/data/', ['full']) self.fullcourse_location = Location( ['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None])
def test_remove_hide_progress_tab(self): module_store = modulestore('direct') import_from_xml(module_store, 'common/test/data/', ['full']) source_location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012') course = module_store.get_item(source_location) self.assertFalse(course.hide_progress_tab)
def test_remove_hide_progress_tab(self): module_store = modulestore('direct') import_from_xml(module_store, 'common/test/data/', ['full']) source_location = CourseDescriptor.id_to_location( 'edX/full/6.002_Spring_2012') course = module_store.get_item(source_location) self.assertFalse(course.hide_progress_tab)
def initdb(): # connect to the db store = MongoModuleStore(HOST, DB, COLLECTION, FS_ROOT, RENDER_TEMPLATE, default_class=DEFAULT_CLASS) # Explicitly list the courses to load (don't want the big one) courses = ['toy', 'simple'] import_from_xml(store, DATA_DIR, courses) update_templates(store) return store
def check_edit_unit(self, test_course_name): import_from_xml(modulestore('direct'), 'common/test/data/', [test_course_name]) for descriptor in modulestore().get_items(Location(None, None, 'vertical', None, None)): print "Checking ", descriptor.location.url() print descriptor.__class__, descriptor.location resp = self.client.get(reverse('edit_unit', kwargs={'location': descriptor.location.url()})) self.assertEqual(resp.status_code, 200)
def test_generate_import_export_timings(self, source_ms, dest_ms, num_assets): """ Generate timings for different amounts of asset metadata and different modulestores. """ if CodeBlockTimer is None: raise SkipTest("CodeBlockTimer undefined.") desc = "XMLRoundTrip:{}->{}:{}".format(SHORT_NAME_MAP[source_ms], SHORT_NAME_MAP[dest_ms], num_assets) with CodeBlockTimer(desc): with CodeBlockTimer("fake_assets"): # First, make the fake asset metadata. make_asset_xml(num_assets, ASSET_XML_PATH) validate_xml(ASSET_XSD_PATH, ASSET_XML_PATH) with source_ms.build() as (source_content, source_store): with dest_ms.build() as (dest_content, dest_store): source_course_key = source_store.make_course_key( 'a', 'course', 'course') dest_course_key = dest_store.make_course_key( 'a', 'course', 'course') with CodeBlockTimer("initial_import"): import_from_xml( source_store, 'test_user', TEST_DATA_ROOT, course_dirs=TEST_COURSE, static_content_store=source_content, target_course_id=source_course_key, create_course_if_not_present=True, raise_on_failure=True, ) with CodeBlockTimer("export"): export_to_xml( source_store, source_content, source_course_key, self.export_dir, 'exported_source_course', ) with CodeBlockTimer("second_import"): import_from_xml( dest_store, 'test_user', self.export_dir, course_dirs=['exported_source_course'], static_content_store=dest_content, target_course_id=dest_course_key, create_course_if_not_present=True, raise_on_failure=True, )
def test_import_textbook_as_content_element(self): module_store = modulestore('direct') import_from_xml(module_store, 'common/test/data/', ['full']) course = module_store.get_item( Location( ['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None])) self.assertGreater(len(course.textbooks), 0)
def load_courses(self): """Load test courses and return list of ids""" store = modulestore() courses = store.get_courses() if TEST_COURSE_ID not in [c.id for c in courses]: import_from_xml(store, DATA_DIR, ['toy', 'simple']) return [course.id for course in store.get_courses()]
def test_no_static_link_rewrites_on_import(self): module_store = modulestore('direct') import_from_xml(module_store, 'common/test/data/', ['toy'], do_import_static=False, verbose=True) handouts = module_store.get_item(Location(['i4x', 'edX', 'toy', 'course_info', 'handouts', None])) self.assertIn('/static/', handouts.data) handouts = module_store.get_item(Location(['i4x', 'edX', 'toy', 'html', 'toyhtml', None])) self.assertIn('/static/', handouts.data)
def test_full_textbooks_loads(self): module_store = modulestore() import_from_xml(module_store, TEST_DATA_DIR, ['full']) course = module_store.get_item( Location( ['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None])) self.assertGreater(len(course.textbooks), 0)
def test_asset_sizes(self, source_ms, num_assets): """ Generate timings for different amounts of asset metadata and different modulestores. """ # First, make the fake asset metadata. make_asset_xml(num_assets, ASSET_XML_PATH) validate_xml(ASSET_XSD_PATH, ASSET_XML_PATH) # Construct the contentstore for storing the first import with MongoContentstoreBuilder().build() as source_content: # Construct the modulestore for storing the first import (using the previously created contentstore) with source_ms.build(source_content) as source_store: source_course_key = source_store.make_course_key( 'a', 'course', 'course') import_from_xml( source_store, 'test_user', TEST_DATA_ROOT, course_dirs=TEST_COURSE, static_content_store=source_content, target_course_id=source_course_key, create_course_if_not_present=True, raise_on_failure=True, ) asset_collection = source_ms.asset_collection() # Ensure the asset collection exists. if asset_collection.name in asset_collection.database.collection_names( ): # Map gets the size of each structure. mapper = Code(""" function() { emit("size", (this == null) ? 0 : Object.bsonsize(this)) } """) # Reduce finds the largest structure size and returns only it. reducer = Code(""" function(key, values) { var max_size = 0; for (var i=0; i < values.length; i++) { if (values[i] > max_size) { max_size = values[i]; } } return max_size; } """) results = asset_collection.map_reduce( mapper, reducer, "size_results") result_str = "{} - Store: {:<15} - Num Assets: {:>6} - Result: {}\n".format( self.test_run_time, SHORT_NAME_MAP[source_ms], num_assets, [r for r in results.find()]) with open("bson_sizes.txt", "a") as f: f.write(result_str)
def test_malformed_edit_unit_request(self): store = modulestore('direct') import_from_xml(store, 'common/test/data/', ['simple']) # just pick one vertical descriptor = store.get_items(Location('i4x', 'edX', 'simple', 'vertical', None, None))[0] location = descriptor.location._replace(name='.' + descriptor.location.name) resp = self.client.get(reverse('edit_unit', kwargs={'location': location.url()})) self.assertEqual(resp.status_code, 400)
def test_import_polls(self): module_store = modulestore('direct') import_from_xml(module_store, 'common/test/data/', ['full']) items = module_store.get_items(['i4x', 'edX', 'full', 'poll_question', None, None]) found = len(items) > 0 self.assertTrue(found) # check that there's actually content in the 'question' field self.assertGreater(len(items[0].question), 0)
def test_delete_course(self): module_store = modulestore('direct') import_from_xml(module_store, 'common/test/data/', ['full']) content_store = contentstore() location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012') delete_course(module_store, content_store, location, commit=True) items = module_store.get_items(Location(['i4x', 'edX', 'full', 'vertical', None])) self.assertEqual(len(items), 0)
def load_test_import_course(self): ''' Load the standard course used to test imports (for do_import_static=False behavior). ''' content_store = contentstore() module_store = modulestore('direct') import_from_xml(module_store, 'common/test/data/', ['test_import_course'], static_content_store=content_store, do_import_static=False, verbose=True) course_location = CourseDescriptor.id_to_location('edX/test_import_course/2012_Fall') course = module_store.get_item(course_location) self.assertIsNotNone(course) return module_store, content_store, course, course_location
def import_and_populate_course(self): """ Imports the test toy course and populates it with additional test data """ content_store = contentstore() import_from_xml(self.store, self.user.id, 'common/test/data/', ['toy'], static_content_store=content_store) course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') # create an Orphan # We had a bug where orphaned draft nodes caused export to fail. This is here to cover that case. vertical = self.store.get_item(course_id.make_usage_key('vertical', self.TEST_VERTICAL), depth=1) vertical.location = vertical.location.replace(name='no_references') self.store.update_item(vertical, self.user.id, allow_not_found=True) orphan_vertical = self.store.get_item(vertical.location) self.assertEqual(orphan_vertical.location.name, 'no_references') self.assertEqual(len(orphan_vertical.children), len(vertical.children)) # create a Draft vertical vertical = self.store.get_item(course_id.make_usage_key('vertical', self.TEST_VERTICAL), depth=1) draft_vertical = self.store.convert_to_draft(vertical.location, self.user.id) self.assertTrue(self.store.has_published_version(draft_vertical)) # create a Private (draft only) vertical private_vertical = self.store.create_item(self.user.id, course_id, 'vertical', self.PRIVATE_VERTICAL) self.assertFalse(self.store.has_published_version(private_vertical)) # create a Published (no draft) vertical public_vertical = self.store.create_item(self.user.id, course_id, 'vertical', self.PUBLISHED_VERTICAL) public_vertical = self.store.publish(public_vertical.location, self.user.id) self.assertTrue(self.store.has_published_version(public_vertical)) # add the new private and new public as children of the sequential sequential = self.store.get_item(course_id.make_usage_key('sequential', self.SEQUENTIAL)) sequential.children.append(private_vertical.location) sequential.children.append(public_vertical.location) self.store.update_item(sequential, self.user.id) # lock an asset content_store.set_attr(self.LOCKED_ASSET_KEY, 'locked', True) # create a non-portable link - should be rewritten in new courses html_module = self.store.get_item(course_id.make_usage_key('html', 'nonportable')) new_data = html_module.data = html_module.data.replace( '/static/', '/c4x/{0}/{1}/asset/'.format(course_id.org, course_id.course) ) self.store.update_item(html_module, self.user.id) html_module = self.store.get_item(html_module.location) self.assertEqual(new_data, html_module.data) return course_id
def test_metadata_inheritance(self): module_store = modulestore('direct') import_from_xml(module_store, 'common/test/data/', ['full']) course = module_store.get_item( Location( ['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None])) verticals = module_store.get_items( ['i4x', 'edX', 'full', 'vertical', None, None]) # let's assert on the metadata_inheritance on an existing vertical for vertical in verticals: self.assertEqual(course.lms.xqa_key, vertical.lms.xqa_key) self.assertGreater(len(verticals), 0) new_component_location = Location('i4x', 'edX', 'full', 'html', 'new_component') source_template_location = Location('i4x', 'edx', 'templates', 'html', 'Blank_HTML_Page') # crate a new module and add it as a child to a vertical module_store.clone_item(source_template_location, new_component_location) parent = verticals[0] module_store.update_children( parent.location, parent.children + [new_component_location.url()]) # flush the cache module_store.refresh_cached_metadata_inheritance_tree( new_component_location) new_module = module_store.get_item(new_component_location) # check for grace period definition which should be defined at the course level self.assertEqual(parent.lms.graceperiod, new_module.lms.graceperiod) self.assertEqual(course.lms.xqa_key, new_module.lms.xqa_key) # # now let's define an override at the leaf node level # new_module.lms.graceperiod = timedelta(1) module_store.update_metadata(new_module.location, own_metadata(new_module)) # flush the cache and refetch module_store.refresh_cached_metadata_inheritance_tree( new_component_location) new_module = module_store.get_item(new_component_location) self.assertEqual(timedelta(1), new_module.lms.graceperiod)
def test_export_all_courses(self): """ This test validates that redundant Mac metadata files ('._example.txt', '.DS_Store') are cleaned up on import """ import_from_xml(self.module_store, '**replace_user**', 'common/test/data/', ['dot-underscore'], static_content_store=self.content_store, do_import_static=True, verbose=True) course = self.module_store.get_course( SlashSeparatedCourseKey('edX', 'dot-underscore', '2014_Fall')) self.assertIsNotNone(course) # check that there are two assets ['example.txt', '.example.txt'] in contentstore for imported course all_assets, count = self.content_store.get_all_content_for_course( course.id) self.assertEqual(count, 2) self.assertEqual(set([asset['_id']['name'] for asset in all_assets]), set([u'.example.txt', u'example.txt'])) # manually add redundant assets (file ".DS_Store" and filename starts with "._") course_filter = course.id.make_asset_key("asset", None) query = location_to_query(course_filter, wildcard=True, tag=XASSET_LOCATION_TAG) query['_id.name'] = all_assets[0]['_id']['name'] asset_doc = self.content_store.fs_files.find_one(query) asset_doc['_id']['name'] = u'._example_test.txt' self.content_store.fs_files.insert(asset_doc) asset_doc['_id']['name'] = u'.DS_Store' self.content_store.fs_files.insert(asset_doc) # check that now course has four assets all_assets, count = self.content_store.get_all_content_for_course( course.id) self.assertEqual(count, 4) self.assertEqual( set([asset['_id']['name'] for asset in all_assets]), set([ u'.example.txt', u'example.txt', u'._example_test.txt', u'.DS_Store' ])) # now call asset_cleanup command and check that there is only two proper assets in contentstore for the course call_command('cleanup_assets') all_assets, count = self.content_store.get_all_content_for_course( course.id) self.assertEqual(count, 2) self.assertEqual(set([asset['_id']['name'] for asset in all_assets]), set([u'.example.txt', u'example.txt']))