Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
b5dc03ec
Commit
b5dc03ec
authored
Aug 29, 2013
by
Alexander Kryklia
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Acceptance test for LTI module (not finished), but working
parent
e4bad0a6
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
379 additions
and
0 deletions
+379
-0
common/lib/xmodule/xmodule/lti_module.py
+1
-0
lms/djangoapps/courseware/features/lti.feature
+10
-0
lms/djangoapps/courseware/features/lti.py
+98
-0
lms/djangoapps/courseware/features/lti_setup.py
+40
-0
lms/djangoapps/courseware/mock_lti_server/__init__.py
+0
-0
lms/djangoapps/courseware/mock_lti_server/mock_lti_server.py
+158
-0
lms/djangoapps/courseware/mock_lti_server/test_mock_lti_server.py
+72
-0
No files found.
common/lib/xmodule/xmodule/lti_module.py
View file @
b5dc03ec
...
...
@@ -83,6 +83,7 @@ class LTIModule(LTIFields, XModule):
# with 'Content-Type': 'application/x-www-form-urlencoded'
# so '='' becomes '%3D', but server waits for unencoded signature.
# Decode signature back:
# may be it may be encoded by browser again... check
params
[
u'oauth_signature'
]
=
urllib
.
unquote
(
params
[
u'oauth_signature'
])
.
decode
(
'utf8'
)
return
params
...
...
lms/djangoapps/courseware/features/lti.feature
0 → 100644
View file @
b5dc03ec
Feature
:
LTI component
As a student, I want to view LTI component in LMS.
Scenario
:
LTI component in LMS is not rendered
Given
the course has a LTI component with empty fields
Then
I view the LTI and it is not rendered
Scenario
:
LTI component in LMS is rendered
Given
the course has a LTI component filled with correct data
Then
I view the LTI and it is rendered
lms/djangoapps/courseware/features/lti.py
0 → 100644
View file @
b5dc03ec
#pylint: disable=C0111
from
lettuce
import
world
,
step
from
lettuce.django
import
django_url
from
common
import
i_am_registered_for_the_course
,
section_location
@step
(
'I view the LTI and it is not rendered'
)
def
lti_is_not_rendered
(
_step
):
# lti div has no class rendered
assert
world
.
is_css_not_present
(
'div.lti.rendered'
)
# error is shown
assert
world
.
css_visible
(
'.error_message'
)
# iframe is not visible
assert
(
not
world
.
css_visible
(
'iframe'
))
#inside iframe test content is not presented
with
world
.
browser
.
get_iframe
(
'ltiLaunchFrame'
)
as
iframe
:
# iframe does not contain functions from terrain/ui_helpers.py
assert
iframe
.
is_element_not_present_by_css
(
'.result'
,
wait_time
=
5
)
@step
(
'I view the LTI and it is rendered'
)
def
lti_is_rendered
(
_step
):
# lti div has class rendered
assert
world
.
is_css_present
(
'div.lti.rendered'
)
# error is hidden
assert
(
not
world
.
css_visible
(
'.error_message'
))
# iframe is visible
assert
world
.
css_visible
(
'iframe'
)
#inside iframe test content is presented
with
world
.
browser
.
get_iframe
(
'ltiLaunchFrame'
)
as
iframe
:
# iframe does not contain functions from terrain/ui_helpers.py
assert
iframe
.
is_element_present_by_css
(
'.result'
,
wait_time
=
5
)
@step
(
'the course has a LTI component filled with correct data'
)
def
view_lti_with_data
(
_step
):
coursenum
=
'test_course'
i_am_registered_for_the_course
(
_step
,
coursenum
)
add_correct_lti_to_course
(
coursenum
)
chapter_name
=
world
.
scenario_dict
[
'SECTION'
]
.
display_name
.
replace
(
" "
,
"_"
)
section_name
=
chapter_name
url
=
django_url
(
'/courses/
%
s/
%
s/
%
s/courseware/
%
s/
%
s'
%
(
world
.
scenario_dict
[
'COURSE'
]
.
org
,
world
.
scenario_dict
[
'COURSE'
]
.
number
,
world
.
scenario_dict
[
'COURSE'
]
.
display_name
.
replace
(
' '
,
'_'
),
chapter_name
,
section_name
,)
)
world
.
browser
.
visit
(
url
)
@step
(
'the course has a LTI component with empty fields'
)
def
view_default_lti
(
_step
):
coursenum
=
'test_course'
i_am_registered_for_the_course
(
_step
,
coursenum
)
add_default_lti_to_course
(
coursenum
)
chapter_name
=
world
.
scenario_dict
[
'SECTION'
]
.
display_name
.
replace
(
" "
,
"_"
)
section_name
=
chapter_name
url
=
django_url
(
'/courses/
%
s/
%
s/
%
s/courseware/
%
s/
%
s'
%
(
world
.
scenario_dict
[
'COURSE'
]
.
org
,
world
.
scenario_dict
[
'COURSE'
]
.
number
,
world
.
scenario_dict
[
'COURSE'
]
.
display_name
.
replace
(
' '
,
'_'
),
chapter_name
,
section_name
,)
)
world
.
browser
.
visit
(
url
)
def
add_correct_lti_to_course
(
course
):
category
=
'lti'
world
.
ItemFactory
.
create
(
parent_location
=
section_location
(
course
),
category
=
category
,
display_name
=
'LTI'
,
metadata
=
{
'client_key'
:
'client_key'
,
'clent_secret'
:
'client_secret'
,
'lti_url'
:
'http://127.0.0.1:{}/correct_lti_endpoint'
.
format
(
world
.
lti_server_port
)
}
)
def
add_default_lti_to_course
(
course
):
category
=
'lti'
world
.
ItemFactory
.
create
(
parent_location
=
section_location
(
course
),
category
=
category
,
display_name
=
'LTI'
)
lms/djangoapps/courseware/features/lti_setup.py
0 → 100644
View file @
b5dc03ec
#pylint: disable=C0111
#pylint: disable=W0621
from
courseware.mock_lti_server.mock_lti_server
import
MockLTIServer
from
lettuce
import
before
,
after
,
world
from
django.conf
import
settings
import
threading
from
logging
import
getLogger
logger
=
getLogger
(
__name__
)
@before.all
def
setup_mock_lti_server
():
# Add +1 to XQUEUE random port number
server_port
=
settings
.
XQUEUE_PORT
+
1
# Create the mock server instance
server
=
MockLTIServer
(
server_port
)
logger
.
debug
(
"LTI server started at {} port"
.
format
(
str
(
server_port
)))
# Start the server running in a separate daemon thread
# Because the thread is a daemon, it will terminate
# when the main thread terminates.
server_thread
=
threading
.
Thread
(
target
=
server
.
serve_forever
)
server_thread
.
daemon
=
True
server_thread
.
start
()
# Store the server instance in lettuce's world
# so that other steps can access it
# (and we can shut it down later)
world
.
lti_server
=
server
world
.
lti_server_port
=
server_port
@after.all
def
teardown_mock_lti_server
(
total
):
# Stop the LTI server and free up the port
world
.
lti_server
.
shutdown
()
lms/djangoapps/courseware/mock_lti_server/__init__.py
0 → 100644
View file @
b5dc03ec
lms/djangoapps/courseware/mock_lti_server/mock_lti_server.py
0 → 100644
View file @
b5dc03ec
from
BaseHTTPServer
import
HTTPServer
,
BaseHTTPRequestHandler
import
json
import
urllib
import
urlparse
import
threading
from
logging
import
getLogger
logger
=
getLogger
(
__name__
)
# todo - implement oauth
class
MockLTIRequestHandler
(
BaseHTTPRequestHandler
):
'''
A handler for LTI POST requests.
'''
protocol
=
"HTTP/1.0"
def
do_HEAD
(
self
):
self
.
_send_head
()
def
do_POST
(
self
):
'''
Handle a POST request from the client and sends response back.
'''
self
.
_send_head
()
post_dict
=
self
.
_post_dict
()
# Retrieve the POST data
# Log the request
logger
.
debug
(
"LTI provider received POST request {} to path {}"
.
format
(
str
(
post_dict
),
self
.
path
)
)
# Respond only to requests with correct lti endpoint:
if
self
.
_is_correct_lti_request
():
correct_dict
=
{
'user_id'
:
'default_user_id'
,
'oauth_nonce'
:
'22990037033121997701377766132'
,
'oauth_timestamp'
:
'1377766132'
,
'oauth_consumer_key'
:
'client_key'
,
'lti_version'
:
'LTI-1p0'
,
'oauth_signature_method'
:
'HMAC-SHA1'
,
'oauth_version'
:
'1.0'
,
'oauth_signature'
:
'HGYMAU/G5EMxd0CDOvWubsqxLIY='
,
'lti_message_type'
:
'basic-lti-launch-request'
,
'oauth_callback'
:
'about:blank'
}
if
sorted
(
correct_dict
.
keys
())
!=
sorted
(
post_dict
.
keys
()):
error_message
=
"Incorrect LTI header"
else
:
error_message
=
"This is LTI tool."
else
:
error_message
=
"Invalid request URL"
self
.
_send_response
(
error_message
)
def
_send_head
(
self
):
'''
Send the response code and MIME headers
'''
if
self
.
_is_correct_lti_request
():
self
.
send_response
(
200
)
else
:
self
.
send_response
(
500
)
self
.
send_header
(
'Content-type'
,
'text/html'
)
self
.
end_headers
()
def
_post_dict
(
self
):
'''
Retrieve the POST parameters from the client as a dictionary
'''
try
:
length
=
int
(
self
.
headers
.
getheader
(
'content-length'
))
post_dict
=
urlparse
.
parse_qs
(
self
.
rfile
.
read
(
length
))
# The POST dict will contain a list of values for each key.
# None of our parameters are lists, however, so we map [val] --> val.
#I f the list contains multiple entries, we pick the first one
post_dict
=
dict
(
map
(
lambda
(
key
,
list_val
):
(
key
,
list_val
[
0
]),
post_dict
.
items
()
)
)
except
:
# We return an empty dict here, on the assumption
# that when we later check that the request has
# the correct fields, it won't find them,
# and will therefore send an error response
return
{}
return
post_dict
def
_send_response
(
self
,
message
):
'''
Send message back to the client
'''
response_str
=
"""<html><head><title>TEST TITLE</title></head>
<body>
<div><h2>IFrame loaded</h2>
\
<h3>Server response is:</h3>
\
<h3 class="result">{}</h3></div>
</body></html>"""
.
format
(
message
)
# Log the response
logger
.
debug
(
"LTI: sent response {}"
.
format
(
response_str
))
self
.
wfile
.
write
(
response_str
)
def
_is_correct_lti_request
(
self
):
'''If url to get LTI is correct.'''
return
'correct_lti_endpoint'
in
self
.
path
class
MockLTIServer
(
HTTPServer
):
'''
A mock LTI provider server that responds
to POST requests to localhost.
'''
def
__init__
(
self
,
port_num
,
oauth
=
{}):
'''
Initialize the mock XQueue server instance.
*port_num* is the localhost port to listen to
*grade_response_dict* is a dictionary that will be JSON-serialized
and sent in response to XQueue grading requests.
'''
self
.
clent_key
=
oauth
.
get
(
'client_key'
,
''
)
self
.
clent_secret
=
oauth
.
get
(
'client_secret'
,
''
)
self
.
check_oauth
()
handler
=
MockLTIRequestHandler
address
=
(
''
,
port_num
)
HTTPServer
.
__init__
(
self
,
address
,
handler
)
def
shutdown
(
self
):
'''
Stop the server and free up the port
'''
# First call superclass shutdown()
HTTPServer
.
shutdown
(
self
)
# We also need to manually close the socket
self
.
socket
.
close
()
def
get_oauth_signature
(
self
):
'''test'''
return
self
.
_signature
def
check_oauth
(
self
):
''' generate oauth signature '''
self
.
_signature
=
'12345'
lms/djangoapps/courseware/mock_lti_server/test_mock_lti_server.py
0 → 100644
View file @
b5dc03ec
import
mock
import
unittest
import
threading
import
json
import
urllib
import
time
from
mock_lti_server
import
MockLTIServer
,
MockLTIRequestHandler
from
nose.plugins.skip
import
SkipTest
class
MockLTIServerTest
(
unittest
.
TestCase
):
'''
A mock version of the LTI provider server that listens on a local
port and responds with pre-defined grade messages.
Used for lettuce BDD tests in lms/courseware/features/lti.feature
'''
def
setUp
(
self
):
# This is a test of the test setup,
# so it does not need to run as part of the unit test suite
# You can re-enable it by commenting out the line below
# raise SkipTest
# Create the server
server_port
=
8034
self
.
server_url
=
'http://127.0.0.1:
%
d'
%
server_port
self
.
server
=
MockLTIServer
(
server_port
,
{
'client_key'
:
''
,
'client_secret'
:
''
})
# Start the server in a separate daemon thread
server_thread
=
threading
.
Thread
(
target
=
self
.
server
.
serve_forever
)
server_thread
.
daemon
=
True
server_thread
.
start
()
def
tearDown
(
self
):
# Stop the server, freeing up the port
self
.
server
.
shutdown
()
def
test_oauth_request
(
self
):
# Send a grade request
header
=
{
'Content-Type'
:
'application/x-www-form-urlencoded'
,
u'Authorization'
:
u'OAuth oauth_nonce="151177408427657509491377691584",
\
oauth_timestamp="1377691584", oauth_version="1.0",
\
oauth_signature_method="HMAC-SHA1", oauth_consumer_key="",
\
oauth_signature="wc1unKXxsX5e4HXJu
%2
FuiQ1KbrVo
%3
D"'
,
'launch_presentation_return_url'
:
''
,
'user_id'
:
'default_user_id'
,
'lis_result_sourcedid'
:
''
,
'lti_version'
:
'LTI-1p0'
,
'lis_outcome_service_url'
:
''
,
'lti_message_type'
:
'basic-lti-launch-request'
,
'oauth_callback'
:
'about:blank'
}
body
=
{}
request
=
{
'header'
:
json
.
dumps
(
header
),
'body'
:
json
.
dumps
(
body
)}
response_handle
=
urllib
.
urlopen
(
self
.
server_url
+
'/correct_lti_endpoint'
,
urllib
.
urlencode
(
request
)
)
response_dict
=
json
.
loads
(
response_handle
.
read
())
# Expect that the response is success
self
.
assertEqual
(
response_dict
[
'return_code'
],
0
)
# self.assertEqual(response_dict['return_code'], 0)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment