|
4 | 4 | APIs.
|
5 | 5 | """
|
6 | 6 | import ddt
|
| 7 | +from django.test import LiveServerTestCase |
7 | 8 | from opaque_keys.edx.keys import UsageKey
|
8 | 9 | from rest_framework.test import APIClient
|
9 |
| -from xmodule.modulestore.django import contentstore |
| 10 | +from xmodule.modulestore.django import contentstore, modulestore |
10 | 11 | from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, upload_file_to_course
|
11 |
| -from xmodule.modulestore.tests.factories import BlockFactory, CourseFactory, ToyCourseFactory |
| 12 | +from xmodule.modulestore.tests.factories import BlockFactory, CourseFactory, LibraryFactory, ToyCourseFactory |
| 13 | + |
| 14 | +from cms.djangoapps.contentstore.utils import reverse_usage_url |
| 15 | +from cms.djangoapps.contentstore.tests.utils import AjaxEnabledTestClient |
12 | 16 |
|
13 | 17 | CLIPBOARD_ENDPOINT = "/api/content-staging/v1/clipboard/"
|
14 | 18 | XBLOCK_ENDPOINT = "/xblock/"
|
@@ -109,7 +113,7 @@ def test_copy_and_paste_component(self, block_args):
|
109 | 113 | """
|
110 | 114 | Test copying a component (XBlock) from one course into another
|
111 | 115 | """
|
112 |
| - source_course = CourseFactory.create(display_name='Destination Course') |
| 116 | + source_course = CourseFactory.create(display_name='Source Course') |
113 | 117 | source_block = BlockFactory.create(parent_location=source_course.location, **block_args)
|
114 | 118 |
|
115 | 119 | dest_course = CourseFactory.create(display_name='Destination Course')
|
@@ -204,3 +208,79 @@ def test_paste_with_assets(self):
|
204 | 208 | source_pic2_hash = contentstore().find(source_course.id.make_asset_key("asset", "picture2.jpg")).content_digest
|
205 | 209 | dest_pic2_hash = contentstore().find(dest_course_key.make_asset_key("asset", "picture2.jpg")).content_digest
|
206 | 210 | assert source_pic2_hash != dest_pic2_hash # Because there was a conflict, this file was unchanged.
|
| 211 | + |
| 212 | + |
| 213 | +class ClipboardLibraryContentPasteTestCase(LiveServerTestCase, ModuleStoreTestCase): |
| 214 | + """ |
| 215 | + Test Clipboard Paste functionality with library content |
| 216 | + """ |
| 217 | + |
| 218 | + def setUp(self): |
| 219 | + """ |
| 220 | + Set up a v2 Content Library and a library content block |
| 221 | + """ |
| 222 | + super().setUp() |
| 223 | + self.client = AjaxEnabledTestClient() |
| 224 | + self.client.login(username=self.user.username, password=self.user_password) |
| 225 | + self.store = modulestore() |
| 226 | + |
| 227 | + def test_paste_library_content_block_v1(self): |
| 228 | + """ |
| 229 | + Same as the above test, but uses modulestore (v1) content library |
| 230 | + """ |
| 231 | + library = LibraryFactory.create() |
| 232 | + data = { |
| 233 | + 'parent_locator': str(library.location), |
| 234 | + 'category': 'html', |
| 235 | + 'display_name': 'HTML Content', |
| 236 | + } |
| 237 | + response = self.client.ajax_post(XBLOCK_ENDPOINT, data) |
| 238 | + self.assertEqual(response.status_code, 200) |
| 239 | + course = CourseFactory.create(display_name='Course') |
| 240 | + orig_lc_block = BlockFactory.create( |
| 241 | + parent=course, |
| 242 | + category="library_content", |
| 243 | + source_library_id=str(library.location.library_key), |
| 244 | + display_name="LC Block", |
| 245 | + publish_item=False, |
| 246 | + ) |
| 247 | + orig_lc_block.refresh_children() |
| 248 | + orig_child = self.store.get_item(orig_lc_block.children[0]) |
| 249 | + assert orig_child.display_name == "HTML Content" |
| 250 | + # Copy a library content block that has children: |
| 251 | + copy_response = self.client.post(CLIPBOARD_ENDPOINT, { |
| 252 | + "usage_key": str(orig_lc_block.location) |
| 253 | + }, format="json") |
| 254 | + assert copy_response.status_code == 200 |
| 255 | + |
| 256 | + # Paste the Library content block: |
| 257 | + paste_response = self.client.ajax_post(XBLOCK_ENDPOINT, { |
| 258 | + "parent_locator": str(course.location), |
| 259 | + "staged_content": "clipboard", |
| 260 | + }) |
| 261 | + assert paste_response.status_code == 200 |
| 262 | + dest_lc_block_key = UsageKey.from_string(paste_response.json()["locator"]) |
| 263 | + |
| 264 | + # Get the ID of the new child: |
| 265 | + dest_lc_block = self.store.get_item(dest_lc_block_key) |
| 266 | + dest_child = self.store.get_item(dest_lc_block.children[0]) |
| 267 | + assert dest_child.display_name == "HTML Content" |
| 268 | + |
| 269 | + # Importantly, the ID of the child must not changed when the library content is synced. |
| 270 | + # Otherwise, user state saved against this child will be lost when it syncs. |
| 271 | + dest_lc_block.refresh_children() |
| 272 | + updated_dest_child = self.store.get_item(dest_lc_block.children[0]) |
| 273 | + assert dest_child.location == updated_dest_child.location |
| 274 | + |
| 275 | + def _sync_lc_block_from_library(self, attr_name): |
| 276 | + """ |
| 277 | + Helper method to "sync" a Library Content Block by [re-]fetching its |
| 278 | + children from the library. |
| 279 | + """ |
| 280 | + usage_key = getattr(self, attr_name).location |
| 281 | + # It's easiest to do this via the REST API: |
| 282 | + handler_url = reverse_usage_url('preview_handler', usage_key, kwargs={'handler': 'upgrade_and_sync'}) |
| 283 | + response = self.client.post(handler_url) |
| 284 | + assert response.status_code == 200 |
| 285 | + # Now reload the block and make sure the child is in place |
| 286 | + setattr(self, attr_name, self.store.get_item(usage_key)) # we must reload after upgrade_and_sync |
0 commit comments