chore: make the tests run for both BuiltIn and Extracted LTI Blocks (#36020)
This commit is contained in:
committed by
GitHub
parent
68ba45a858
commit
a6c3c3236d
@@ -1,14 +1,19 @@
|
||||
"""LTI integration tests"""
|
||||
|
||||
|
||||
import importlib
|
||||
import json
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
|
||||
from unittest import mock
|
||||
from unittest.mock import patch
|
||||
import urllib
|
||||
import oauthlib
|
||||
from django.conf import settings
|
||||
from django.test import override_settings
|
||||
from django.urls import reverse
|
||||
from xblock import plugin
|
||||
|
||||
from common.djangoapps.xblock_django.constants import ATTR_KEY_ANONYMOUS_USER_ID
|
||||
from lms.djangoapps.courseware.tests.helpers import BaseTestXmodule
|
||||
@@ -16,9 +21,11 @@ from lms.djangoapps.courseware.views.views import get_course_lti_endpoints
|
||||
from openedx.core.lib.url_utils import quote_slashes
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.tests.helpers import mock_render_template
|
||||
from xmodule import lti_block
|
||||
|
||||
|
||||
class TestLTI(BaseTestXmodule):
|
||||
class _TestLTIBase(BaseTestXmodule):
|
||||
"""
|
||||
Integration test for lti xmodule.
|
||||
|
||||
@@ -26,8 +33,15 @@ class TestLTI(BaseTestXmodule):
|
||||
As part of that, checks oauth signature generation by mocking signing function
|
||||
of `oauthlib` library.
|
||||
"""
|
||||
__test__ = False
|
||||
CATEGORY = "lti"
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
plugin.PLUGIN_CACHE = {}
|
||||
importlib.reload(lti_block)
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Mock oauth1 signing of requests library for testing.
|
||||
@@ -115,21 +129,37 @@ class TestLTI(BaseTestXmodule):
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def test_lti_constructor(self):
|
||||
@patch('xblock.utils.resources.ResourceLoader.render_django_template', side_effect=mock_render_template)
|
||||
def test_lti_constructor(self, mock_render_django_template):
|
||||
generated_content = self.block.student_view(None).content
|
||||
expected_content = self.runtime.render_template('lti.html', self.expected_context)
|
||||
|
||||
if settings.USE_EXTRACTED_LTI_BLOCK:
|
||||
# Remove i18n service from the extracted LTI Block's rendered `student_view` content
|
||||
generated_content = re.sub(r"\{.*?}", "{}", generated_content)
|
||||
expected_content = self.runtime.render_template('templates/lti.html', self.expected_context)
|
||||
mock_render_django_template.assert_called_once()
|
||||
else:
|
||||
expected_content = self.runtime.render_template('lti.html', self.expected_context)
|
||||
assert generated_content == expected_content
|
||||
|
||||
def test_lti_preview_handler(self):
|
||||
@patch('xblock.utils.resources.ResourceLoader.render_django_template', side_effect=mock_render_template)
|
||||
def test_lti_preview_handler(self, mock_render_django_template):
|
||||
generated_content = self.block.preview_handler(None, None).body
|
||||
expected_content = self.runtime.render_template('lti_form.html', self.expected_context)
|
||||
|
||||
if settings.USE_EXTRACTED_LTI_BLOCK:
|
||||
expected_content = self.runtime.render_template('templates/lti_form.html', self.expected_context)
|
||||
mock_render_django_template.assert_called_once()
|
||||
else:
|
||||
expected_content = self.runtime.render_template('lti_form.html', self.expected_context)
|
||||
assert generated_content.decode('utf-8') == expected_content
|
||||
|
||||
|
||||
class TestLTIBlockListing(SharedModuleStoreTestCase):
|
||||
class _TestLTIBlockListingBase(SharedModuleStoreTestCase):
|
||||
"""
|
||||
a test for the rest endpoint that lists LTI blocks in a course
|
||||
"""
|
||||
|
||||
__test__ = False
|
||||
# arbitrary constant
|
||||
COURSE_SLUG = "100"
|
||||
COURSE_NAME = "test_course"
|
||||
@@ -214,3 +244,23 @@ class TestLTIBlockListing(SharedModuleStoreTestCase):
|
||||
request.method = method
|
||||
response = get_course_lti_endpoints(request, str(self.course.id))
|
||||
assert 405 == response.status_code
|
||||
|
||||
|
||||
@override_settings(USE_EXTRACTED_LTI_BLOCK=True)
|
||||
class TestLTIExtracted(_TestLTIBase):
|
||||
__test__ = True
|
||||
|
||||
|
||||
@override_settings(USE_EXTRACTED_LTI_BLOCK=False)
|
||||
class TestLTIBuiltIn(_TestLTIBase):
|
||||
__test__ = True
|
||||
|
||||
|
||||
@override_settings(USE_EXTRACTED_LTI_BLOCK=True)
|
||||
class TestLTIBlockListingExtracted(_TestLTIBlockListingBase):
|
||||
__test__ = True
|
||||
|
||||
|
||||
@override_settings(USE_EXTRACTED_LTI_BLOCK=False)
|
||||
class TestLTIBlockListingBuiltIn(_TestLTIBlockListingBase):
|
||||
__test__ = True
|
||||
|
||||
@@ -992,8 +992,17 @@ oauth_consumer_key="", oauth_signature="frVp4JuvT1mVXlxktiAUjQ7%2F1cw%3D"'}
|
||||
return close_date is not None and datetime.datetime.now(ZoneInfo("UTC")) > close_date
|
||||
|
||||
|
||||
LTIBlock = (
|
||||
_ExtractedLTIBlock if settings.USE_EXTRACTED_LTI_BLOCK
|
||||
else _BuiltInLTIBlock
|
||||
)
|
||||
LTIBlock = None
|
||||
|
||||
|
||||
def reset_class():
|
||||
"""Reset class as per django settings flag"""
|
||||
global LTIBlock
|
||||
LTIBlock = (
|
||||
_ExtractedLTIBlock if settings.USE_EXTRACTED_LTI_BLOCK
|
||||
else _BuiltInLTIBlock
|
||||
)
|
||||
return LTIBlock
|
||||
|
||||
reset_class()
|
||||
LTIBlock.__name__ = "LTIBlock"
|
||||
|
||||
@@ -3,25 +3,39 @@
|
||||
|
||||
import datetime
|
||||
import textwrap
|
||||
import unittest
|
||||
from django.conf import settings
|
||||
from django.test import TestCase, override_settings
|
||||
from unittest.mock import Mock
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from xblock.field_data import DictFieldData
|
||||
|
||||
from xmodule.lti_2_util import LTIError
|
||||
from xmodule.lti_block import LTIBlock
|
||||
from xmodule import lti_block
|
||||
from xmodule.tests.helpers import StubUserService
|
||||
|
||||
from . import get_test_system
|
||||
|
||||
|
||||
class LTI20RESTResultServiceTest(unittest.TestCase):
|
||||
from xmodule.lti_2_util import LTIError as BuiltInLTIError
|
||||
from xblocks_contrib.lti.lti_2_util import LTIError as ExtractedLTIError
|
||||
|
||||
|
||||
class _LTI20RESTResultServiceTestBase(TestCase):
|
||||
"""Logic tests for LTI block. LTI2.0 REST ResultService"""
|
||||
|
||||
__test__ = False
|
||||
USER_STANDIN = Mock()
|
||||
USER_STANDIN.id = 999
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.lti_class = lti_block.reset_class()
|
||||
if settings.USE_EXTRACTED_LTI_BLOCK:
|
||||
cls.LTIError = ExtractedLTIError
|
||||
else:
|
||||
cls.LTIError = BuiltInLTIError
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.runtime = get_test_system(user=self.USER_STANDIN)
|
||||
@@ -29,7 +43,7 @@ class LTI20RESTResultServiceTest(unittest.TestCase):
|
||||
self.runtime.publish = Mock()
|
||||
self.runtime._services['rebind_user'] = Mock() # pylint: disable=protected-access
|
||||
|
||||
self.xblock = LTIBlock(self.runtime, DictFieldData({}), Mock())
|
||||
self.xblock = self.lti_class(self.runtime, DictFieldData({}), Mock())
|
||||
self.lti_id = self.xblock.lti_id
|
||||
self.xblock.due = None
|
||||
self.xblock.graceperiod = None
|
||||
@@ -56,7 +70,7 @@ class LTI20RESTResultServiceTest(unittest.TestCase):
|
||||
"""
|
||||
Input with bad content type
|
||||
"""
|
||||
with self.assertRaisesRegex(LTIError, "Content-Type must be"):
|
||||
with self.assertRaisesRegex(self.LTIError, "Content-Type must be"):
|
||||
request = Mock(headers={'Content-Type': 'Non-existent'})
|
||||
self.xblock.verify_lti_2_0_result_rest_headers(request)
|
||||
|
||||
@@ -65,8 +79,8 @@ class LTI20RESTResultServiceTest(unittest.TestCase):
|
||||
Input with bad oauth body hash verification
|
||||
"""
|
||||
err_msg = "OAuth body verification failed"
|
||||
self.xblock.verify_oauth_body_sign = Mock(side_effect=LTIError(err_msg))
|
||||
with self.assertRaisesRegex(LTIError, err_msg):
|
||||
self.xblock.verify_oauth_body_sign = Mock(side_effect=self.LTIError(err_msg))
|
||||
with self.assertRaisesRegex(self.LTIError, err_msg):
|
||||
request = Mock(headers={'Content-Type': 'application/vnd.ims.lis.v2.result+json'})
|
||||
self.xblock.verify_lti_2_0_result_rest_headers(request)
|
||||
|
||||
@@ -99,7 +113,7 @@ class LTI20RESTResultServiceTest(unittest.TestCase):
|
||||
fit the form user/<anon_id>
|
||||
"""
|
||||
for einput in self.BAD_DISPATCH_INPUTS:
|
||||
with self.assertRaisesRegex(LTIError, "No valid user id found in endpoint URL"):
|
||||
with self.assertRaisesRegex(self.LTIError, "No valid user id found in endpoint URL"):
|
||||
self.xblock.parse_lti_2_0_handler_suffix(einput)
|
||||
|
||||
GOOD_DISPATCH_INPUTS = [
|
||||
@@ -160,7 +174,7 @@ class LTI20RESTResultServiceTest(unittest.TestCase):
|
||||
"""
|
||||
for error_inputs, error_message in self.BAD_JSON_INPUTS:
|
||||
for einput in error_inputs:
|
||||
with self.assertRaisesRegex(LTIError, error_message):
|
||||
with self.assertRaisesRegex(self.LTIError, error_message):
|
||||
self.xblock.parse_lti_2_0_result_json(einput)
|
||||
|
||||
GOOD_JSON_INPUTS = [
|
||||
@@ -341,7 +355,7 @@ class LTI20RESTResultServiceTest(unittest.TestCase):
|
||||
Test that we get a 401 when header verification fails
|
||||
"""
|
||||
self.setup_system_xblock_mocks_for_lti20_request_test()
|
||||
self.xblock.verify_lti_2_0_result_rest_headers = Mock(side_effect=LTIError())
|
||||
self.xblock.verify_lti_2_0_result_rest_headers = Mock(side_effect=self.LTIError())
|
||||
mock_request = self.get_signed_lti20_mock_request(self.GOOD_JSON_PUT)
|
||||
response = self.xblock.lti_2_0_result_rest_handler(mock_request, "user/abcd")
|
||||
assert response.status_code == 401
|
||||
@@ -360,7 +374,7 @@ class LTI20RESTResultServiceTest(unittest.TestCase):
|
||||
Test that we get a 404 when json verification fails
|
||||
"""
|
||||
self.setup_system_xblock_mocks_for_lti20_request_test()
|
||||
self.xblock.parse_lti_2_0_result_json = Mock(side_effect=LTIError())
|
||||
self.xblock.parse_lti_2_0_result_json = Mock(side_effect=self.LTIError())
|
||||
mock_request = self.get_signed_lti20_mock_request(self.GOOD_JSON_PUT)
|
||||
response = self.xblock.lti_2_0_result_rest_handler(mock_request, "user/abcd")
|
||||
assert response.status_code == 404
|
||||
@@ -385,3 +399,13 @@ class LTI20RESTResultServiceTest(unittest.TestCase):
|
||||
mock_request = self.get_signed_lti20_mock_request(self.GOOD_JSON_PUT)
|
||||
response = self.xblock.lti_2_0_result_rest_handler(mock_request, "user/abcd")
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@override_settings(USE_EXTRACTED_LTI_BLOCK=True)
|
||||
class TestLTI20RESTResultServiceWithExtracted(_LTI20RESTResultServiceTestBase):
|
||||
__test__ = True
|
||||
|
||||
|
||||
@override_settings(USE_EXTRACTED_LTI_BLOCK=False)
|
||||
class TestLTI20RESTResultServiceWithBuiltIn(_LTI20RESTResultServiceTestBase):
|
||||
__test__ = True
|
||||
|
||||
@@ -21,17 +21,30 @@ from xblock.fields import ScopeIds, Timedelta
|
||||
|
||||
|
||||
from common.djangoapps.xblock_django.constants import ATTR_KEY_ANONYMOUS_USER_ID
|
||||
from xmodule.lti_2_util import LTIError
|
||||
from xmodule.lti_block import LTIBlock
|
||||
from xmodule import lti_block
|
||||
from xmodule.tests.helpers import StubUserService
|
||||
|
||||
from . import get_test_system
|
||||
|
||||
from xmodule.lti_2_util import LTIError as BuiltInLTIError
|
||||
from xblocks_contrib.lti.lti_2_util import LTIError as ExtractedLTIError
|
||||
|
||||
|
||||
@override_settings(LMS_BASE="edx.org")
|
||||
class LTIBlockTest(TestCase):
|
||||
class _TestLTIBase(TestCase):
|
||||
"""Logic tests for LTI block."""
|
||||
|
||||
__test__ = False
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.lti_class = lti_block.reset_class()
|
||||
if settings.USE_EXTRACTED_LTI_BLOCK:
|
||||
cls.LTIError = ExtractedLTIError
|
||||
else:
|
||||
cls.LTIError = BuiltInLTIError
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.environ = {'wsgi.url_scheme': 'http', 'REQUEST_METHOD': 'POST'}
|
||||
@@ -66,7 +79,7 @@ class LTIBlockTest(TestCase):
|
||||
self.runtime.publish = Mock()
|
||||
self.runtime._services['rebind_user'] = Mock() # pylint: disable=protected-access
|
||||
|
||||
self.xblock = LTIBlock(
|
||||
self.xblock = self.lti_class(
|
||||
self.runtime,
|
||||
DictFieldData({}),
|
||||
ScopeIds(None, None, None, BlockUsageLocator(self.course_id, 'lti', 'name'))
|
||||
@@ -374,7 +387,7 @@ class LTIBlockTest(TestCase):
|
||||
runtime = Mock(modulestore=modulestore)
|
||||
self.xblock.runtime = runtime
|
||||
self.xblock.lti_id = 'lti_id'
|
||||
with pytest.raises(LTIError):
|
||||
with pytest.raises(self.LTIError):
|
||||
self.xblock.get_client_key_secret()
|
||||
|
||||
@patch('xmodule.lti_block.signature.verify_hmac_sha1', Mock(return_value=True))
|
||||
@@ -468,7 +481,7 @@ class LTIBlockTest(TestCase):
|
||||
"""
|
||||
Oauth signing verify fail.
|
||||
"""
|
||||
with pytest.raises(LTIError):
|
||||
with pytest.raises(self.LTIError):
|
||||
req = self.get_signed_grade_mock_request()
|
||||
self.xblock.verify_oauth_body_sign(req)
|
||||
|
||||
@@ -523,7 +536,7 @@ class LTIBlockTest(TestCase):
|
||||
self.xblock.custom_parameters = bad_custom_params
|
||||
self.xblock.get_client_key_secret = Mock(return_value=('test_client_key', 'test_client_secret'))
|
||||
self.xblock.oauth_params = Mock()
|
||||
with pytest.raises(LTIError):
|
||||
with pytest.raises(self.LTIError):
|
||||
self.xblock.get_input_fields()
|
||||
|
||||
def test_max_score(self):
|
||||
@@ -541,3 +554,13 @@ class LTIBlockTest(TestCase):
|
||||
Tests that LTI parameter context_id is equal to course_id.
|
||||
"""
|
||||
assert str(self.course_id) == self.xblock.context_id
|
||||
|
||||
|
||||
@override_settings(USE_EXTRACTED_LTI_BLOCK=True)
|
||||
class TestLTIExtracted(_TestLTIBase):
|
||||
__test__ = True
|
||||
|
||||
|
||||
@override_settings(USE_EXTRACTED_LTI_BLOCK=False)
|
||||
class TestLTIBuiltIn(_TestLTIBase):
|
||||
__test__ = True
|
||||
|
||||
Reference in New Issue
Block a user