Skip to content

Commit c215ec0

Browse files
committed
chore: make the tests run for both BuiltIn and Extracted LTI Blocks
1 parent 1d9ca33 commit c215ec0

File tree

4 files changed

+135
-29
lines changed

4 files changed

+135
-29
lines changed

lms/djangoapps/courseware/tests/test_lti_integration.py

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,47 @@
11
"""LTI integration tests"""
22

33

4+
import importlib
45
import json
6+
import re
57
from collections import OrderedDict
68

79
from unittest import mock
10+
from unittest.mock import patch
811
import urllib
912
import oauthlib
1013
from django.conf import settings
14+
from django.test import override_settings
1115
from django.urls import reverse
16+
from xblock import plugin
1217

1318
from common.djangoapps.xblock_django.constants import ATTR_KEY_ANONYMOUS_USER_ID
1419
from lms.djangoapps.courseware.tests.helpers import BaseTestXmodule
1520
from lms.djangoapps.courseware.views.views import get_course_lti_endpoints
1621
from openedx.core.lib.url_utils import quote_slashes
1722
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
1823
from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory # lint-amnesty, pylint: disable=wrong-import-order
24+
from xmodule.tests.helpers import mock_render_template
25+
from xmodule import lti_block
1926

2027

21-
class TestLTI(BaseTestXmodule):
28+
class _TestLTIBase(BaseTestXmodule):
2229
"""
2330
Integration test for lti xmodule.
2431
2532
It checks overall code, by assuring that context that goes to template is correct.
2633
As part of that, checks oauth signature generation by mocking signing function
2734
of `oauthlib` library.
2835
"""
36+
__test__ = False
2937
CATEGORY = "lti"
3038

39+
@classmethod
40+
def setUpClass(cls):
41+
super().setUpClass()
42+
plugin.PLUGIN_CACHE = {}
43+
importlib.reload(lti_block)
44+
3145
def setUp(self):
3246
"""
3347
Mock oauth1 signing of requests library for testing.
@@ -115,21 +129,37 @@ def mocked_sign(self, *args, **kwargs):
115129
patcher.start()
116130
self.addCleanup(patcher.stop)
117131

118-
def test_lti_constructor(self):
132+
@patch('xblock.utils.resources.ResourceLoader.render_django_template', side_effect=mock_render_template)
133+
def test_lti_constructor(self, mock_render_django_template):
119134
generated_content = self.block.student_view(None).content
120-
expected_content = self.runtime.render_template('lti.html', self.expected_context)
135+
136+
if settings.USE_EXTRACTED_LTI_BLOCK:
137+
# Remove i18n service from the extracted LTI Block's rendered `student_view` content
138+
generated_content = re.sub(r"\{.*?}", "{}", generated_content)
139+
expected_content = self.runtime.render_template('templates/lti.html', self.expected_context)
140+
mock_render_django_template.assert_called_once()
141+
else:
142+
expected_content = self.runtime.render_template('lti.html', self.expected_context)
121143
assert generated_content == expected_content
122144

123-
def test_lti_preview_handler(self):
145+
@patch('xblock.utils.resources.ResourceLoader.render_django_template', side_effect=mock_render_template)
146+
def test_lti_preview_handler(self, mock_render_django_template):
124147
generated_content = self.block.preview_handler(None, None).body
125-
expected_content = self.runtime.render_template('lti_form.html', self.expected_context)
148+
149+
if settings.USE_EXTRACTED_LTI_BLOCK:
150+
expected_content = self.runtime.render_template('templates/lti_form.html', self.expected_context)
151+
mock_render_django_template.assert_called_once()
152+
else:
153+
expected_content = self.runtime.render_template('lti_form.html', self.expected_context)
126154
assert generated_content.decode('utf-8') == expected_content
127155

128156

129-
class TestLTIBlockListing(SharedModuleStoreTestCase):
157+
class _TestLTIBlockListingBase(SharedModuleStoreTestCase):
130158
"""
131159
a test for the rest endpoint that lists LTI blocks in a course
132160
"""
161+
162+
__test__ = False
133163
# arbitrary constant
134164
COURSE_SLUG = "100"
135165
COURSE_NAME = "test_course"
@@ -214,3 +244,23 @@ def test_lti_rest_non_get(self):
214244
request.method = method
215245
response = get_course_lti_endpoints(request, str(self.course.id))
216246
assert 405 == response.status_code
247+
248+
249+
@override_settings(USE_EXTRACTED_LTI_BLOCK=True)
250+
class TestLTIExtracted(_TestLTIBase):
251+
__test__ = True
252+
253+
254+
@override_settings(USE_EXTRACTED_LTI_BLOCK=False)
255+
class TestLTIBuiltIn(_TestLTIBase):
256+
__test__ = True
257+
258+
259+
@override_settings(USE_EXTRACTED_LTI_BLOCK=True)
260+
class TestLTIBlockListingExtracted(_TestLTIBlockListingBase):
261+
__test__ = True
262+
263+
264+
@override_settings(USE_EXTRACTED_LTI_BLOCK=False)
265+
class TestLTIBlockListingBuiltIn(_TestLTIBlockListingBase):
266+
__test__ = True

xmodule/lti_block.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -992,8 +992,17 @@ def is_past_due(self):
992992
return close_date is not None and datetime.datetime.now(UTC) > close_date
993993

994994

995-
LTIBlock = (
996-
_ExtractedLTIBlock if settings.USE_EXTRACTED_LTI_BLOCK
997-
else _BuiltInLTIBlock
998-
)
995+
LTIBlock = None
996+
997+
998+
def reset_class():
999+
"""Reset class as per django settings flag"""
1000+
global LTIBlock
1001+
LTIBlock = (
1002+
_ExtractedLTIBlock if settings.USE_EXTRACTED_LTI_BLOCK
1003+
else _BuiltInLTIBlock
1004+
)
1005+
return LTIBlock
1006+
1007+
reset_class()
9991008
LTIBlock.__name__ = "LTIBlock"

xmodule/tests/test_lti20_unit.py

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,47 @@
33

44
import datetime
55
import textwrap
6-
import unittest
6+
from django.conf import settings
7+
from django.test import TestCase, override_settings
78
from unittest.mock import Mock
89

910
from pytz import UTC
1011
from xblock.field_data import DictFieldData
1112

12-
from xmodule.lti_2_util import LTIError
13-
from xmodule.lti_block import LTIBlock
13+
from xmodule import lti_block
1414
from xmodule.tests.helpers import StubUserService
1515

1616
from . import get_test_system
1717

1818

19-
class LTI20RESTResultServiceTest(unittest.TestCase):
19+
from xmodule.lti_2_util import LTIError as BuiltInLTIError
20+
from xblocks_contrib.lti.lti_2_util import LTIError as ExtractedLTIError
21+
22+
23+
class _LTI20RESTResultServiceTestBase(TestCase):
2024
"""Logic tests for LTI block. LTI2.0 REST ResultService"""
2125

26+
__test__ = False
2227
USER_STANDIN = Mock()
2328
USER_STANDIN.id = 999
2429

30+
@classmethod
31+
def setUpClass(cls):
32+
super().setUpClass()
33+
cls.lti_class = lti_block.reset_class()
34+
if settings.USE_EXTRACTED_LTI_BLOCK:
35+
cls.LTIError = ExtractedLTIError
36+
else:
37+
cls.LTIError = BuiltInLTIError
38+
2539
def setUp(self):
2640
super().setUp()
2741
self.runtime = get_test_system(user=self.USER_STANDIN)
2842
self.environ = {'wsgi.url_scheme': 'http', 'REQUEST_METHOD': 'POST'}
2943
self.runtime.publish = Mock()
3044
self.runtime._services['rebind_user'] = Mock() # pylint: disable=protected-access
3145

32-
self.xblock = LTIBlock(self.runtime, DictFieldData({}), Mock())
46+
self.xblock = self.lti_class(self.runtime, DictFieldData({}), Mock())
3347
self.lti_id = self.xblock.lti_id
3448
self.xblock.due = None
3549
self.xblock.graceperiod = None
@@ -56,7 +70,7 @@ def test_lti20_rest_bad_contenttype(self):
5670
"""
5771
Input with bad content type
5872
"""
59-
with self.assertRaisesRegex(LTIError, "Content-Type must be"):
73+
with self.assertRaisesRegex(self.LTIError, "Content-Type must be"):
6074
request = Mock(headers={'Content-Type': 'Non-existent'})
6175
self.xblock.verify_lti_2_0_result_rest_headers(request)
6276

@@ -65,8 +79,8 @@ def test_lti20_rest_failed_oauth_body_verify(self):
6579
Input with bad oauth body hash verification
6680
"""
6781
err_msg = "OAuth body verification failed"
68-
self.xblock.verify_oauth_body_sign = Mock(side_effect=LTIError(err_msg))
69-
with self.assertRaisesRegex(LTIError, err_msg):
82+
self.xblock.verify_oauth_body_sign = Mock(side_effect=self.LTIError(err_msg))
83+
with self.assertRaisesRegex(self.LTIError, err_msg):
7084
request = Mock(headers={'Content-Type': 'application/vnd.ims.lis.v2.result+json'})
7185
self.xblock.verify_lti_2_0_result_rest_headers(request)
7286

@@ -99,7 +113,7 @@ def test_lti20_rest_bad_dispatch(self):
99113
fit the form user/<anon_id>
100114
"""
101115
for einput in self.BAD_DISPATCH_INPUTS:
102-
with self.assertRaisesRegex(LTIError, "No valid user id found in endpoint URL"):
116+
with self.assertRaisesRegex(self.LTIError, "No valid user id found in endpoint URL"):
103117
self.xblock.parse_lti_2_0_handler_suffix(einput)
104118

105119
GOOD_DISPATCH_INPUTS = [
@@ -160,7 +174,7 @@ def test_lti20_bad_json(self):
160174
"""
161175
for error_inputs, error_message in self.BAD_JSON_INPUTS:
162176
for einput in error_inputs:
163-
with self.assertRaisesRegex(LTIError, error_message):
177+
with self.assertRaisesRegex(self.LTIError, error_message):
164178
self.xblock.parse_lti_2_0_result_json(einput)
165179

166180
GOOD_JSON_INPUTS = [
@@ -341,7 +355,7 @@ def test_lti20_request_handler_bad_headers(self):
341355
Test that we get a 401 when header verification fails
342356
"""
343357
self.setup_system_xblock_mocks_for_lti20_request_test()
344-
self.xblock.verify_lti_2_0_result_rest_headers = Mock(side_effect=LTIError())
358+
self.xblock.verify_lti_2_0_result_rest_headers = Mock(side_effect=self.LTIError())
345359
mock_request = self.get_signed_lti20_mock_request(self.GOOD_JSON_PUT)
346360
response = self.xblock.lti_2_0_result_rest_handler(mock_request, "user/abcd")
347361
assert response.status_code == 401
@@ -360,7 +374,7 @@ def test_lti20_request_handler_bad_json(self):
360374
Test that we get a 404 when json verification fails
361375
"""
362376
self.setup_system_xblock_mocks_for_lti20_request_test()
363-
self.xblock.parse_lti_2_0_result_json = Mock(side_effect=LTIError())
377+
self.xblock.parse_lti_2_0_result_json = Mock(side_effect=self.LTIError())
364378
mock_request = self.get_signed_lti20_mock_request(self.GOOD_JSON_PUT)
365379
response = self.xblock.lti_2_0_result_rest_handler(mock_request, "user/abcd")
366380
assert response.status_code == 404
@@ -385,3 +399,13 @@ def test_lti20_request_handler_grade_past_due(self):
385399
mock_request = self.get_signed_lti20_mock_request(self.GOOD_JSON_PUT)
386400
response = self.xblock.lti_2_0_result_rest_handler(mock_request, "user/abcd")
387401
assert response.status_code == 404
402+
403+
404+
@override_settings(USE_EXTRACTED_LTI_BLOCK=True)
405+
class TestLTI20RESTResultServiceWithExtracted(_LTI20RESTResultServiceTestBase):
406+
__test__ = True
407+
408+
409+
@override_settings(USE_EXTRACTED_LTI_BLOCK=False)
410+
class TestLTI20RESTResultServiceWithBuiltIn(_LTI20RESTResultServiceTestBase):
411+
__test__ = True

xmodule/tests/test_lti_unit.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,30 @@
2222

2323
from common.djangoapps.xblock_django.constants import ATTR_KEY_ANONYMOUS_USER_ID
2424
from xmodule.fields import Timedelta
25-
from xmodule.lti_2_util import LTIError
26-
from xmodule.lti_block import LTIBlock
25+
from xmodule import lti_block
2726
from xmodule.tests.helpers import StubUserService
2827

2928
from . import get_test_system
3029

30+
from xmodule.lti_2_util import LTIError as BuiltInLTIError
31+
from xblocks_contrib.lti.lti_2_util import LTIError as ExtractedLTIError
32+
3133

3234
@override_settings(LMS_BASE="edx.org")
33-
class LTIBlockTest(TestCase):
35+
class _TestLTIBase(TestCase):
3436
"""Logic tests for LTI block."""
3537

38+
__test__ = False
39+
40+
@classmethod
41+
def setUpClass(cls):
42+
super().setUpClass()
43+
cls.lti_class = lti_block.reset_class()
44+
if settings.USE_EXTRACTED_LTI_BLOCK:
45+
cls.LTIError = ExtractedLTIError
46+
else:
47+
cls.LTIError = BuiltInLTIError
48+
3649
def setUp(self):
3750
super().setUp()
3851
self.environ = {'wsgi.url_scheme': 'http', 'REQUEST_METHOD': 'POST'}
@@ -67,7 +80,7 @@ def setUp(self):
6780
self.runtime.publish = Mock()
6881
self.runtime._services['rebind_user'] = Mock() # pylint: disable=protected-access
6982

70-
self.xblock = LTIBlock(
83+
self.xblock = self.lti_class(
7184
self.runtime,
7285
DictFieldData({}),
7386
ScopeIds(None, None, None, BlockUsageLocator(self.course_id, 'lti', 'name'))
@@ -375,7 +388,7 @@ def test_bad_client_key_secret(self):
375388
runtime = Mock(modulestore=modulestore)
376389
self.xblock.runtime = runtime
377390
self.xblock.lti_id = 'lti_id'
378-
with pytest.raises(LTIError):
391+
with pytest.raises(self.LTIError):
379392
self.xblock.get_client_key_secret()
380393

381394
@patch('xmodule.lti_block.signature.verify_hmac_sha1', Mock(return_value=True))
@@ -469,7 +482,7 @@ def test_failed_verify_oauth_body_sign(self):
469482
"""
470483
Oauth signing verify fail.
471484
"""
472-
with pytest.raises(LTIError):
485+
with pytest.raises(self.LTIError):
473486
req = self.get_signed_grade_mock_request()
474487
self.xblock.verify_oauth_body_sign(req)
475488

@@ -524,7 +537,7 @@ def test_bad_custom_params(self):
524537
self.xblock.custom_parameters = bad_custom_params
525538
self.xblock.get_client_key_secret = Mock(return_value=('test_client_key', 'test_client_secret'))
526539
self.xblock.oauth_params = Mock()
527-
with pytest.raises(LTIError):
540+
with pytest.raises(self.LTIError):
528541
self.xblock.get_input_fields()
529542

530543
def test_max_score(self):
@@ -542,3 +555,13 @@ def test_context_id(self):
542555
Tests that LTI parameter context_id is equal to course_id.
543556
"""
544557
assert str(self.course_id) == self.xblock.context_id
558+
559+
560+
@override_settings(USE_EXTRACTED_LTI_BLOCK=True)
561+
class TestLTIExtracted(_TestLTIBase):
562+
__test__ = True
563+
564+
565+
@override_settings(USE_EXTRACTED_LTI_BLOCK=False)
566+
class TestLTIBuiltIn(_TestLTIBase):
567+
__test__ = True

0 commit comments

Comments
 (0)