refactor: fixes tests

* UserStubService now takes user, user_is_staff, and anonymous_user_id
* get_test_system() creates a UserStubService with an anonymous_user_id of 'student'
* Removes references to deprecated ModuleSystem attributes from test code
* Fixes and simplifies the ConditionalBlock tests, using get_module provided by TestModuleSystem instead of trying to mock out all the pieces.

(cherry picked from commit 927016a8df)
This commit is contained in:
Jillian Vogel
2021-08-31 16:26:44 +09:30
parent bafc0cd91f
commit b1b3c646ad
9 changed files with 44 additions and 71 deletions

View File

@@ -34,8 +34,10 @@ from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.draft_and_published import ModuleStoreDraftAndPublished
from xmodule.modulestore.inheritance import InheritanceMixin
from xmodule.modulestore.xml import CourseLocationManager
from xmodule.tests.helpers import StubUserService
from xmodule.x_module import ModuleSystem, XModuleDescriptor, XModuleMixin
MODULE_DIR = path(__file__).dirname()
# Location of common test DATA directory
# '../../../../edx-platform/common/test/data/'
@@ -90,6 +92,7 @@ class TestModuleSystem(ModuleSystem): # pylint: disable=abstract-method
def get_test_system(
course_id=CourseKey.from_string('/'.join(['org', 'course', 'run'])),
user=None,
user_is_staff=False,
):
"""
Construct a test ModuleSystem instance.
@@ -105,6 +108,11 @@ def get_test_system(
"""
if not user:
user = Mock(name='get_test_system.user', is_staff=False)
user_service = StubUserService(
user=user,
anonymous_user_id='student',
user_is_staff=user_is_staff,
)
descriptor_system = get_test_descriptor_system()
@@ -130,11 +138,13 @@ def get_test_system(
get_module=get_module,
render_template=mock_render_template,
replace_urls=str,
user=user,
get_real_user=lambda __: user,
filestore=Mock(name='get_test_system.filestore', root_path='.'),
debug=True,
hostname="edx.org",
services={
'user': user_service,
},
xqueue={
'interface': None,
'callback_url': '/',
@@ -143,7 +153,6 @@ def get_test_system(
'construct_callback': Mock(name='get_test_system.xqueue.construct_callback', side_effect="/"),
},
node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"),
anonymous_student_id='student',
course_id=course_id,
error_descriptor_class=ErrorBlock,
get_user_role=Mock(name='get_test_system.get_user_role', is_staff=False),

View File

@@ -4,6 +4,7 @@ Utility methods for unit tests.
import filecmp
from unittest.mock import Mock
from path import Path as path
from xblock.reference.user_service import UserService, XBlockUser
@@ -34,8 +35,10 @@ class StubUserService(UserService):
Stub UserService for testing the sequence module.
"""
def __init__(self, is_anonymous=False, **kwargs):
self.is_anonymous = is_anonymous
def __init__(self, user=None, user_is_staff=False, anonymous_user_id=None, **kwargs):
self.user = user or Mock(name='StubUserService.user')
self.user_is_staff = user_is_staff
self.anonymous_user_id = anonymous_user_id
super().__init__(**kwargs)
def get_current_user(self):
@@ -43,9 +46,12 @@ class StubUserService(UserService):
Implements abstract method for getting the current user.
"""
user = XBlockUser()
if self.is_anonymous:
if self.user.is_authenticated:
user.opt_attrs['edx-platform.anonymous_user_id'] = self.anonymous_user_id
user.opt_attrs['edx-platform.user_is_staff'] = self.user_is_staff
user.opt_attrs['edx-platform.user_id'] = self.user.id
user.opt_attrs['edx-platform.username'] = self.user.username
else:
user.opt_attrs['edx-platform.username'] = 'anonymous'
user.opt_attrs['edx-platform.is_authenticated'] = False
else:
user.opt_attrs['edx-platform.username'] = 'bilbo'
return user

View File

@@ -113,8 +113,7 @@ class CapaFactory:
# since everything else is a string.
field_data['attempts'] = int(attempts)
system = get_test_system(course_id=location.course_key)
system.user_is_staff = kwargs.get('user_is_staff', False)
system = get_test_system(course_id=location.course_key, user_is_staff=kwargs.get('user_is_staff', False))
system.render_template = Mock(return_value="<div>Test Template HTML</div>")
module = ProblemBlock(
system,

View File

@@ -178,7 +178,6 @@ class ConditionalBlockBasicTest(unittest.TestCase):
modules['cond_module'].save()
modules['source_module'].is_attempted = "false"
ajax = json.loads(modules['cond_module'].handle_ajax('', ''))
print("ajax: ", ajax)
fragments = ajax['fragments']
assert not any(('This is a secret' in item['content']) for item in fragments)
@@ -186,7 +185,6 @@ class ConditionalBlockBasicTest(unittest.TestCase):
modules['source_module'].is_attempted = "true"
ajax = json.loads(modules['cond_module'].handle_ajax('', ''))
modules['cond_module'].save()
print("post-attempt ajax: ", ajax)
fragments = ajax['fragments']
assert any(('This is a secret' in item['content']) for item in fragments)
@@ -220,63 +218,29 @@ class ConditionalBlockXmlTest(unittest.TestCase):
Make sure ConditionalBlock works, by loading data in from an XML-defined course.
"""
@staticmethod
def get_system(load_error_modules=True):
'''Get a dummy system'''
return DummySystem(load_error_modules)
def setUp(self):
super().setUp()
self.test_system = get_test_system()
def get_course(self, name):
"""Get a test course by directory name. If there's more than one, error."""
print(f"Importing {name}")
modulestore = XMLModuleStore(DATA_DIR, source_dirs=[name])
courses = modulestore.get_courses()
self.modulestore = modulestore # lint-amnesty, pylint: disable=attribute-defined-outside-init
self.modulestore = XMLModuleStore(DATA_DIR, source_dirs=['conditional_and_poll'])
courses = self.modulestore.get_courses()
assert len(courses) == 1
return courses[0]
self.course = courses[0]
def get_module_for_location(self, location):
descriptor = self.modulestore.get_item(location, depth=None)
return self.test_system.get_module(descriptor)
@patch('xmodule.x_module.descriptor_global_local_resource_url')
@patch.dict(settings.FEATURES, {'ENABLE_EDXNOTES': False})
def test_conditional_module(self, _):
"""Make sure that conditional module works"""
print("Starting import")
course = self.get_course('conditional_and_poll')
print("Course: ", course)
print("id: ", course.id)
def inner_get_module(descriptor):
if isinstance(descriptor, BlockUsageLocator):
location = descriptor
descriptor = self.modulestore.get_item(location, depth=None)
descriptor.xmodule_runtime = get_test_system()
descriptor.xmodule_runtime.descriptor_runtime = descriptor._runtime # pylint: disable=protected-access
descriptor.xmodule_runtime.get_module = inner_get_module
return descriptor
# edx - HarvardX
# cond_test - ER22x
location = BlockUsageLocator(CourseLocator("HarvardX", "ER22x", "2013_Spring", deprecated=True),
"conditional", "condone", deprecated=True)
def replace_urls(text, staticfiles_prefix=None, replace_prefix='/static/', course_namespace=None): # lint-amnesty, pylint: disable=unused-argument
return text
self.test_system.replace_urls = replace_urls
self.test_system.get_module = inner_get_module
module = inner_get_module(location)
print("module: ", module)
print("module children: ", module.get_children())
print("module display items (children): ", module.get_display_items())
module = self.get_module_for_location(location)
html = module.render(STUDENT_VIEW).content
print("html type: ", type(html))
print("html: ", html)
html_expect = module.xmodule_runtime.render_template(
'conditional_ajax.html',
{
@@ -288,29 +252,20 @@ class ConditionalBlockXmlTest(unittest.TestCase):
)
assert html == html_expect
gdi = module.get_display_items()
print("gdi=", gdi)
ajax = json.loads(module.handle_ajax('', ''))
module.save()
print("ajax: ", ajax)
fragments = ajax['fragments']
assert not any(('This is a secret' in item['content']) for item in fragments)
# Now change state of the capa problem to make it completed
inner_module = inner_get_module(location.replace(category="problem", name='choiceprob'))
inner_module = self.get_module_for_location(location.replace(category="problem", name='choiceprob'))
inner_module.attempts = 1
# Save our modifications to the underlying KeyValueStore so they can be persisted
inner_module.save()
ajax = json.loads(module.handle_ajax('', ''))
module.save()
print("post-attempt ajax: ", ajax)
fragments = ajax['fragments']
assert any(('This is a secret' in item['content']) for item in fragments)
maxDiff = None
def test_conditional_module_with_empty_sources_list(self):
"""
If a ConditionalBlock is initialized with an empty sources_list, we assert that the sources_list is set

View File

@@ -4,6 +4,7 @@ import unittest
from unittest.mock import Mock
import ddt
from django.contrib.auth.models import AnonymousUser
from django.test.utils import override_settings
from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator
from xblock.field_data import DictFieldData
@@ -135,8 +136,7 @@ class HtmlBlockSubstitutionTestCase(unittest.TestCase): # lint-amnesty, pylint:
def test_substitution_without_anonymous_student_id(self):
sample_xml = '''%%USER_ID%%'''
field_data = DictFieldData({'data': sample_xml})
module_system = get_test_system()
module_system.anonymous_student_id = None
module_system = get_test_system(user=AnonymousUser())
module = HtmlBlock(module_system, field_data, Mock())
assert module.get_html() == sample_xml

View File

@@ -37,8 +37,7 @@ class LTI20RESTResultServiceTest(unittest.TestCase):
mocked_course = Mock(name='mocked_course', lti_passports=['lti_id:test_client:test_secret'])
modulestore = Mock(name='modulestore')
modulestore.get_course.return_value = mocked_course
runtime = Mock(name='runtime', modulestore=modulestore, anonymous_student_id='student')
self.xmodule.runtime = runtime
self.xmodule.runtime.modulestore = modulestore
self.xmodule.lti_id = "lti_id"
test_cases = ( # (before sanitize, after sanitize)

View File

@@ -16,6 +16,7 @@ from webob.request import Request
from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds
from common.djangoapps.xblock_django.constants import ATTR_KEY_ANONYMOUS_USER_ID
from xmodule.fields import Timedelta
from xmodule.lti_2_util import LTIError
from xmodule.lti_module import LTIBlock
@@ -59,13 +60,14 @@ class LTIBlockTest(unittest.TestCase):
self.system.get_real_user = Mock()
self.system.publish = Mock()
self.system.rebind_noauth_module_to_user = Mock()
self.user_id = self.system.anonymous_student_id
self.xmodule = LTIBlock(
self.system,
DictFieldData({}),
ScopeIds(None, None, None, BlockUsageLocator(self.system.course_id, 'lti', 'name'))
)
current_user = self.system.service(self.xmodule, 'user').get_current_user()
self.user_id = current_user.opt_attrs.get(ATTR_KEY_ANONYMOUS_USER_ID)
self.lti_id = self.xmodule.lti_id
self.unquoted_resource_link_id = '{}-i4x-2-3-lti-31de800015cf4afb973356dbe81496df'.format(
self.xmodule.runtime.hostname

View File

@@ -13,6 +13,7 @@ from unittest.mock import Mock, patch
import pytz
import ddt
from fs.memoryfs import MemoryFS
from django.contrib.auth.models import AnonymousUser
from . import get_test_system
from .helpers import StubUserService
@@ -157,11 +158,11 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
now = datetime.now(pytz.UTC)
self.vertical.due = now + timedelta(days=days)
if view == STUDENT_VIEW:
self.module_system._services['user'] = StubUserService()
self.module_system._services['user'] = StubUserService(user=Mock(username=self.username))
self.module_system._services['completion'] = StubCompletionService(enabled=True,
completion_value=completion_value)
elif view == PUBLIC_VIEW:
self.module_system._services['user'] = StubUserService(is_anonymous=True)
self.module_system._services['user'] = StubUserService(user=AnonymousUser())
html = self.module_system.render(
self.vertical, view, self.default_context if context is None else context

View File

@@ -10,6 +10,7 @@ import oauthlib
from django.conf import settings
from django.urls import reverse
from common.djangoapps.xblock_django.constants import ATTR_KEY_ANONYMOUS_USER_ID
from lms.djangoapps.courseware.tests.helpers import BaseTestXmodule
from lms.djangoapps.courseware.views.views import get_course_lti_endpoints
from openedx.core.lib.url_utils import quote_slashes
@@ -40,7 +41,8 @@ class TestLTI(BaseTestXmodule):
# Note: this course_id is actually a course_key
context_id = str(self.item_descriptor.course_id)
user_id = str(self.item_descriptor.xmodule_runtime.anonymous_student_id)
user_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'user')
user_id = str(user_service.get_current_user().opt_attrs.get(ATTR_KEY_ANONYMOUS_USER_ID))
hostname = self.item_descriptor.xmodule_runtime.hostname
resource_link_id = str(urllib.parse.quote(f'{hostname}-{self.item_descriptor.location.html_id()}'))