diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py
index ca5ea3e954..94194502be 100644
--- a/cms/djangoapps/contentstore/tests/test_contentstore.py
+++ b/cms/djangoapps/contentstore/tests/test_contentstore.py
@@ -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("""
-
-
-
- """).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):
"""
+ 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)
diff --git a/cms/djangoapps/contentstore/tests/utils.py b/cms/djangoapps/contentstore/tests/utils.py
index cae265f5ab..b58ec17502 100644
--- a/cms/djangoapps/contentstore/tests/utils.py
+++ b/cms/djangoapps/contentstore/tests/utils.py
@@ -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