The /jump_to/ LMS endpoint is used in a number of places to direct users to courseware. It currently only redirects to Legacy courseware URLs, which then conditionally may redirect to the Learning MFE. Two issues with this: 1. Performance Impact: In most cases, going to Legacy first is just an extra redirect. 2. Confusion for Privileged Users: Neither course nor global staff are auto-redirected from the Legacy experience to the MFE. Thus, these priviliged users confusingly never see the MFE by default; they must always manually click into it. This commit makes it so that /jump_to/ directs users to whatever the default courseware experience is for them. For staff of courses active in the new experience, this will impact (at a minimum) the "View Live" links in Studio, all links on the old and new LMS course outline, and the "Resume" links on the course dashboard. Learners should see no difference other than a performance improvement when following courseware links from the LMS. This also adds an optional 'experience=[legacy|new]' query param to /jump_to/, allowing us to specifically generate Legacy courseware URLs for the "View in Legacy Experience" tool. TNL-7796
237 lines
8.3 KiB
Python
237 lines
8.3 KiB
Python
"""
|
|
Tests for Course Blocks serializers
|
|
"""
|
|
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
from common.djangoapps.student.roles import CourseStaffRole
|
|
from common.djangoapps.student.tests.factories import UserFactory
|
|
from lms.djangoapps.course_blocks.api import get_course_block_access_transformers, get_course_blocks
|
|
from openedx.core.djangoapps.content.block_structure.transformers import BlockStructureTransformers
|
|
from xmodule.modulestore import ModuleStoreEnum
|
|
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
|
from xmodule.modulestore.tests.factories import ToyCourseFactory
|
|
|
|
from ..serializers import BlockDictSerializer, BlockSerializer
|
|
from ..transformers.blocks_api import BlocksAPITransformer
|
|
from .helpers import deserialize_usage_key
|
|
|
|
|
|
class TestBlockSerializerBase(SharedModuleStoreTestCase):
|
|
"""
|
|
Base class for testing BlockSerializer and BlockDictSerializer
|
|
"""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
|
|
cls.course = ToyCourseFactory.create()
|
|
|
|
# Hide the html block
|
|
key = cls.course.id.make_usage_key('html', 'secret:toylab')
|
|
cls.html_block = cls.store.get_item(key)
|
|
cls.html_block.visible_to_staff_only = True
|
|
cls.store.update_item(cls.html_block, ModuleStoreEnum.UserID.test)
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
|
|
self.user = UserFactory.create()
|
|
|
|
blocks_api_transformer = BlocksAPITransformer(
|
|
block_types_to_count=['video'],
|
|
requested_student_view_data=['video'],
|
|
)
|
|
self.transformers = BlockStructureTransformers(
|
|
get_course_block_access_transformers(self.user) + [blocks_api_transformer]
|
|
)
|
|
self.block_structure = get_course_blocks(
|
|
self.user,
|
|
self.course.location,
|
|
self.transformers,
|
|
)
|
|
self.serializer_context = {
|
|
'request': MagicMock(),
|
|
'block_structure': self.block_structure,
|
|
'requested_fields': ['type'],
|
|
}
|
|
|
|
def assert_basic_block(self, block_key_string, serialized_block):
|
|
"""
|
|
Verifies the given serialized_block when basic fields are requested.
|
|
"""
|
|
block_key = deserialize_usage_key(block_key_string, self.course.id)
|
|
assert self.block_structure.get_xblock_field(block_key, 'category') == serialized_block['type']
|
|
assert set(serialized_block.keys()) == {
|
|
'id', 'block_id', 'type', 'lms_web_url', 'legacy_web_url', 'student_view_url'
|
|
}
|
|
|
|
def add_additional_requested_fields(self, context=None):
|
|
"""
|
|
Adds additional fields to the requested_fields context for the serializer.
|
|
"""
|
|
if context is None:
|
|
context = self.serializer_context
|
|
context['requested_fields'].extend([
|
|
'children',
|
|
'display_name',
|
|
'graded',
|
|
'format',
|
|
'block_counts',
|
|
'student_view_data',
|
|
'student_view_multi_device',
|
|
'lti_url',
|
|
'visible_to_staff_only',
|
|
])
|
|
|
|
def assert_extended_block(self, serialized_block):
|
|
"""
|
|
Verifies the given serialized_block when additional fields are requested.
|
|
"""
|
|
assert {
|
|
'id',
|
|
'type',
|
|
'lms_web_url',
|
|
'legacy_web_url',
|
|
'student_view_url',
|
|
'display_name',
|
|
'graded',
|
|
'student_view_multi_device',
|
|
'lti_url',
|
|
'visible_to_staff_only'
|
|
} <= set(serialized_block.keys())
|
|
|
|
# video blocks should have student_view_data
|
|
if serialized_block['type'] == 'video':
|
|
assert 'student_view_data' in serialized_block
|
|
|
|
# html blocks should have student_view_multi_device set to True
|
|
if serialized_block['type'] == 'html':
|
|
assert 'student_view_multi_device' in serialized_block
|
|
assert serialized_block['student_view_multi_device']
|
|
|
|
# chapters with video should have block_counts
|
|
if serialized_block['type'] == 'chapter':
|
|
if serialized_block['display_name'] not in ('poll_test', 'handout_container'):
|
|
assert 'block_counts' in serialized_block
|
|
else:
|
|
assert 'block_counts' not in serialized_block
|
|
|
|
def create_staff_context(self):
|
|
"""
|
|
Create staff user and course blocks accessible by that user
|
|
"""
|
|
# Create a staff user to be able to test visible_to_staff_only
|
|
staff_user = UserFactory.create()
|
|
CourseStaffRole(self.course.location.course_key).add_users(staff_user)
|
|
|
|
block_structure = get_course_blocks(
|
|
staff_user,
|
|
self.course.location,
|
|
self.transformers,
|
|
)
|
|
return {
|
|
'request': MagicMock(),
|
|
'block_structure': block_structure,
|
|
'requested_fields': ['type'],
|
|
}
|
|
|
|
def assert_staff_fields(self, serialized_block):
|
|
"""
|
|
Test fields accessed by a staff user
|
|
"""
|
|
if serialized_block['id'] == str(self.html_block.location):
|
|
assert serialized_block['visible_to_staff_only']
|
|
else:
|
|
assert not serialized_block['visible_to_staff_only']
|
|
|
|
|
|
class TestBlockSerializer(TestBlockSerializerBase):
|
|
"""
|
|
Tests the BlockSerializer class, which returns a list of blocks.
|
|
"""
|
|
|
|
def create_serializer(self, context=None):
|
|
"""
|
|
creates a BlockSerializer
|
|
"""
|
|
if context is None:
|
|
context = self.serializer_context
|
|
return BlockSerializer(
|
|
context['block_structure'], many=True, context=context,
|
|
)
|
|
|
|
def test_basic(self):
|
|
serializer = self.create_serializer()
|
|
for serialized_block in serializer.data:
|
|
self.assert_basic_block(serialized_block['id'], serialized_block)
|
|
assert len(serializer.data) == 28
|
|
|
|
def test_additional_requested_fields(self):
|
|
self.add_additional_requested_fields()
|
|
serializer = self.create_serializer()
|
|
for serialized_block in serializer.data:
|
|
self.assert_extended_block(serialized_block)
|
|
assert len(serializer.data) == 28
|
|
|
|
def test_staff_fields(self):
|
|
"""
|
|
Test fields accessed by a staff user
|
|
"""
|
|
context = self.create_staff_context()
|
|
self.add_additional_requested_fields(context)
|
|
serializer = self.create_serializer(context)
|
|
for serialized_block in serializer.data:
|
|
self.assert_extended_block(serialized_block)
|
|
self.assert_staff_fields(serialized_block)
|
|
assert len(serializer.data) == 29
|
|
|
|
|
|
class TestBlockDictSerializer(TestBlockSerializerBase):
|
|
"""
|
|
Tests the BlockDictSerializer class, which returns a dict of blocks key-ed by its block_key.
|
|
"""
|
|
|
|
def create_serializer(self, context=None):
|
|
"""
|
|
creates a BlockDictSerializer
|
|
"""
|
|
if context is None:
|
|
context = self.serializer_context
|
|
return BlockDictSerializer(
|
|
context['block_structure'], many=False, context=context,
|
|
)
|
|
|
|
def test_basic(self):
|
|
serializer = self.create_serializer()
|
|
|
|
# verify root
|
|
assert serializer.data['root'] == str(self.block_structure.root_block_usage_key)
|
|
|
|
# verify blocks
|
|
for block_key_string, serialized_block in serializer.data['blocks'].items():
|
|
assert serialized_block['id'] == block_key_string
|
|
self.assert_basic_block(block_key_string, serialized_block)
|
|
assert len(serializer.data['blocks']) == 28
|
|
|
|
def test_additional_requested_fields(self):
|
|
self.add_additional_requested_fields()
|
|
serializer = self.create_serializer()
|
|
for serialized_block in serializer.data['blocks'].values():
|
|
self.assert_extended_block(serialized_block)
|
|
assert len(serializer.data['blocks']) == 28
|
|
|
|
def test_staff_fields(self):
|
|
"""
|
|
Test fields accessed by a staff user
|
|
"""
|
|
context = self.create_staff_context()
|
|
self.add_additional_requested_fields(context)
|
|
serializer = self.create_serializer(context)
|
|
for serialized_block in serializer.data['blocks'].values():
|
|
self.assert_extended_block(serialized_block)
|
|
self.assert_staff_fields(serialized_block)
|
|
assert len(serializer.data['blocks']) == 29
|