test: update test_contentstore.py for split modulestore
This commit is contained in:
committed by
David Ormsbee
parent
f3de63058c
commit
3877201dd2
@@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
import copy
|
||||
import re
|
||||
import shutil
|
||||
from datetime import timedelta
|
||||
from functools import wraps
|
||||
@@ -34,6 +35,8 @@ from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from xmodule.modulestore.inheritance import own_metadata
|
||||
from xmodule.modulestore.split_mongo import BlockKey
|
||||
from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, check_mongo_calls
|
||||
from xmodule.modulestore.xml_exporter import export_course_to_xml
|
||||
from xmodule.modulestore.xml_importer import import_course_from_xml, perform_xlint
|
||||
@@ -83,6 +86,7 @@ class ContentStoreTestCase(CourseTestCase):
|
||||
"""
|
||||
Base class for Content Store Test Cases
|
||||
"""
|
||||
MODULESTORE = TEST_DATA_SPLIT_MODULESTORE
|
||||
|
||||
|
||||
class ImportRequiredTestCases(ContentStoreTestCase):
|
||||
@@ -212,7 +216,7 @@ class ImportRequiredTestCases(ContentStoreTestCase):
|
||||
all_thumbnails = content_store.get_all_content_thumbnails_for_course(course.id)
|
||||
self.assertGreater(len(all_thumbnails), 0)
|
||||
|
||||
location = AssetKey.from_string('/c4x/edX/toy/asset/just_a_test.jpg')
|
||||
location = AssetKey.from_string('asset-v1:edX+toy+2012_Fall+type@asset+block@just_a_test.jpg')
|
||||
content = content_store.find(location)
|
||||
self.assertIsNotNone(content)
|
||||
|
||||
@@ -282,7 +286,7 @@ class ImportRequiredTestCases(ContentStoreTestCase):
|
||||
)
|
||||
|
||||
# first check a static asset link
|
||||
course_key = self.store.make_course_key('edX', 'toy', 'run')
|
||||
course_key = self.store.make_course_key('edX', 'toy', '2012_Fall')
|
||||
html_block_location = course_key.make_usage_key('html', 'nonportable')
|
||||
html_block = self.store.get_item(html_block_location)
|
||||
self.assertIn('/static/foo.jpg', html_block.data)
|
||||
@@ -302,101 +306,6 @@ class ImportRequiredTestCases(ContentStoreTestCase):
|
||||
filesystem = OSFS(root_dir / ('test_export/' + dirname))
|
||||
self.assertTrue(filesystem.exists(item.location.block_id + filename_suffix))
|
||||
|
||||
@mock.patch('xmodule.course_block.requests.get')
|
||||
def test_export_course_roundtrip(self, mock_get):
|
||||
mock_get.return_value.text = dedent("""
|
||||
<?xml version="1.0"?><table_of_contents>
|
||||
<entry page="5" page_label="ii" name="Table of Contents"/>
|
||||
</table_of_contents>
|
||||
""").strip()
|
||||
|
||||
content_store = contentstore()
|
||||
course_id = self.import_and_populate_course()
|
||||
|
||||
root_dir = path(mkdtemp_clean())
|
||||
print(f'Exporting to tempdir = {root_dir}')
|
||||
|
||||
# export out to a tempdir
|
||||
export_course_to_xml(self.store, content_store, course_id, root_dir, 'test_export')
|
||||
|
||||
# check for static tabs
|
||||
self.verify_content_existence(self.store, root_dir, course_id, 'tabs', 'static_tab', '.html')
|
||||
|
||||
# check for about content
|
||||
self.verify_content_existence(self.store, root_dir, course_id, 'about', 'about', '.html')
|
||||
|
||||
# assert that there is an html and video directory in drafts:
|
||||
draft_dir = OSFS(root_dir / 'test_export/drafts')
|
||||
self.assertTrue(draft_dir.exists('html'))
|
||||
self.assertTrue(draft_dir.exists('video'))
|
||||
# and assert that they contain the created modules
|
||||
self.assertIn(self.DRAFT_HTML + ".xml", draft_dir.listdir('html'))
|
||||
self.assertIn(self.DRAFT_VIDEO + ".xml", draft_dir.listdir('video'))
|
||||
# and assert the child of the orphaned draft wasn't exported
|
||||
self.assertNotIn(self.ORPHAN_DRAFT_HTML + ".xml", draft_dir.listdir('html'))
|
||||
|
||||
# check for grading_policy.json
|
||||
filesystem = OSFS(root_dir / 'test_export/policies/2012_Fall')
|
||||
self.assertTrue(filesystem.exists('grading_policy.json'))
|
||||
|
||||
course = self.store.get_course(course_id)
|
||||
# compare what's on disk compared to what we have in our course
|
||||
with filesystem.open('grading_policy.json', 'r') as grading_policy:
|
||||
on_disk = loads(grading_policy.read())
|
||||
self.assertEqual(on_disk, course.grading_policy)
|
||||
|
||||
# check for policy.json
|
||||
self.assertTrue(filesystem.exists('policy.json'))
|
||||
|
||||
# compare what's on disk to what we have in the course block
|
||||
with filesystem.open('policy.json', 'r') as course_policy:
|
||||
on_disk = loads(course_policy.read())
|
||||
self.assertIn('course/2012_Fall', on_disk)
|
||||
self.assertEqual(on_disk['course/2012_Fall'], own_metadata(course))
|
||||
|
||||
# remove old course
|
||||
self.store.delete_course(course_id, self.user.id)
|
||||
|
||||
# reimport over old course
|
||||
self.check_import(root_dir, content_store, course_id)
|
||||
|
||||
# import to different course id
|
||||
new_course_id = self.store.make_course_key('anotherX', 'anotherToy', 'Someday')
|
||||
self.check_import(root_dir, content_store, new_course_id)
|
||||
self.assertCoursesEqual(course_id, new_course_id)
|
||||
|
||||
shutil.rmtree(root_dir)
|
||||
|
||||
def check_import(self, root_dir, content_store, course_id):
|
||||
"""Imports the course in root_dir into the given course_id and verifies its content"""
|
||||
# reimport
|
||||
import_course_from_xml(
|
||||
self.store,
|
||||
self.user.id,
|
||||
root_dir,
|
||||
['test_export'],
|
||||
static_content_store=content_store,
|
||||
target_id=course_id,
|
||||
)
|
||||
|
||||
# verify content of the course
|
||||
self.check_populated_course(course_id)
|
||||
|
||||
# verify additional export attributes
|
||||
def verify_export_attrs_removed(attributes):
|
||||
"""Verifies all temporary attributes added during export are removed"""
|
||||
self.assertNotIn('index_in_children_list', attributes)
|
||||
self.assertNotIn('parent_sequential_url', attributes)
|
||||
self.assertNotIn('parent_url', attributes)
|
||||
|
||||
vertical = self.store.get_item(course_id.make_usage_key('vertical', self.TEST_VERTICAL))
|
||||
verify_export_attrs_removed(vertical.xml_attributes)
|
||||
|
||||
for child in vertical.get_children():
|
||||
verify_export_attrs_removed(child.xml_attributes)
|
||||
if hasattr(child, 'data'):
|
||||
verify_export_attrs_removed(child.data)
|
||||
|
||||
def test_export_course_with_metadata_only_video(self):
|
||||
content_store = contentstore()
|
||||
|
||||
@@ -552,7 +461,8 @@ class ImportRequiredTestCases(ContentStoreTestCase):
|
||||
import_course_from_xml(
|
||||
self.store, self.user.id, root_dir, ['test_export_no_content_store'],
|
||||
static_content_store=None,
|
||||
target_id=course_id
|
||||
target_id=course_id,
|
||||
create_if_not_present=True
|
||||
)
|
||||
|
||||
# Verify reimported course
|
||||
@@ -560,7 +470,6 @@ class ImportRequiredTestCases(ContentStoreTestCase):
|
||||
items = self.store.get_items(
|
||||
course_id,
|
||||
qualifiers={
|
||||
'category': 'sequential',
|
||||
'name': 'vertical_sequential',
|
||||
}
|
||||
)
|
||||
@@ -734,7 +643,7 @@ class MiscCourseTests(ContentStoreTestCase):
|
||||
information but the draft child xblock has parent information.
|
||||
"""
|
||||
# Make an existing unit a draft
|
||||
self.store.convert_to_draft(self.problem.location, self.user.id)
|
||||
self.problem = self.store.unpublish(self.problem.location, self.user.id)
|
||||
root_dir = path(mkdtemp_clean())
|
||||
export_course_to_xml(self.store, None, self.course.id, root_dir, 'test_export')
|
||||
|
||||
@@ -786,19 +695,12 @@ class MiscCourseTests(ContentStoreTestCase):
|
||||
def test_advanced_components_require_two_clicks(self):
|
||||
self.check_components_on_page(['word_cloud'], ['Word cloud'])
|
||||
|
||||
def test_malformed_edit_unit_request(self):
|
||||
# just pick one vertical
|
||||
usage_key = self.course.id.make_usage_key('vertical', None)
|
||||
|
||||
resp = self.client.get_html(get_url('container_handler', usage_key))
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
|
||||
def test_edit_unit(self):
|
||||
"""Verifies rendering the editor in all the verticals in the given test course"""
|
||||
self._check_verticals([self.vert_loc])
|
||||
|
||||
def _get_draft_counts(self, item): # lint-amnesty, pylint: disable=missing-function-docstring
|
||||
cnt = 1 if getattr(item, 'is_draft', False) else 0
|
||||
cnt = 1 if not self.store.has_published_version(item) else 0
|
||||
for child in item.get_children():
|
||||
cnt = cnt + self._get_draft_counts(child)
|
||||
|
||||
@@ -810,15 +712,14 @@ class MiscCourseTests(ContentStoreTestCase):
|
||||
Unfortunately, None = published for the revision field, so get_items() would return
|
||||
both draft and non-draft copies.
|
||||
"""
|
||||
self.store.convert_to_draft(self.problem.location, self.user.id)
|
||||
self.problem = self.store.unpublish(self.problem.location, self.user.id)
|
||||
|
||||
# Query get_items() and find the html item. This should just return back a single item (not 2).
|
||||
direct_store_items = self.store.get_items(
|
||||
self.course.id, revision=ModuleStoreEnum.RevisionOption.published_only
|
||||
)
|
||||
items_from_direct_store = [item for item in direct_store_items if item.location == self.problem.location]
|
||||
self.assertEqual(len(items_from_direct_store), 1)
|
||||
self.assertFalse(getattr(items_from_direct_store[0], 'is_draft', False))
|
||||
self.assertEqual(len(items_from_direct_store), 0)
|
||||
|
||||
# Fetch from the draft store.
|
||||
draft_store_items = self.store.get_items(
|
||||
@@ -826,8 +727,6 @@ class MiscCourseTests(ContentStoreTestCase):
|
||||
)
|
||||
items_from_draft_store = [item for item in draft_store_items if item.location == self.problem.location]
|
||||
self.assertEqual(len(items_from_draft_store), 1)
|
||||
# TODO the below won't work for split mongo
|
||||
self.assertTrue(getattr(items_from_draft_store[0], 'is_draft', False))
|
||||
|
||||
def test_draft_metadata(self):
|
||||
"""
|
||||
@@ -899,11 +798,11 @@ class MiscCourseTests(ContentStoreTestCase):
|
||||
self.assertEqual(num_drafts, 0)
|
||||
|
||||
# put into draft
|
||||
self.store.convert_to_draft(self.problem.location, self.user.id)
|
||||
self.problem = self.store.unpublish(self.problem.location, self.user.id)
|
||||
|
||||
# make sure we can query that item and verify that it is a draft
|
||||
draft_problem = self.store.get_item(self.problem.location)
|
||||
self.assertTrue(getattr(draft_problem, 'is_draft', False))
|
||||
self.assertEqual(self.store.has_published_version(draft_problem), False)
|
||||
|
||||
# now requery with depth
|
||||
course = self.store.get_course(self.course.id, depth=None)
|
||||
@@ -1025,15 +924,9 @@ class MiscCourseTests(ContentStoreTestCase):
|
||||
self.assertEqual(count, 0)
|
||||
|
||||
def test_illegal_draft_crud_ops(self):
|
||||
# this test presumes old mongo and split_draft not full split
|
||||
with self.assertRaises(InvalidVersionError):
|
||||
self.store.convert_to_draft(self.chapter_loc, self.user.id)
|
||||
|
||||
chapter = self.store.get_item(self.chapter_loc)
|
||||
chapter.data = 'chapter data'
|
||||
self.store.update_item(chapter, self.user.id)
|
||||
newobject = self.store.get_item(self.chapter_loc)
|
||||
self.assertFalse(getattr(newobject, 'is_draft', False))
|
||||
|
||||
with self.assertRaises(InvalidVersionError):
|
||||
self.store.unpublish(self.chapter_loc, self.user.id)
|
||||
@@ -1043,17 +936,11 @@ class MiscCourseTests(ContentStoreTestCase):
|
||||
Test that user get proper responses for urls with invalid url or
|
||||
asset/course key
|
||||
"""
|
||||
resp = self.client.get_html('/c4x/CDX/123123/asset/&invalid.png')
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
|
||||
resp = self.client.get_html('/c4x/CDX/123123/asset/invalid.png')
|
||||
resp = self.client.get_html('asset-v1:CDX+123123+2012_Fall+type@asset+block@&invalid.png')
|
||||
self.assertEqual(resp.status_code, 404)
|
||||
|
||||
# Now test that 404 response is returned when user tries to access
|
||||
# asset of some invalid course from split ModuleStore
|
||||
with self.store.default_store(ModuleStoreEnum.Type.split):
|
||||
resp = self.client.get_html('/c4x/InvalidOrg/InvalidCourse/asset/invalid.png')
|
||||
self.assertEqual(resp.status_code, 404)
|
||||
resp = self.client.get_html('asset-v1:CDX+123123+2012_Fall+type@asset+block@invalid.png')
|
||||
self.assertEqual(resp.status_code, 404)
|
||||
|
||||
@override_waffle_switch(waffle.ENABLE_ACCESSIBILITY_POLICY_PAGE, active=False)
|
||||
def test_disabled_accessibility_page(self):
|
||||
@@ -1078,20 +965,21 @@ class MiscCourseTests(ContentStoreTestCase):
|
||||
self.assertGreater(len(assets), 0)
|
||||
self.assertGreater(count, 0)
|
||||
|
||||
self.store.convert_to_draft(self.vert_loc, self.user.id)
|
||||
self.store.unpublish(self.vert_loc, self.user.id)
|
||||
|
||||
# delete the course
|
||||
self.store.delete_course(self.course.id, self.user.id)
|
||||
|
||||
# assert that there's absolutely no non-draft modules in the course
|
||||
# this should also include all draft items
|
||||
items = self.store.get_items(self.course.id)
|
||||
self.assertEqual(len(items), 0)
|
||||
with self.assertRaises(ItemNotFoundError):
|
||||
self.store.get_items(self.course.id)
|
||||
|
||||
# assert that all content in the asset library is also deleted
|
||||
# assert that all content in the asset library is keeped
|
||||
# in case the course is later restored.
|
||||
assets, count = contentstore().get_all_content_for_course(self.course.id)
|
||||
self.assertEqual(len(assets), 0)
|
||||
self.assertEqual(count, 0)
|
||||
self.assertGreater(len(assets), 0)
|
||||
self.assertGreater(count, 0)
|
||||
|
||||
def test_course_handouts_rewrites(self):
|
||||
"""
|
||||
@@ -1111,7 +999,8 @@ class MiscCourseTests(ContentStoreTestCase):
|
||||
# check that /static/ has been converted to the full path
|
||||
# note, we know the link it should be because that's what in the 'toy' course in the test data
|
||||
asset_key = self.course.id.make_asset_key('asset', 'handouts_sample_handout.txt')
|
||||
self.assertContains(resp, str(asset_key))
|
||||
asset_url = '/'.join(str(asset_key).rsplit('@', 1))
|
||||
self.assertContains(resp, asset_url)
|
||||
|
||||
def test_prefetch_children(self):
|
||||
# make sure we haven't done too many round trips to DB:
|
||||
@@ -1121,19 +1010,19 @@ class MiscCourseTests(ContentStoreTestCase):
|
||||
# so we don't need to make an extra query to compute it.
|
||||
# set the branch to 'publish' in order to prevent extra lookups of draft versions
|
||||
with self.store.branch_setting(ModuleStoreEnum.Branch.published_only, self.course.id):
|
||||
with check_mongo_calls(4):
|
||||
course = self.store.get_course(self.course.id, depth=2)
|
||||
with check_mongo_calls(2):
|
||||
course = self.store.get_course(self.course.id, depth=2, lazy=False)
|
||||
|
||||
# make sure we pre-fetched a known sequential which should be at depth=2
|
||||
self.assertIn(self.seq_loc, course.system.module_data)
|
||||
self.assertIn(BlockKey.from_usage_key(self.seq_loc), course.system.module_data)
|
||||
|
||||
# make sure we don't have a specific vertical which should be at depth=3
|
||||
self.assertNotIn(self.vert_loc, course.system.module_data)
|
||||
self.assertNotIn(BlockKey.from_usage_key(self.vert_loc), course.system.module_data)
|
||||
|
||||
# Now, test with the branch set to draft. No extra round trips b/c it doesn't go deep enough to get
|
||||
# beyond direct only categories
|
||||
with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, self.course.id):
|
||||
with check_mongo_calls(4):
|
||||
with check_mongo_calls(2):
|
||||
self.store.get_course(self.course.id, depth=2)
|
||||
|
||||
def _check_verticals(self, locations):
|
||||
@@ -1205,21 +1094,19 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
self.course_data['run'] = 'run.name'
|
||||
self.assert_created_course()
|
||||
|
||||
@ddt.data(ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.mongo)
|
||||
def test_course_with_different_cases(self, default_store):
|
||||
def test_course_with_different_cases(self):
|
||||
"""
|
||||
Tests that course can not be created with different case using an AJAX request to
|
||||
course handler.
|
||||
"""
|
||||
course_number = '99x'
|
||||
with self.store.default_store(default_store):
|
||||
# Verify create a course passes with lower case.
|
||||
self.course_data['number'] = course_number.lower()
|
||||
self.assert_created_course()
|
||||
# Verify create a course passes with lower case.
|
||||
self.course_data['number'] = course_number.lower()
|
||||
self.assert_created_course()
|
||||
|
||||
# Verify create a course fail when same course number is provided with different case.
|
||||
self.course_data['number'] = course_number.upper()
|
||||
self.assert_course_creation_failed(self.duplicate_course_error)
|
||||
# Verify create a course fail when same course number is provided with different case.
|
||||
self.course_data['number'] = course_number.upper()
|
||||
self.assert_course_creation_failed(self.duplicate_course_error)
|
||||
|
||||
def test_create_course_check_forum_seeding(self):
|
||||
"""Test new course creation and verify forum seeding """
|
||||
@@ -1356,38 +1243,35 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
self.course_data['display_name'] = 'Robot Super Course Two'
|
||||
self.course_data['run'] = '2013_Summer'
|
||||
|
||||
self.assert_course_creation_failed(self.duplicate_course_error)
|
||||
self.assert_created_course()
|
||||
|
||||
@ddt.data(ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.mongo)
|
||||
def test_create_course_case_change(self, default_store):
|
||||
def test_create_course_case_change(self):
|
||||
"""Test new course creation - error path due to case insensitive name equality"""
|
||||
self.course_data['number'] = '99x'
|
||||
|
||||
with self.store.default_store(default_store):
|
||||
# Verify that the course was created properly.
|
||||
self.assert_created_course()
|
||||
|
||||
# Verify that the course was created properly.
|
||||
self.assert_created_course()
|
||||
# Keep the copy of original org
|
||||
cache_current = self.course_data['org']
|
||||
|
||||
# Keep the copy of original org
|
||||
cache_current = self.course_data['org']
|
||||
# Change `org` to lower case and verify that course did not get created
|
||||
self.course_data['org'] = self.course_data['org'].lower()
|
||||
self.assert_course_creation_failed(self.duplicate_course_error)
|
||||
|
||||
# Change `org` to lower case and verify that course did not get created
|
||||
self.course_data['org'] = self.course_data['org'].lower()
|
||||
self.assert_course_creation_failed(self.duplicate_course_error)
|
||||
# Replace the org with its actual value, and keep the copy of course number.
|
||||
self.course_data['org'] = cache_current
|
||||
cache_current = self.course_data['number']
|
||||
|
||||
# Replace the org with its actual value, and keep the copy of course number.
|
||||
self.course_data['org'] = cache_current
|
||||
cache_current = self.course_data['number']
|
||||
self.course_data['number'] = self.course_data['number'].upper()
|
||||
self.assert_course_creation_failed(self.duplicate_course_error)
|
||||
|
||||
self.course_data['number'] = self.course_data['number'].upper()
|
||||
self.assert_course_creation_failed(self.duplicate_course_error)
|
||||
# Replace the org with its actual value, and keep the copy of course number.
|
||||
self.course_data['number'] = cache_current
|
||||
__ = self.course_data['run']
|
||||
|
||||
# Replace the org with its actual value, and keep the copy of course number.
|
||||
self.course_data['number'] = cache_current
|
||||
__ = self.course_data['run']
|
||||
|
||||
self.course_data['run'] = self.course_data['run'].upper()
|
||||
self.assert_course_creation_failed(self.duplicate_course_error)
|
||||
self.course_data['run'] = self.course_data['run'].upper()
|
||||
self.assert_course_creation_failed(self.duplicate_course_error)
|
||||
|
||||
def test_course_substring(self):
|
||||
"""
|
||||
@@ -1515,7 +1399,9 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
data = parse_json(resp)
|
||||
retarget = str(course.id.make_usage_key('chapter', 'REPLACE')).replace('REPLACE', r'([0-9]|[a-f]){3,}')
|
||||
retarget = re.escape(
|
||||
str(course.id.make_usage_key('chapter', 'REPLACE'))
|
||||
).replace('REPLACE', r'([0-9]|[a-f]){3,}')
|
||||
self.assertRegex(data['locator'], retarget)
|
||||
|
||||
def test_capa_block(self):
|
||||
@@ -1625,7 +1511,7 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
self.assertEqual(course_block.pdf_textbooks[0]["chapters"][1]["url"], '/static/Chapter2.pdf')
|
||||
|
||||
def test_import_into_new_course_id_wiki_slug_renamespacing(self):
|
||||
# If reimporting into the same course do not change the wiki_slug.
|
||||
# If reimporting into the same course change the wiki_slug.
|
||||
target_id = self.store.make_course_key('edX', 'toy', '2012_Fall')
|
||||
course_data = {
|
||||
'org': target_id.org,
|
||||
@@ -1641,7 +1527,7 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
# Import a course with wiki_slug == location.course
|
||||
import_course_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'], target_id=target_id)
|
||||
course_block = self.store.get_course(target_id)
|
||||
self.assertEqual(course_block.wiki_slug, 'toy')
|
||||
self.assertEqual(course_block.wiki_slug, 'edX.toy.2012_Fall')
|
||||
|
||||
# But change the wiki_slug if it is a different course.
|
||||
target_id = self.store.make_course_key('MITx', '111', '2013_Spring')
|
||||
@@ -1667,7 +1553,7 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
import_course_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['simple'], create_if_not_present=True)
|
||||
did_load_item = False
|
||||
try:
|
||||
course_key = self.store.make_course_key('edX', 'simple', 'problem')
|
||||
course_key = self.store.make_course_key('edX', 'simple', '2012_Fall')
|
||||
usage_key = course_key.make_usage_key('problem', 'ps01-simple')
|
||||
self.store.get_item(usage_key)
|
||||
did_load_item = True
|
||||
@@ -1677,13 +1563,12 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
# make sure we found the item (e.g. it didn't error while loading)
|
||||
self.assertTrue(did_load_item)
|
||||
|
||||
@ddt.data(ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.mongo)
|
||||
def test_forum_id_generation(self, default_store):
|
||||
def test_forum_id_generation(self):
|
||||
"""
|
||||
Test that a discussion item, even if it doesn't set its discussion_id,
|
||||
consistently generates the same one
|
||||
"""
|
||||
course = CourseFactory.create(default_store=default_store)
|
||||
course = CourseFactory.create()
|
||||
|
||||
# create a discussion item
|
||||
discussion_item = self.store.create_item(self.user.id, course.id, 'discussion', 'new_component')
|
||||
@@ -1825,9 +1710,11 @@ class MetadataSaveTestCase(ContentStoreTestCase):
|
||||
<track src="http://www.example.com/track"/>
|
||||
</video>
|
||||
"""
|
||||
video_data = VideoBlock.parse_video_xml(video_sample_xml)
|
||||
video_data.pop('source')
|
||||
self.video_descriptor = ItemFactory.create(
|
||||
parent_location=course.location, category='video',
|
||||
**VideoBlock.parse_video_xml(video_sample_xml)
|
||||
**video_data
|
||||
)
|
||||
|
||||
def test_metadata_not_persistence(self):
|
||||
@@ -1891,10 +1778,6 @@ class RerunCourseTest(ContentStoreTestCase):
|
||||
rerun_course_data.update(destination_course_data)
|
||||
destination_course_key = _get_course_id(self.store, destination_course_data)
|
||||
|
||||
# course_handler raise 404 for old mongo course
|
||||
if destination_course_key.deprecated:
|
||||
raise SkipTest('OldMongo Deprecation')
|
||||
|
||||
# post the request
|
||||
course_url = get_url('course_handler', destination_course_key, 'course_key_string')
|
||||
response = self.client.ajax_post(course_url, rerun_course_data)
|
||||
@@ -2154,7 +2037,7 @@ class ContentLicenseTest(ContentStoreTestCase):
|
||||
self.course.license = "creative-commons: BY SA"
|
||||
self.store.update_item(self.course, None)
|
||||
export_course_to_xml(self.store, content_store, self.course.id, root_dir, 'test_license')
|
||||
fname = f"{self.course.scope_ids.usage_id.block_id}.xml"
|
||||
fname = f"{self.course.id.run}.xml"
|
||||
run_file_path = root_dir / "test_license" / "course" / fname
|
||||
with run_file_path.open() as f:
|
||||
run_xml = etree.parse(f)
|
||||
@@ -2221,10 +2104,6 @@ def _create_course(test, course_key, course_data):
|
||||
"""
|
||||
Creates a course via an AJAX request and verifies the URL returned in the response.
|
||||
"""
|
||||
# course_handler raise 404 for old mongo course
|
||||
if course_key.deprecated:
|
||||
raise SkipTest('OldMongo Deprecation')
|
||||
|
||||
course_url = get_url('course_handler', course_key, 'course_key_string')
|
||||
response = test.client.ajax_post(course_url, course_data)
|
||||
test.assertEqual(response.status_code, 200)
|
||||
|
||||
@@ -8,15 +8,13 @@ import json
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
|
||||
from django.test.client import Client
|
||||
from opaque_keys.edx.keys import AssetKey, CourseKey
|
||||
from opaque_keys.edx.keys import AssetKey
|
||||
from xmodule.contentstore.django import contentstore
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.inheritance import own_metadata
|
||||
from xmodule.modulestore.split_mongo.split import SplitMongoModuleStore
|
||||
from xmodule.modulestore.tests.django_utils import TEST_DATA_MONGO_MODULESTORE, ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from xmodule.modulestore.tests.utils import ProceduralCourseTestMixin
|
||||
from xmodule.modulestore.xml_importer import import_course_from_xml
|
||||
from xmodule.tests.test_transcripts_utils import YoutubeVideoHTMLResponse
|
||||
|
||||
from cms.djangoapps.contentstore.utils import reverse_url
|
||||
@@ -125,158 +123,6 @@ class CourseTestCase(ProceduralCourseTestMixin, ModuleStoreTestCase):
|
||||
DRAFT_VIDEO = 'draft_video'
|
||||
LOCKED_ASSET_KEY = AssetKey.from_string('/c4x/edX/toy/asset/sample_static.html')
|
||||
|
||||
def import_and_populate_course(self):
|
||||
"""
|
||||
Imports the test toy course and populates it with additional test data
|
||||
"""
|
||||
content_store = contentstore()
|
||||
import_course_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'], static_content_store=content_store)
|
||||
course_id = CourseKey.from_string('/'.join(['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.block_id, 'no_references')
|
||||
self.assertEqual(len(orphan_vertical.children), len(vertical.children))
|
||||
|
||||
# create an orphan vertical and html; we already don't try to import
|
||||
# the orphaned vertical, but we should make sure we don't import
|
||||
# the orphaned vertical's child html, too
|
||||
orphan_draft_vertical = self.store.create_item(
|
||||
self.user.id, course_id, 'vertical', self.ORPHAN_DRAFT_VERTICAL
|
||||
)
|
||||
orphan_draft_html = self.store.create_item(
|
||||
self.user.id, course_id, 'html', self.ORPHAN_DRAFT_HTML
|
||||
)
|
||||
orphan_draft_vertical.children.append(orphan_draft_html.location)
|
||||
self.store.update_item(orphan_draft_vertical, self.user.id)
|
||||
|
||||
# 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)
|
||||
|
||||
# create an html and video component to make drafts:
|
||||
draft_html = self.store.create_item(self.user.id, course_id, 'html', self.DRAFT_HTML)
|
||||
draft_video = self.store.create_item(self.user.id, course_id, 'video', self.DRAFT_VIDEO)
|
||||
|
||||
# add them as children to the public_vertical
|
||||
public_vertical.children.append(draft_html.location)
|
||||
public_vertical.children.append(draft_video.location)
|
||||
self.store.update_item(public_vertical, self.user.id)
|
||||
# publish changes to vertical
|
||||
self.store.publish(public_vertical.location, self.user.id)
|
||||
# convert html/video to draft
|
||||
self.store.convert_to_draft(draft_html.location, self.user.id)
|
||||
self.store.convert_to_draft(draft_video.location, 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_block = self.store.get_item(course_id.make_usage_key('html', 'nonportable'))
|
||||
new_data = html_block.data = html_block.data.replace(
|
||||
'/static/',
|
||||
f'/c4x/{course_id.org}/{course_id.course}/asset/'
|
||||
)
|
||||
self.store.update_item(html_block, self.user.id)
|
||||
|
||||
html_block = self.store.get_item(html_block.location)
|
||||
self.assertEqual(new_data, html_block.data)
|
||||
|
||||
return course_id
|
||||
|
||||
def check_populated_course(self, course_id):
|
||||
"""
|
||||
Verifies the content of the given course, per data that was populated in import_and_populate_course
|
||||
"""
|
||||
items = self.store.get_items(
|
||||
course_id,
|
||||
qualifiers={'category': 'vertical'},
|
||||
revision=ModuleStoreEnum.RevisionOption.published_only
|
||||
)
|
||||
self.check_verticals(items)
|
||||
|
||||
def verify_item_publish_state(item, publish_state):
|
||||
"""Verifies the publish state of the item is as expected."""
|
||||
self.assertEqual(self.store.has_published_version(item), publish_state)
|
||||
|
||||
def get_and_verify_publish_state(item_type, item_name, publish_state):
|
||||
"""
|
||||
Gets the given item from the store and verifies the publish state
|
||||
of the item is as expected.
|
||||
"""
|
||||
item = self.store.get_item(course_id.make_usage_key(item_type, item_name))
|
||||
verify_item_publish_state(item, publish_state)
|
||||
return item
|
||||
|
||||
# verify draft vertical has a published version with published children
|
||||
vertical = get_and_verify_publish_state('vertical', self.TEST_VERTICAL, True)
|
||||
for child in vertical.get_children():
|
||||
verify_item_publish_state(child, True)
|
||||
|
||||
# verify that it has a draft too
|
||||
self.assertTrue(getattr(vertical, "is_draft", False))
|
||||
|
||||
# make sure that we don't have a sequential that is in draft mode
|
||||
sequential = get_and_verify_publish_state('sequential', self.SEQUENTIAL, True)
|
||||
self.assertFalse(getattr(sequential, "is_draft", False))
|
||||
|
||||
# verify that we have the private vertical
|
||||
private_vertical = get_and_verify_publish_state('vertical', self.PRIVATE_VERTICAL, False)
|
||||
|
||||
# verify that we have the public vertical
|
||||
public_vertical = get_and_verify_publish_state('vertical', self.PUBLISHED_VERTICAL, True)
|
||||
|
||||
# verify that we have the draft html
|
||||
draft_html = self.store.get_item(course_id.make_usage_key('html', self.DRAFT_HTML))
|
||||
self.assertTrue(getattr(draft_html, 'is_draft', False))
|
||||
|
||||
# verify that we have the draft video
|
||||
draft_video = self.store.get_item(course_id.make_usage_key('video', self.DRAFT_VIDEO))
|
||||
self.assertTrue(getattr(draft_video, 'is_draft', False))
|
||||
|
||||
# verify verticals are children of sequential
|
||||
for vert in [vertical, private_vertical, public_vertical]:
|
||||
self.assertIn(vert.location, sequential.children)
|
||||
|
||||
# verify draft html is the child of the public vertical
|
||||
self.assertIn(draft_html.location, public_vertical.children)
|
||||
|
||||
# verify draft video is the child of the public vertical
|
||||
self.assertIn(draft_video.location, public_vertical.children)
|
||||
|
||||
# verify textbook exists
|
||||
course = self.store.get_course(course_id)
|
||||
self.assertGreater(len(course.textbooks), 0)
|
||||
|
||||
# verify asset attributes of locked asset key
|
||||
self.assertAssetsEqual(self.LOCKED_ASSET_KEY, self.LOCKED_ASSET_KEY.course_key, course_id)
|
||||
|
||||
# verify non-portable links are rewritten
|
||||
html_block = self.store.get_item(course_id.make_usage_key('html', 'nonportable'))
|
||||
self.assertIn('/static/foo.jpg', html_block.data)
|
||||
|
||||
return course
|
||||
|
||||
def assertCoursesEqual(self, course1_id, course2_id):
|
||||
"""
|
||||
Verifies the content of the two given courses are equal
|
||||
|
||||
Reference in New Issue
Block a user