11"""Tests for LTI Xmodule LTIv2.0 functional logic."""
22
3-
43import datetime
54import textwrap
6- import unittest
75from unittest .mock import Mock
86
97from pytz import UTC
8+ from django .conf import settings
9+ from django .test import TestCase , override_settings
1010from xblock .field_data import DictFieldData
1111
1212from xblocks_contrib .lti .lti_2_util import LTIError
1313from xblocks_contrib .lti .lti import LTIBlock
1414from .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