Files
edx-platform/cms/djangoapps/contentstore/views/tests/test_preview.py
Simon Chen 4f339e2f9e fix: make the studio user_id the actual anonymous_user_id (#30248)
* feat: test out individualized anonymouse_user_id for studio preview on xblocks

* test: adds tests for the PreviewModuleSystem anonymous_user_id (#30400)

and the INDIVIDUALIZE_ANONYMOUS_USER_ID flag.

Co-authored-by: Jillian Vogel <jill@opencraft.com>

Co-authored-by: Simon Chen <schen@edX-C02FW0GUML85.local>
Co-authored-by: Jillian Vogel <jill@opencraft.com>
2022-05-16 10:37:39 -04:00

325 lines
12 KiB
Python

"""
Tests for contentstore.views.preview.py
"""
import re
from unittest import mock
import ddt
from django.test.client import Client, RequestFactory
from django.test.utils import override_settings
from edx_toggles.toggles.testutils import override_waffle_flag
from web_fragments.fragment import Fragment
from xblock.core import XBlock, XBlockAside
from xmodule.contentstore.django import contentstore
from xmodule.lti_module import LTIBlock
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import (
TEST_DATA_MONGO_MODULESTORE, ModuleStoreTestCase, upload_file_to_course,
)
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore.tests.test_asides import AsideTestType
from cms.djangoapps.contentstore.utils import reverse_usage_url
from cms.djangoapps.contentstore.toggles import INDIVIDUALIZE_ANONYMOUS_USER_ID
from cms.djangoapps.xblock_config.models import StudioConfig
from common.djangoapps import static_replace
from common.djangoapps.student.tests.factories import UserFactory
from ..preview import _preview_module_system, get_preview_fragment
@ddt.ddt
class GetPreviewHtmlTestCase(ModuleStoreTestCase):
"""
Tests for get_preview_fragment.
Note that there are other existing test cases in test_contentstore that indirectly execute
get_preview_fragment via the xblock RESTful API.
"""
@XBlockAside.register_temp_plugin(AsideTestType, 'test_aside')
def test_preview_fragment(self):
"""
Test for calling get_preview_html. Ensures data-usage-id is correctly set and
asides are correctly included.
"""
course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split)
html = ItemFactory.create(
parent_location=course.location,
category="html",
data={'data': "<html>foobar</html>"}
)
config = StudioConfig.current()
config.enabled = True
config.save()
request = RequestFactory().get('/dummy-url')
request.user = UserFactory()
request.session = {}
# Call get_preview_fragment directly.
context = {
'reorderable_items': set(),
'read_only': True
}
html = get_preview_fragment(request, html, context).content
# Verify student view html is returned, and the usage ID is as expected.
html_pattern = re.escape(
str(course.id.make_usage_key('html', 'replaceme'))
).replace('replaceme', r'html_[0-9]*')
self.assertRegex(
html,
f'data-usage-id="{html_pattern}"'
)
self.assertRegex(html, '<html>foobar</html>')
self.assertRegex(html, r"data-block-type=[\"\']test_aside[\"\']")
self.assertRegex(html, "Aside rendered")
# Now ensure the acid_aside is not in the result
self.assertNotRegex(html, r"data-block-type=[\"\']acid_aside[\"\']")
# Ensure about pages don't have asides
about = modulestore().get_item(course.id.make_usage_key('about', 'overview'))
html = get_preview_fragment(request, about, context).content
self.assertNotRegex(html, r"data-block-type=[\"\']test_aside[\"\']")
self.assertNotRegex(html, "Aside rendered")
@XBlockAside.register_temp_plugin(AsideTestType, 'test_aside')
def test_preview_no_asides(self):
"""
Test for calling get_preview_html. Ensures data-usage-id is correctly set and
asides are correctly excluded because they are not enabled.
"""
course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split)
html = ItemFactory.create(
parent_location=course.location,
category="html",
data={'data': "<html>foobar</html>"}
)
config = StudioConfig.current()
config.enabled = False
config.save()
request = RequestFactory().get('/dummy-url')
request.user = UserFactory()
request.session = {}
# Call get_preview_fragment directly.
context = {
'reorderable_items': set(),
'read_only': True
}
html = get_preview_fragment(request, html, context).content
self.assertNotRegex(html, r"data-block-type=[\"\']test_aside[\"\']")
self.assertNotRegex(html, "Aside rendered")
@mock.patch('xmodule.conditional_module.ConditionalBlock.is_condition_satisfied')
def test_preview_conditional_module_children_context(self, mock_is_condition_satisfied):
"""
Tests that when empty context is pass to children of ConditionalBlock it will not raise KeyError.
"""
mock_is_condition_satisfied.return_value = True
client = Client()
client.login(username=self.user.username, password=self.user_password)
with self.store.default_store(ModuleStoreEnum.Type.split):
course = CourseFactory.create()
conditional_block = ItemFactory.create(
parent_location=course.location,
category="conditional"
)
# child conditional_block
ItemFactory.create(
parent_location=conditional_block.location,
category="conditional"
)
url = reverse_usage_url(
'preview_handler',
conditional_block.location,
kwargs={'handler': 'xmodule_handler/conditional_get'}
)
response = client.post(url)
self.assertEqual(response.status_code, 200)
@ddt.data(ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.mongo)
def test_block_branch_not_changed_by_preview_handler(self, default_store):
"""
Tests preview_handler should not update blocks being previewed
"""
client = Client()
client.login(username=self.user.username, password=self.user_password)
with self.store.default_store(default_store):
course = CourseFactory.create()
block = ItemFactory.create(
parent_location=course.location,
category="problem"
)
url = reverse_usage_url(
'preview_handler',
block.location,
kwargs={'handler': 'xmodule_handler/problem_check'}
)
response = client.post(url)
self.assertEqual(response.status_code, 200)
self.assertFalse(modulestore().has_changes(modulestore().get_item(block.location)))
@XBlock.needs("field-data")
@XBlock.needs("i18n")
@XBlock.needs("mako")
@XBlock.needs("replace_urls")
@XBlock.needs("user")
@XBlock.needs("teams_configuration")
class PureXBlock(XBlock):
"""
Pure XBlock to use in tests.
"""
def student_view(self, context):
"""
Renders the output that a student will see.
"""
fragment = Fragment()
fragment.add_content(self.runtime.service(self, 'mako').render_template('edxmako.html', context))
return fragment
@ddt.ddt
class StudioXBlockServiceBindingTest(ModuleStoreTestCase):
"""
Tests that the Studio Module System (XBlock Runtime) provides an expected set of services.
"""
def setUp(self):
"""
Set up the user and request that will be used.
"""
super().setUp()
self.user = UserFactory()
self.course = CourseFactory.create()
self.request = mock.Mock()
self.field_data = mock.Mock()
@XBlock.register_temp_plugin(PureXBlock, identifier='pure')
@ddt.data("user", "i18n", "field-data", "teams_configuration", "replace_urls")
def test_expected_services_exist(self, expected_service):
"""
Tests that the 'user' and 'i18n' services are provided by the Studio runtime.
"""
descriptor = ItemFactory(category="pure", parent=self.course)
runtime = _preview_module_system(
self.request,
descriptor,
self.field_data,
)
service = runtime.service(descriptor, expected_service)
self.assertIsNotNone(service)
class CmsModuleSystemShimTest(ModuleStoreTestCase):
"""
Tests that the deprecated attributes in the Module System (XBlock Runtime) return the expected values.
"""
MODULESTORE = TEST_DATA_MONGO_MODULESTORE
COURSE_ID = 'edX/CmsModuleShimTest/2021_Fall'
PYTHON_LIB_FILENAME = 'test_python_lib.zip'
PYTHON_LIB_SOURCE_FILE = './common/test/data/uploads/python_lib.zip'
def setUp(self):
"""
Set up the user, course and other fields that will be used to instantiate the runtime.
"""
super().setUp()
org, number, run = self.COURSE_ID.split('/')
self.course = CourseFactory.create(org=org, number=number, run=run)
self.user = UserFactory()
self.request = RequestFactory().get('/dummy-url')
self.request.user = self.user
self.request.session = {}
self.descriptor = ItemFactory(category="video", parent=self.course)
self.field_data = mock.Mock()
self.contentstore = contentstore()
self.runtime = _preview_module_system(
self.request,
descriptor=ItemFactory(category="problem", parent=self.course),
field_data=mock.Mock(),
)
def test_get_user_role(self):
assert self.runtime.get_user_role() == 'staff'
@XBlock.register_temp_plugin(PureXBlock, identifier='pure')
def test_render_template(self):
descriptor = ItemFactory(category="pure", parent=self.course)
html = get_preview_fragment(self.request, descriptor, {'element_id': 142}).content
assert '<div id="142" ns="main">Testing the MakoService</div>' in html
@override_settings(COURSES_WITH_UNSAFE_CODE=[COURSE_ID])
def test_can_execute_unsafe_code(self):
assert self.runtime.can_execute_unsafe_code()
def test_cannot_execute_unsafe_code(self):
assert not self.runtime.can_execute_unsafe_code()
@override_settings(PYTHON_LIB_FILENAME=PYTHON_LIB_FILENAME)
def test_get_python_lib_zip(self):
zipfile = upload_file_to_course(
course_key=self.course.id,
contentstore=self.contentstore,
source_file=self.PYTHON_LIB_SOURCE_FILE,
target_filename=self.PYTHON_LIB_FILENAME,
)
assert self.runtime.get_python_lib_zip() == zipfile
def test_no_get_python_lib_zip(self):
zipfile = upload_file_to_course(
course_key=self.course.id,
contentstore=self.contentstore,
source_file=self.PYTHON_LIB_SOURCE_FILE,
target_filename=self.PYTHON_LIB_FILENAME,
)
assert self.runtime.get_python_lib_zip() is None
def test_cache(self):
assert hasattr(self.runtime.cache, 'get')
assert hasattr(self.runtime.cache, 'set')
def test_replace_urls(self):
html = '<a href="/static/id">'
assert self.runtime.replace_urls(html) == \
static_replace.replace_static_urls(html, course_id=self.runtime.course_id)
def test_anonymous_user_id_preview(self):
assert self.runtime.anonymous_student_id == 'student'
@override_waffle_flag(INDIVIDUALIZE_ANONYMOUS_USER_ID, active=True)
def test_anonymous_user_id_individual_per_student(self):
"""Test anonymous_user_id on a block which uses per-student anonymous IDs"""
# Create the runtime with the flag turned on.
runtime = _preview_module_system(
self.request,
descriptor=ItemFactory(category="problem", parent=self.course),
field_data=mock.Mock(),
)
assert runtime.anonymous_student_id == '26262401c528d7c4a6bbeabe0455ec46'
@override_waffle_flag(INDIVIDUALIZE_ANONYMOUS_USER_ID, active=True)
def test_anonymous_user_id_individual_per_course(self):
"""Test anonymous_user_id on a block which uses per-course anonymous IDs"""
# Create the runtime with the flag turned on.
runtime = _preview_module_system(
self.request,
descriptor=ItemFactory(category="lti", parent=self.course, spec=LTIBlock),
field_data=mock.Mock(),
)
assert runtime.anonymous_student_id == 'cf99fd26f9a41d4d9b4069739cc2be7b'