Files
edx-platform/openedx/features/course_experience/tests/test_url_helpers.py
Matjaz Gregoric fdf600a527 fix: support Learning MFE hosted on subpath.
The Referrer header from the MFE hosted on a different origin does not
include the path, so make sure to ignore the path portion of the
learning MFE base URL when determining whether the request is coming
from the new MFE or the old courseware view.
2021-06-13 10:32:46 +02:00

288 lines
10 KiB
Python

"""
Test some of the functions in url_helpers
"""
from unittest import mock
import ddt
from django.test import TestCase
from django.test.client import RequestFactory
from django.test.utils import override_settings
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from .. import url_helpers
def _patch_courseware_mfe_is_active(ret_val):
return mock.patch.object(
url_helpers,
'courseware_mfe_is_active',
return_value=ret_val,
)
@ddt.ddt
class IsLearningMfeTests(TestCase):
"""
Test is_request_from_learning_mfe.
"""
def setUp(self):
super().setUp()
self.request_factory = RequestFactory()
@ddt.data(
('', '', False,),
('https://mfe-url/', 'https://platform-url/course', False,),
('https://mfe-url/', 'https://mfe-url/course', True,),
('https://mfe-url/', 'https://mfe-url/', True,),
('https://mfe-url/subpath/', 'https://platform-url/course', False,),
('https://mfe-url/subpath/', 'https://mfe-url/course', True,),
('https://mfe-url/subpath/', 'https://mfe-url/', True,),
)
@ddt.unpack
def test_is_request_from_learning_mfe(self, mfe_url, referrer_url, is_mfe):
with override_settings(LEARNING_MICROFRONTEND_URL=mfe_url):
request = self.request_factory.get('/course')
request.META['HTTP_REFERER'] = referrer_url
assert url_helpers.is_request_from_learning_mfe(request) == is_mfe
@ddt.ddt
class GetCoursewareUrlTests(SharedModuleStoreTestCase):
"""
Test get_courseware_url.
Mock out `courseware_mfe_is_active`; that is tested elseware.
"""
@classmethod
def setUpClass(cls):
"""
Set up data used across test functions.
"""
# pylint: disable=super-method-not-called
with super().setUpClassAndTestData():
cls.items = cls.create_test_courses()
@classmethod
def create_test_courses(cls):
"""
We build two simple course structures (one using Split, the other Old Mongo).
Each course structure is a non-branching tree from the root Course block down
to the Component-level problem block; that is, we make one item for each course
hierarchy level.
For easy access in the test functions, we return them in a dict like this:
{
"split": {
"course_run": <course block for Split Mongo course>,
"section": <chapter block in course run>
"subsection": <sequence block in section>
"unit": <vertical block in subsection>
"component": <problem block in unit>
},
"mongo": {
"course_run": <course block for (deprecated) Old Mongo course>,
... etc ...
}
}
"""
# Make Split Mongo course.
with cls.store.default_store(ModuleStoreEnum.Type.split):
course_run = CourseFactory.create(
org='TestX',
number='UrlHelpers',
run='split',
display_name='URL Helpers Test Course',
)
with cls.store.bulk_operations(course_run.id):
section = ItemFactory.create(
parent_location=course_run.location,
category='chapter',
display_name="Generated Section",
)
subsection = ItemFactory.create(
parent_location=section.location,
category='sequential',
display_name="Generated Subsection",
)
unit = ItemFactory.create(
parent_location=subsection.location,
category='vertical',
display_name="Generated Unit",
)
component = ItemFactory.create(
parent_location=unit.location,
category='problem',
display_name="Generated Problem Component",
)
# Make (deprecated) Old Mongo course.
with cls.store.default_store(ModuleStoreEnum.Type.mongo):
deprecated_course_run = CourseFactory.create(
org='TestX',
number='UrlHelpers',
run='mongo',
display_name='URL Helpers Test Course (Deprecated)',
)
with cls.store.bulk_operations(deprecated_course_run.id):
deprecated_section = ItemFactory.create(
parent_location=deprecated_course_run.location,
category='chapter',
display_name="Generated Section",
)
deprecated_subsection = ItemFactory.create(
parent_location=deprecated_section.location,
category='sequential',
display_name="Generated Subsection",
)
deprecated_unit = ItemFactory.create(
parent_location=deprecated_subsection.location,
category='vertical',
display_name="Generated Unit",
)
deprecated_component = ItemFactory.create(
parent_location=deprecated_unit.location,
category='problem',
display_name="Generated Problem Component",
)
return {
ModuleStoreEnum.Type.split: {
'course_run': course_run,
'section': section,
'subsection': subsection,
'unit': unit,
'component': component,
},
ModuleStoreEnum.Type.mongo: {
'course_run': deprecated_course_run,
'section': deprecated_section,
'subsection': deprecated_subsection,
'unit': deprecated_unit,
'component': deprecated_component,
}
}
@ddt.data(
(
ModuleStoreEnum.Type.split,
'mfe',
'course_run',
'http://learning-mfe/course/course-v1:TestX+UrlHelpers+split'
),
(
ModuleStoreEnum.Type.split,
'mfe',
'section',
(
'http://learning-mfe/course/course-v1:TestX+UrlHelpers+split' +
'/block-v1:TestX+UrlHelpers+split+type@chapter+block@Generated_Section'
),
),
(
ModuleStoreEnum.Type.split,
'mfe',
'subsection',
(
'http://learning-mfe/course/course-v1:TestX+UrlHelpers+split' +
'/block-v1:TestX+UrlHelpers+split+type@sequential+block@Generated_Subsection'
),
),
(
ModuleStoreEnum.Type.split,
'mfe',
'unit',
(
'http://learning-mfe/course/course-v1:TestX+UrlHelpers+split' +
'/block-v1:TestX+UrlHelpers+split+type@sequential+block@Generated_Subsection' +
'/block-v1:TestX+UrlHelpers+split+type@vertical+block@Generated_Unit'
),
),
(
ModuleStoreEnum.Type.split,
'mfe',
'component',
(
'http://learning-mfe/course/course-v1:TestX+UrlHelpers+split' +
'/block-v1:TestX+UrlHelpers+split+type@sequential+block@Generated_Subsection' +
'/block-v1:TestX+UrlHelpers+split+type@vertical+block@Generated_Unit'
),
),
(
ModuleStoreEnum.Type.split,
'legacy',
'course_run',
'/courses/course-v1:TestX+UrlHelpers+split/courseware',
),
(
ModuleStoreEnum.Type.split,
'legacy',
'subsection',
'/courses/course-v1:TestX+UrlHelpers+split/courseware/Generated_Section/Generated_Subsection/',
),
(
ModuleStoreEnum.Type.split,
'legacy',
'unit',
'/courses/course-v1:TestX+UrlHelpers+split/courseware/Generated_Section/Generated_Subsection/1',
),
(
ModuleStoreEnum.Type.split,
'legacy',
'component',
'/courses/course-v1:TestX+UrlHelpers+split/courseware/Generated_Section/Generated_Subsection/1',
),
(
ModuleStoreEnum.Type.mongo,
'legacy',
'course_run',
'/courses/TestX/UrlHelpers/mongo/courseware',
),
(
ModuleStoreEnum.Type.mongo,
'legacy',
'subsection',
'/courses/TestX/UrlHelpers/mongo/courseware/Generated_Section/Generated_Subsection/',
),
(
ModuleStoreEnum.Type.mongo,
'legacy',
'unit',
'/courses/TestX/UrlHelpers/mongo/courseware/Generated_Section/Generated_Subsection/1',
),
(
ModuleStoreEnum.Type.mongo,
'legacy',
'component',
'/courses/TestX/UrlHelpers/mongo/courseware/Generated_Section/Generated_Subsection/1',
),
)
@ddt.unpack
def test_get_courseware_url(
self,
store_type,
active_experience,
structure_level,
expected_path,
):
"""
Given:
* a `store_type` ('split' or [old] 'mongo'),
* an `active_experience` ('mfe' or 'legacy'),
* and a `structure_level` ('course_run', 'section', 'subsection', 'unit', or 'component'),
check that the expected path (URL without querystring) is returned by `get_courseware_url`.
"""
block = self.items[store_type][structure_level]
with _patch_courseware_mfe_is_active(active_experience == 'mfe') as mock_mfe_is_active:
url = url_helpers.get_courseware_url(block.location)
path = url.split('?')[0]
assert path == expected_path
course_run = self.items[store_type]['course_run']
mock_mfe_is_active.assert_called_once_with(course_run.id)