Skip to content

Commit a42e959

Browse files
committed
fix: fix
1 parent 648fbf8 commit a42e959

4 files changed

Lines changed: 284 additions & 189 deletions

File tree

xblocks_contrib/lti/lti.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
from django.conf import settings
7171
from lxml import etree
7272
from oauthlib.oauth1.rfc5849 import signature
73+
from opaque_keys.edx.keys import UsageKey
7374
from pytz import UTC
7475
from webob import Response
7576
from web_fragments.fragment import Fragment
@@ -374,6 +375,26 @@ class LTIBlock(
374375
"ask_to_send_email", "ask_to_send_username", "has_score", "weight",
375376
)
376377

378+
@property
379+
def course_id(self):
380+
return self.location.course_key
381+
382+
@property
383+
def category(self):
384+
return self.scope_ids.block_type
385+
386+
@property
387+
def location(self):
388+
return self.scope_ids.usage_id
389+
390+
@location.setter
391+
def location(self, value):
392+
assert isinstance(value, UsageKey)
393+
self.scope_ids = self.scope_ids._replace(
394+
def_id=value, # Note: assigning a UsageKey as def_id is OK in old mongo / import system but wrong in split
395+
usage_id=value,
396+
)
397+
377398
def max_score(self):
378399
return self.weight if self.has_score else None
379400

xblocks_contrib/lti/tests/helpers.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -139,23 +139,17 @@ def get_test_system(
139139
course_id=CourseKey.from_string("/".join(["org", "course", "run"])),
140140
user=None,
141141
user_is_staff=False,
142-
user_location=None,
143142
):
144143
"""Construct a minimal test system for the LTIBlockTest."""
145-
# course_id = course_id or CourseKey.from_string("org/course/run")
146-
# user = user or Mock(id="student", is_staff=user_is_staff)
147144

148145
if not user:
149146
user = Mock(name='get_test_system.user', is_staff=False)
150-
if not user_location:
151-
user_location = Mock(name='get_test_system.user_location')
152147
user_service = StubUserService(
153148
user=user,
154149
anonymous_user_id='student',
155150
deprecated_anonymous_user_id='student',
156151
user_is_staff=user_is_staff,
157152
user_role='student',
158-
request_country_code=user_location,
159153
)
160154
runtime = MockRuntime(
161155
anonymous_student_id="student",
@@ -164,8 +158,4 @@ def get_test_system(
164158
}
165159
)
166160

167-
# Add necessary mocks
168-
runtime.publish = Mock(name="publish")
169-
runtime._services["rebind_user"] = Mock(name="rebind_user")
170-
171161
return runtime

xblocks_contrib/lti/tests/test_lti_2_unit.py renamed to xblocks_contrib/lti/tests/test_lti20_unit.py

Lines changed: 85 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
"""Tests for LTI Xmodule LTIv2.0 functional logic."""
22

3-
43
import datetime
54
import textwrap
6-
import unittest
75
from unittest.mock import Mock
86

97
from pytz import UTC
8+
from django.conf import settings
9+
from django.test import TestCase, override_settings
1010
from xblock.field_data import DictFieldData
1111

1212
from xblocks_contrib.lti.lti_2_util import LTIError
1313
from xblocks_contrib.lti.lti import LTIBlock
1414
from .helpers import StubUserService, get_test_system
1515

1616

17-
class LTI20RESTResultServiceTest(unittest.TestCase):
17+
@override_settings(LMS_BASE="edx.org")
18+
class LTI20RESTResultServiceTest(TestCase):
1819
"""Logic tests for LTI block. LTI2.0 REST ResultService"""
1920

2021
USER_STANDIN = Mock()
@@ -23,20 +24,27 @@ class LTI20RESTResultServiceTest(unittest.TestCase):
2324
def setUp(self):
2425
super().setUp()
2526
self.runtime = get_test_system(user=self.USER_STANDIN)
26-
self.environ = {'wsgi.url_scheme': 'http', 'REQUEST_METHOD': 'POST'}
27+
self.environ = {"wsgi.url_scheme": "http", "REQUEST_METHOD": "POST"}
2728
self.runtime.publish = Mock()
28-
self.runtime._services['rebind_user'] = Mock() # pylint: disable=protected-access
29+
self.runtime._services["rebind_user"] = Mock() # pylint: disable=protected-access
2930

3031
self.xblock = LTIBlock(self.runtime, DictFieldData({}), Mock())
3132
self.lti_id = self.xblock.lti_id
33+
34+
self.unquoted_resource_link_id = (
35+
"{}-i4x-2-3-lti-31de800015cf4afb973356dbe81496df".format(settings.LMS_BASE)
36+
)
37+
3238
self.xblock.due = None
3339
self.xblock.graceperiod = None
3440

3541
def test_sanitize_get_context(self):
3642
"""Tests that the get_context function does basic sanitization"""
3743
# get_context, unfortunately, requires a lot of mocking machinery
38-
mocked_course = Mock(name='mocked_course', lti_passports=['lti_id:test_client:test_secret'])
39-
modulestore = Mock(name='modulestore')
44+
mocked_course = Mock(
45+
name="mocked_course", lti_passports=["lti_id:test_client:test_secret"]
46+
)
47+
modulestore = Mock(name="modulestore")
4048
modulestore.get_course.return_value = mocked_course
4149
self.xblock.runtime.modulestore = modulestore
4250
self.xblock.lti_id = "lti_id"
@@ -48,14 +56,14 @@ def test_sanitize_get_context(self):
4856
)
4957
for case in test_cases:
5058
self.xblock.score_comment = case[0]
51-
assert case[1] == self.xblock.get_context()['comment']
59+
assert case[1] == self.xblock.get_context()["comment"]
5260

5361
def test_lti20_rest_bad_contenttype(self):
5462
"""
5563
Input with bad content type
5664
"""
5765
with self.assertRaisesRegex(LTIError, "Content-Type must be"):
58-
request = Mock(headers={'Content-Type': 'Non-existent'})
66+
request = Mock(headers={"Content-Type": "Non-existent"})
5967
self.xblock.verify_lti_2_0_result_rest_headers(request)
6068

6169
def test_lti20_rest_failed_oauth_body_verify(self):
@@ -65,7 +73,9 @@ def test_lti20_rest_failed_oauth_body_verify(self):
6573
err_msg = "OAuth body verification failed"
6674
self.xblock.verify_oauth_body_sign = Mock(side_effect=LTIError(err_msg))
6775
with self.assertRaisesRegex(LTIError, err_msg):
68-
request = Mock(headers={'Content-Type': 'application/vnd.ims.lis.v2.result+json'})
76+
request = Mock(
77+
headers={"Content-Type": "application/vnd.ims.lis.v2.result+json"}
78+
)
6979
self.xblock.verify_lti_2_0_result_rest_headers(request)
7080

7181
def test_lti20_rest_good_headers(self):
@@ -74,7 +84,9 @@ def test_lti20_rest_good_headers(self):
7484
"""
7585
self.xblock.verify_oauth_body_sign = Mock(return_value=True)
7686

77-
request = Mock(headers={'Content-Type': 'application/vnd.ims.lis.v2.result+json'})
87+
request = Mock(
88+
headers={"Content-Type": "application/vnd.ims.lis.v2.result+json"}
89+
)
7890
self.xblock.verify_lti_2_0_result_rest_headers(request)
7991
# We just want the above call to complete without exceptions, and to have called verify_oauth_body_sign
8092
assert self.xblock.verify_oauth_body_sign.called
@@ -88,7 +100,7 @@ def test_lti20_rest_good_headers(self):
88100
"user//"
89101
"user/gbere/"
90102
"user/gbere/xsdf"
91-
"user/ಠ益ಠ" # not alphanumeric
103+
"user/ಠ益ಠ", # not alphanumeric
92104
]
93105

94106
def test_lti20_rest_bad_dispatch(self):
@@ -202,7 +214,7 @@ def test_lti20_good_json(self):
202214
"comment": "ಠ益ಠ"}
203215
""").encode('utf-8')
204216

205-
def get_signed_lti20_mock_request(self, body, method='PUT'):
217+
def get_signed_lti20_mock_request(self, body, method="PUT"):
206218
"""
207219
Example of signed from LTI 2.0 Provider. Signatures and hashes are example only and won't verify
208220
"""
@@ -216,9 +228,9 @@ def get_signed_lti20_mock_request(self, body, method='PUT'):
216228
'oauth_consumer_key="test_client_key", '
217229
'oauth_signature="my_signature%3D", '
218230
'oauth_body_hash="gz+PeJZuF2//n9hNUnDj2v5kN70="'
219-
)
231+
),
220232
}
221-
mock_request.url = 'http://testurl'
233+
mock_request.url = "http://testurl"
222234
mock_request.http_method = method
223235
mock_request.method = method
224236
mock_request.body = body
@@ -229,7 +241,9 @@ def setup_system_xblock_mocks_for_lti20_request_test(self):
229241
Helper fn to set up mocking for lti 2.0 request test
230242
"""
231243
self.xblock.max_score = Mock(return_value=1.0)
232-
self.xblock.get_client_key_secret = Mock(return_value=('test_client_key', 'test_client_secret'))
244+
self.xblock.get_client_key_secret = Mock(
245+
return_value=("test_client_key", "test_client_secret")
246+
)
233247
self.xblock.verify_oauth_body_sign = Mock()
234248

235249
def test_lti20_put_like_delete_success(self):
@@ -241,17 +255,25 @@ def test_lti20_put_like_delete_success(self):
241255
COMMENT = "ಠ益ಠ" # pylint: disable=invalid-name
242256
self.xblock.module_score = SCORE
243257
self.xblock.score_comment = COMMENT
244-
mock_request = self.get_signed_lti20_mock_request(self.GOOD_JSON_PUT_LIKE_DELETE)
258+
mock_request = self.get_signed_lti20_mock_request(
259+
self.GOOD_JSON_PUT_LIKE_DELETE
260+
)
245261
# Now call the handler
246262
response = self.xblock.lti_2_0_result_rest_handler(mock_request, "user/abcd")
247263
# Now assert there's no score
248264
assert response.status_code == 200
249265
assert self.xblock.module_score is None
250-
assert self.xblock.score_comment == ''
251-
(_, evt_type, called_grade_obj), _ = self.runtime.publish.call_args # pylint: disable=unpacking-non-sequence
252-
assert called_grade_obj ==\
253-
{'user_id': self.USER_STANDIN.id, 'value': None, 'max_value': None, 'score_deleted': True}
254-
assert evt_type == 'grade'
266+
assert self.xblock.score_comment == ""
267+
(_, evt_type, called_grade_obj), _ = (
268+
self.runtime.publish.call_args
269+
) # pylint: disable=unpacking-non-sequence
270+
assert called_grade_obj == {
271+
"user_id": self.USER_STANDIN.id,
272+
"value": None,
273+
"max_value": None,
274+
"score_deleted": True,
275+
}
276+
assert evt_type == "grade"
255277

256278
def test_lti20_delete_success(self):
257279
"""
@@ -262,17 +284,23 @@ def test_lti20_delete_success(self):
262284
COMMENT = "ಠ益ಠ" # pylint: disable=invalid-name
263285
self.xblock.module_score = SCORE
264286
self.xblock.score_comment = COMMENT
265-
mock_request = self.get_signed_lti20_mock_request(b"", method='DELETE')
287+
mock_request = self.get_signed_lti20_mock_request(b"", method="DELETE")
266288
# Now call the handler
267289
response = self.xblock.lti_2_0_result_rest_handler(mock_request, "user/abcd")
268290
# Now assert there's no score
269291
assert response.status_code == 200
270292
assert self.xblock.module_score is None
271-
assert self.xblock.score_comment == ''
272-
(_, evt_type, called_grade_obj), _ = self.runtime.publish.call_args # pylint: disable=unpacking-non-sequence
273-
assert called_grade_obj ==\
274-
{'user_id': self.USER_STANDIN.id, 'value': None, 'max_value': None, 'score_deleted': True}
275-
assert evt_type == 'grade'
293+
assert self.xblock.score_comment == ""
294+
(_, evt_type, called_grade_obj), _ = (
295+
self.runtime.publish.call_args
296+
) # pylint: disable=unpacking-non-sequence
297+
assert called_grade_obj == {
298+
"user_id": self.USER_STANDIN.id,
299+
"value": None,
300+
"max_value": None,
301+
"score_deleted": True,
302+
}
303+
assert evt_type == "grade"
276304

277305
def test_lti20_put_set_score_success(self):
278306
"""
@@ -285,23 +313,32 @@ def test_lti20_put_set_score_success(self):
285313
# Now assert
286314
assert response.status_code == 200
287315
assert self.xblock.module_score == 0.1
288-
assert self.xblock.score_comment == 'ಠ益ಠ'
289-
(_, evt_type, called_grade_obj), _ = self.runtime.publish.call_args # pylint: disable=unpacking-non-sequence
290-
assert evt_type == 'grade'
291-
assert called_grade_obj ==\
292-
{'user_id': self.USER_STANDIN.id, 'value': 0.1, 'max_value': 1.0, 'score_deleted': False}
316+
assert self.xblock.score_comment == "ಠ益ಠ"
317+
(_, evt_type, called_grade_obj), _ = (
318+
self.runtime.publish.call_args
319+
) # pylint: disable=unpacking-non-sequence
320+
assert evt_type == "grade"
321+
assert called_grade_obj == {
322+
"user_id": self.USER_STANDIN.id,
323+
"value": 0.1,
324+
"max_value": 1.0,
325+
"score_deleted": False,
326+
}
293327

294328
def test_lti20_get_no_score_success(self):
295329
"""
296330
The happy path for LTI 2.0 GET when there's no score
297331
"""
298332
self.setup_system_xblock_mocks_for_lti20_request_test()
299-
mock_request = self.get_signed_lti20_mock_request(b"", method='GET')
333+
mock_request = self.get_signed_lti20_mock_request(b"", method="GET")
300334
# Now call the handler
301335
response = self.xblock.lti_2_0_result_rest_handler(mock_request, "user/abcd")
302336
# Now assert
303337
assert response.status_code == 200
304-
assert response.json == {'@context': 'http://purl.imsglobal.org/ctx/lis/v2/Result', '@type': 'Result'}
338+
assert response.json == {
339+
"@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
340+
"@type": "Result",
341+
}
305342

306343
def test_lti20_get_with_score_success(self):
307344
"""
@@ -312,14 +349,17 @@ def test_lti20_get_with_score_success(self):
312349
COMMENT = "ಠ益ಠ" # pylint: disable=invalid-name
313350
self.xblock.module_score = SCORE
314351
self.xblock.score_comment = COMMENT
315-
mock_request = self.get_signed_lti20_mock_request(b"", method='GET')
352+
mock_request = self.get_signed_lti20_mock_request(b"", method="GET")
316353
# Now call the handler
317354
response = self.xblock.lti_2_0_result_rest_handler(mock_request, "user/abcd")
318355
# Now assert
319356
assert response.status_code == 200
320-
assert response.json ==\
321-
{'@context': 'http://purl.imsglobal.org/ctx/lis/v2/Result',
322-
'@type': 'Result', 'resultScore': SCORE, 'comment': COMMENT}
357+
assert response.json == {
358+
"@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
359+
"@type": "Result",
360+
"resultScore": SCORE,
361+
"comment": COMMENT,
362+
}
323363

324364
UNSUPPORTED_HTTP_METHODS = ["OPTIONS", "HEAD", "POST", "TRACE", "CONNECT"]
325365

@@ -331,7 +371,9 @@ def test_lti20_unsupported_method_error(self):
331371
mock_request = self.get_signed_lti20_mock_request(self.GOOD_JSON_PUT)
332372
for bad_method in self.UNSUPPORTED_HTTP_METHODS:
333373
mock_request.method = bad_method
334-
response = self.xblock.lti_2_0_result_rest_handler(mock_request, "user/abcd")
374+
response = self.xblock.lti_2_0_result_rest_handler(
375+
mock_request, "user/abcd"
376+
)
335377
assert response.status_code == 404
336378

337379
def test_lti20_request_handler_bad_headers(self):
@@ -368,7 +410,9 @@ def test_lti20_request_handler_bad_user(self):
368410
Test that we get a 404 when the supplied user does not exist
369411
"""
370412
self.setup_system_xblock_mocks_for_lti20_request_test()
371-
self.runtime._services['user'] = StubUserService(user=None) # pylint: disable=protected-access
413+
self.runtime._services["user"] = StubUserService(
414+
user=None
415+
) # pylint: disable=protected-access
372416
mock_request = self.get_signed_lti20_mock_request(self.GOOD_JSON_PUT)
373417
response = self.xblock.lti_2_0_result_rest_handler(mock_request, "user/abcd")
374418
assert response.status_code == 404

0 commit comments

Comments
 (0)