fix: move service initialization from LMS runtime init to module render
This commit is contained in:
@@ -34,6 +34,7 @@ from pyquery import PyQuery # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from web_fragments.fragment import Fragment # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xblock.completable import CompletableXBlockMixin # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xblock.core import XBlock, XBlockAside # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xblock.exceptions import NoSuchServiceError
|
||||
from xblock.field_data import FieldData # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xblock.fields import ScopeIds # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xblock.runtime import DictKeyValueStore, KvsFieldData, Runtime # lint-amnesty, pylint: disable=wrong-import-order
|
||||
@@ -46,7 +47,7 @@ from xmodule.contentstore.django import contentstore
|
||||
from xmodule.html_module import AboutBlock, CourseInfoBlock, HtmlBlock, StaticTabBlock
|
||||
from xmodule.lti_module import LTIBlock
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.django import ModuleI18nService, modulestore
|
||||
from xmodule.modulestore.tests.django_utils import (
|
||||
TEST_DATA_MONGO_AMNESTY_MODULESTORE,
|
||||
ModuleStoreTestCase,
|
||||
@@ -64,6 +65,8 @@ from common.djangoapps.student.tests.factories import GlobalStaffFactory
|
||||
from common.djangoapps.student.tests.factories import RequestFactoryNoCsrf
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from common.djangoapps.xblock_django.constants import ATTR_KEY_ANONYMOUS_USER_ID
|
||||
from lms.djangoapps.badges.tests.factories import BadgeClassFactory
|
||||
from lms.djangoapps.badges.tests.test_models import get_image
|
||||
from lms.djangoapps.courseware import module_render as render
|
||||
from lms.djangoapps.courseware.access_response import AccessResponse
|
||||
from lms.djangoapps.courseware.courses import get_course_info_section, get_course_with_access
|
||||
@@ -91,11 +94,34 @@ from common.djangoapps.xblock_django.models import XBlockConfiguration
|
||||
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
|
||||
|
||||
|
||||
@XBlock.needs("field-data")
|
||||
@XBlock.needs("i18n")
|
||||
@XBlock.needs("fs")
|
||||
@XBlock.needs("user")
|
||||
@XBlock.needs("bookmarks")
|
||||
@XBlock.needs('fs')
|
||||
@XBlock.needs('field-data')
|
||||
@XBlock.needs('mako')
|
||||
@XBlock.needs('user')
|
||||
@XBlock.needs('verification')
|
||||
@XBlock.needs('proctoring')
|
||||
@XBlock.needs('milestones')
|
||||
@XBlock.needs('credit')
|
||||
@XBlock.needs('bookmarks')
|
||||
@XBlock.needs('gating')
|
||||
@XBlock.needs('grade_utils')
|
||||
@XBlock.needs('user_state')
|
||||
@XBlock.needs('content_type_gating')
|
||||
@XBlock.needs('cache')
|
||||
@XBlock.needs('sandbox')
|
||||
@XBlock.needs('xqueue')
|
||||
@XBlock.needs('replace_urls')
|
||||
@XBlock.needs('rebind_user')
|
||||
@XBlock.needs('completion')
|
||||
@XBlock.needs('i18n')
|
||||
@XBlock.needs('library_tools')
|
||||
@XBlock.needs('partitions')
|
||||
@XBlock.needs('settings')
|
||||
@XBlock.needs('user_tags')
|
||||
@XBlock.needs('badging')
|
||||
@XBlock.needs('teams')
|
||||
@XBlock.needs('teams_configuration')
|
||||
@XBlock.needs('call_to_action')
|
||||
class PureXBlock(XBlock):
|
||||
"""
|
||||
Pure XBlock to use in tests.
|
||||
@@ -2232,12 +2258,25 @@ class TestEventPublishing(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
mock_track_function.return_value.assert_called_once_with(event_type, event)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class LMSXBlockServiceBindingTest(SharedModuleStoreTestCase):
|
||||
class LMSXBlockServiceMixin(SharedModuleStoreTestCase):
|
||||
"""
|
||||
Tests that the LMS Module System (XBlock Runtime) provides an expected set of services.
|
||||
Helper class that initializes the LmsModuleSystem.
|
||||
"""
|
||||
def _prepare_runtime(self):
|
||||
"""
|
||||
Instantiate the LmsModuleSystem.
|
||||
"""
|
||||
self.runtime, _ = render.get_module_system_for_user(
|
||||
self.user,
|
||||
self.student_data,
|
||||
self.descriptor,
|
||||
self.course.id,
|
||||
self.track_function,
|
||||
self.request_token,
|
||||
course=self.course
|
||||
)
|
||||
|
||||
@XBlock.register_temp_plugin(PureXBlock, identifier='pure')
|
||||
def setUp(self):
|
||||
"""
|
||||
Set up the user and other fields that will be used to instantiate the runtime.
|
||||
@@ -2248,46 +2287,168 @@ class LMSXBlockServiceBindingTest(SharedModuleStoreTestCase):
|
||||
self.student_data = Mock()
|
||||
self.track_function = Mock()
|
||||
self.request_token = Mock()
|
||||
self.descriptor = ItemFactory(category="pure", parent=self.course)
|
||||
self._prepare_runtime()
|
||||
|
||||
@XBlock.register_temp_plugin(PureXBlock, identifier='pure')
|
||||
@ddt.data("user", "i18n", "fs", "field-data", "bookmarks")
|
||||
|
||||
@ddt.ddt
|
||||
class LMSXBlockServiceBindingTest(LMSXBlockServiceMixin):
|
||||
"""
|
||||
Tests that the LMS Module System (XBlock Runtime) provides an expected set of services.
|
||||
"""
|
||||
|
||||
@ddt.data(
|
||||
'fs',
|
||||
'field-data',
|
||||
'mako',
|
||||
'user',
|
||||
'verification',
|
||||
'proctoring',
|
||||
'milestones',
|
||||
'credit',
|
||||
'bookmarks',
|
||||
'gating',
|
||||
'grade_utils',
|
||||
'user_state',
|
||||
'content_type_gating',
|
||||
'cache',
|
||||
'sandbox',
|
||||
'xqueue',
|
||||
'replace_urls',
|
||||
'rebind_user',
|
||||
'completion',
|
||||
'i18n',
|
||||
'library_tools',
|
||||
'partitions',
|
||||
'settings',
|
||||
'user_tags',
|
||||
'teams',
|
||||
'teams_configuration',
|
||||
'call_to_action',
|
||||
)
|
||||
def test_expected_services_exist(self, expected_service):
|
||||
"""
|
||||
Tests that the 'user', 'i18n', and 'fs' services are provided by the LMS runtime.
|
||||
"""
|
||||
descriptor = ItemFactory(category="pure", parent=self.course)
|
||||
runtime, _ = render.get_module_system_for_user(
|
||||
self.user,
|
||||
self.student_data,
|
||||
descriptor,
|
||||
self.course.id,
|
||||
self.track_function,
|
||||
self.request_token,
|
||||
course=self.course
|
||||
)
|
||||
service = runtime.service(descriptor, expected_service)
|
||||
service = self.runtime.service(self.descriptor, expected_service)
|
||||
assert service is not None
|
||||
|
||||
@XBlock.register_temp_plugin(PureXBlock, identifier='pure')
|
||||
def test_beta_tester_fields_added(self):
|
||||
"""
|
||||
Tests that the beta tester fields are set on LMS runtime.
|
||||
"""
|
||||
descriptor = ItemFactory(category="pure", parent=self.course)
|
||||
descriptor.days_early_for_beta = 5
|
||||
runtime, _ = render.get_module_system_for_user(
|
||||
self.user,
|
||||
self.student_data,
|
||||
descriptor,
|
||||
self.course.id,
|
||||
self.track_function,
|
||||
self.request_token,
|
||||
course=self.course
|
||||
)
|
||||
self.descriptor.days_early_for_beta = 5
|
||||
self._prepare_runtime()
|
||||
|
||||
# pylint: disable=no-member
|
||||
assert not runtime.user_is_beta_tester
|
||||
assert runtime.days_early_for_beta == 5
|
||||
assert not self.runtime.user_is_beta_tester
|
||||
assert self.runtime.days_early_for_beta == 5
|
||||
|
||||
def test_get_set_tag(self):
|
||||
"""
|
||||
Tests the user service interface.
|
||||
"""
|
||||
scope = 'course'
|
||||
key = 'key1'
|
||||
|
||||
# test for when we haven't set the tag yet
|
||||
tag = self.runtime.service(self.descriptor, 'user_tags').get_tag(scope, key)
|
||||
assert tag is None
|
||||
|
||||
# set the tag
|
||||
set_value = 'value'
|
||||
self.runtime.service(self.descriptor, 'user_tags').set_tag(scope, key, set_value)
|
||||
tag = self.runtime.service(self.descriptor, 'user_tags').get_tag(scope, key)
|
||||
|
||||
assert tag == set_value
|
||||
|
||||
# Try to set tag in wrong scope
|
||||
with pytest.raises(ValueError):
|
||||
self.runtime.service(self.descriptor, 'user_tags').set_tag('fake_scope', key, set_value)
|
||||
|
||||
# Try to get tag in wrong scope
|
||||
with pytest.raises(ValueError):
|
||||
self.runtime.service(self.descriptor, 'user_tags').get_tag('fake_scope', key)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestBadgingService(LMSXBlockServiceMixin):
|
||||
"""Test the badging service interface"""
|
||||
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_OPENBADGES': True})
|
||||
def test_service_rendered(self):
|
||||
self._prepare_runtime()
|
||||
assert self.runtime.service(self.descriptor, 'badging')
|
||||
|
||||
def test_no_service_rendered(self):
|
||||
with pytest.raises(NoSuchServiceError):
|
||||
self.runtime.service(self.descriptor, 'badging')
|
||||
|
||||
@ddt.data(True, False)
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_OPENBADGES': True})
|
||||
def test_course_badges_toggle(self, toggle):
|
||||
self.course = CourseFactory.create(metadata={'issue_badges': toggle})
|
||||
self._prepare_runtime()
|
||||
assert self.runtime.service(self.descriptor, 'badging').course_badges_enabled is toggle
|
||||
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_OPENBADGES': True})
|
||||
def test_get_badge_class(self):
|
||||
self._prepare_runtime()
|
||||
badge_service = self.runtime.service(self.descriptor, 'badging')
|
||||
premade_badge_class = BadgeClassFactory.create()
|
||||
# Ignore additional parameters. This class already exists.
|
||||
# We should get back the first class we created, rather than a new one.
|
||||
with get_image('good') as image_handle:
|
||||
badge_class = badge_service.get_badge_class(
|
||||
slug='test_slug', issuing_component='test_component', description='Attempted override',
|
||||
criteria='test', display_name='Testola', image_file_handle=image_handle
|
||||
)
|
||||
# These defaults are set on the factory.
|
||||
assert badge_class.criteria == 'https://example.com/syllabus'
|
||||
assert badge_class.display_name == 'Test Badge'
|
||||
assert badge_class.description == "Yay! It's a test badge."
|
||||
# File name won't always be the same.
|
||||
assert badge_class.image.path == premade_badge_class.image.path
|
||||
|
||||
|
||||
class TestI18nService(LMSXBlockServiceMixin):
|
||||
""" Test ModuleI18nService """
|
||||
|
||||
def test_module_i18n_lms_service(self):
|
||||
"""
|
||||
Test: module i18n service in LMS
|
||||
"""
|
||||
i18n_service = self.runtime.service(self.descriptor, 'i18n')
|
||||
assert i18n_service is not None
|
||||
assert isinstance(i18n_service, ModuleI18nService)
|
||||
|
||||
def test_no_service_exception_with_none_declaration_(self):
|
||||
"""
|
||||
Test: NoSuchServiceError should be raised block declaration returns none
|
||||
"""
|
||||
self.descriptor.service_declaration = Mock(return_value=None)
|
||||
with pytest.raises(NoSuchServiceError):
|
||||
self.runtime.service(self.descriptor, 'i18n')
|
||||
|
||||
def test_no_service_exception_(self):
|
||||
"""
|
||||
Test: NoSuchServiceError should be raised if i18n service is none.
|
||||
"""
|
||||
self.runtime._services['i18n'] = None # pylint: disable=protected-access
|
||||
with pytest.raises(NoSuchServiceError):
|
||||
self.runtime.service(self.descriptor, 'i18n')
|
||||
|
||||
def test_i18n_service_callable(self):
|
||||
"""
|
||||
Test: _services dict should contain the callable i18n service in LMS.
|
||||
"""
|
||||
assert callable(self.runtime._services.get('i18n')) # pylint: disable=protected-access
|
||||
|
||||
def test_i18n_service_not_callable(self):
|
||||
"""
|
||||
Test: i18n service should not be callable in LMS after initialization.
|
||||
"""
|
||||
assert not callable(self.runtime.service(self.descriptor, 'i18n'))
|
||||
|
||||
|
||||
class PureXBlockWithChildren(PureXBlock):
|
||||
|
||||
Reference in New Issue
Block a user