diff --git a/common/lib/xmodule/xmodule/js/spec/lti/lti_spec.js b/common/lib/xmodule/xmodule/js/spec/lti/lti_spec.js deleted file mode 100644 index 9a9369eac1..0000000000 --- a/common/lib/xmodule/xmodule/js/spec/lti/lti_spec.js +++ /dev/null @@ -1,370 +0,0 @@ -/** - * File: constructor.js - * - * Purpose: Jasmine tests for LTI module (front-end part). - * - * - * Because LTI module is constructed so that all methods are available via the - * prototype chain, many times we can test methods without having to - * instantiate a new LTI object. - */ - -/* - * "Hence that general is skillful in attack whose opponent does not know what - * to defend; and he is skillful in defense whose opponent does not know what - * to attack." - * - * ~ Sun Tzu - */ - -(function () { - var IN_NEW_WINDOW = 'true', - IN_IFRAME = 'false', - EMPTY_URL = '', - DEFAULT_URL = 'http://www.example.com', - NEW_URL = 'http://www.example.com/some_book'; - - describe('LTI XModule', function () { - describe('LTIConstructor method', function () { - describe('[in iframe, new url]', function () { - var lti; - - beforeEach(function () { - loadFixtures('lti.html'); - setUpLtiElement($('.lti-wrapper'), IN_IFRAME, NEW_URL); - - spyOnEvent( - $('.lti-wrapper').find('.ltiLaunchForm'), 'submit' - ); - - lti = new window.LTI('.lti-wrapper'); - }); - - it('new LTI object contains all properties', function () { - expect(lti.el).toBeDefined(); - expect(lti.el).toExist(); - - expect(lti.formEl).toBeDefined(); - expect(lti.formEl).toExist(); - expect(lti.formEl).toHaveAttr('action'); - - expect(lti.ltiEl).toBeDefined(); - expect(lti.ltiEl).toExist(); - - expect(lti.formAction).toEqual(NEW_URL); - expect(lti.openInANewPage).toEqual(false); - expect(lti.ajaxUrl).toEqual(jasmine.any(String)); - - expect('submit').toHaveBeenTriggeredOn(lti.formEl); - }); - - afterEach(function () { - lti = undefined; - }); - }); - - describe('[in new window, new url]', function () { - var lti; - - beforeEach(function () { - loadFixtures('lti.html'); - setUpLtiElement($('.lti-wrapper'), IN_NEW_WINDOW, NEW_URL); - - lti = new window.LTI('.lti-wrapper'); - }); - - it('check extra properties and values', function () { - expect(lti.openInANewPage).toEqual(true); - expect(lti.signatureIsNew).toBeTruthy(); - - expect(lti.newWindowBtnEl).toBeDefined(); - expect(lti.newWindowBtnEl).toExist(); - - expect(lti.disableOpenNewWindowBtn).toBe(false); - }); - - afterEach(function () { - lti = undefined; - }); - }); - - describe('[in iframe, NO new url]', function () { - var testCases = [{ - itDescription: 'URL is blank', - action: EMPTY_URL - }, { - itDescription: 'URL is default', - action: DEFAULT_URL - }]; - - $.each(testCases, function (index, test) { - it(test.itDescription, function () { - var lti; - - loadFixtures('lti.html'); - setUpLtiElement( - $('.lti-wrapper'), IN_IFRAME, test.action - ); - - lti = new window.LTI('.lti-wrapper'); - - expect(lti.openInANewPage).not.toBeDefined(); - }); - }); - }); - }); - - describe('submitFormHandler method', function () { - var thisObj; - - beforeEach(function () { - thisObj = { - signatureIsNew: undefined, - getNewSignature: jasmine.createSpy('getNewSignature'), - formEl: { - submit: jasmine.createSpy('submit') - } - }; - }); - - it('signature is new', function () { - thisObj.signatureIsNew = true; - - window.LTI.prototype.submitFormHandler.call(thisObj); - - expect(thisObj.formEl.submit).toHaveBeenCalled(); - expect(thisObj.signatureIsNew).toBe(false); - }); - - it('signature is old', function () { - thisObj.signatureIsNew = false; - - window.LTI.prototype.submitFormHandler.call(thisObj); - - expect(thisObj.formEl.submit).not.toHaveBeenCalled(); - expect(thisObj.signatureIsNew).toBe(false); - expect(thisObj.getNewSignature).toHaveBeenCalled(); - }); - - afterEach(function () { - thisObj = undefined; - }); - }); - - describe('getNewSignature method', function () { - var lti; - - beforeEach(function () { - loadFixtures('lti.html'); - setUpLtiElement($('.lti-wrapper'), IN_NEW_WINDOW, NEW_URL); - - spyOn($, 'postWithPrefix').andCallFake( - function (url, data, callback) { - callback({ - input_fields: {} - }); - } - ); - - lti = new window.LTI('.lti-wrapper'); - - spyOn(lti, 'submitFormHandler').andCallThrough(); - lti.submitFormHandler.reset(); - - spyOn(lti, 'handleAjaxUpdateSignature'); - }); - - it( - '"Open in new page" clicked twice, signature requested once', - function () { - lti.newWindowBtnEl.click(); - lti.newWindowBtnEl.click(); - - expect(lti.submitFormHandler).toHaveBeenCalled(); - expect(lti.submitFormHandler.callCount).toBe(2); - - expect($.postWithPrefix).toHaveBeenCalledWith( - lti.ajaxUrl + '/regenerate_signature', - {}, - jasmine.any(Function) - ); - - expect(lti.disableOpenNewWindowBtn).toBe(true); - - expect(lti.handleAjaxUpdateSignature) - .toHaveBeenCalledWith({ - input_fields: {} - }); - } - ); - - afterEach(function () { - lti = undefined; - }); - }); - - describe('handleAjaxUpdateSignature method', function () { - var lti, oldInputFields, newInputFields, - AjaxCallbackData = {}; - - function fakePostWithPrefix(url, data, callback) { - return callback(AjaxCallbackData); - } - - beforeEach(function () { - oldInputFields = { - oauth_nonce: '28347958723982798572', - oauth_timestamp: '2389479832', - oauth_signature: '89ru3289r3ry283y3r82ryr38yr' - }; - - newInputFields = { - oauth_nonce: 'ru3902ru239ru', - oauth_timestamp: '24ru309rur39r8u', - oauth_signature: '08923ru3082u2rur' - }; - - AjaxCallbackData.error = 0; - AjaxCallbackData.input_fields = newInputFields; - - loadFixtures('lti.html'); - setUpLtiElement($('.lti-wrapper'), IN_NEW_WINDOW, NEW_URL); - - spyOn($, 'postWithPrefix').andCallFake(fakePostWithPrefix); - - lti = new window.LTI('.lti-wrapper'); - - spyOn(lti, 'submitFormHandler').andCallThrough(); - spyOn(lti, 'handleAjaxUpdateSignature').andCallThrough(); - spyOn(lti.formEl, 'submit'); - spyOn(window.console, 'log').andCallThrough(); - - lti.submitFormHandler.reset(); - lti.handleAjaxUpdateSignature.reset(); - lti.formEl.submit.reset(); - window.console.log.reset(); - }); - - it('On second click form is updated, and submitted', function () { - // Setup initial OAuth values in the form. - lti.formEl.find("input[name='oauth_nonce']") - .val(oldInputFields.oauth_nonce); - lti.formEl.find("input[name='oauth_timestamp']") - .val(oldInputFields.oauth_timestamp); - lti.formEl.find("input[name='oauth_signature']") - .val(oldInputFields.oauth_signature); - - // First click. Signature is new. Should just submit the form. - lti.newWindowBtnEl.click(); - - // Initial OAuth values should not have changed. - expect(lti.formEl.find("input[name='oauth_nonce']").val()) - .toBe(oldInputFields.oauth_nonce); - expect(lti.formEl.find("input[name='oauth_timestamp']").val()) - .toBe(oldInputFields.oauth_timestamp); - expect(lti.formEl.find("input[name='oauth_signature']").val()) - .toBe(oldInputFields.oauth_signature); - - expect(lti.submitFormHandler).toHaveBeenCalled(); - expect(lti.submitFormHandler.callCount).toBe(1); - - expect(lti.handleAjaxUpdateSignature).not.toHaveBeenCalled(); - expect(lti.handleAjaxUpdateSignature.callCount).toBe(0); - - expect(lti.formEl.submit).toHaveBeenCalled(); - expect(lti.formEl.submit.callCount).toBe(1); - - lti.submitFormHandler.reset(); - lti.handleAjaxUpdateSignature.reset(); - lti.formEl.submit.reset(); - - // Second click. Signature is old. Should request for a new - // signature, and then submit the form. - lti.newWindowBtnEl.click(); - - expect(lti.submitFormHandler).toHaveBeenCalled(); - expect(lti.submitFormHandler.callCount).toBe(2); - - expect(lti.handleAjaxUpdateSignature).toHaveBeenCalled(); - expect(lti.handleAjaxUpdateSignature.callCount).toBe(1); - - expect(lti.formEl.submit).toHaveBeenCalled(); - expect(lti.formEl.submit.callCount).toBe(1); - - expect(lti.disableOpenNewWindowBtn).toBe(false); - - // The new OAuth values should be in the form. - expect(lti.formEl.find("input[name='oauth_nonce']").val()) - .toBe(newInputFields.oauth_nonce); - expect(lti.formEl.find("input[name='oauth_timestamp']").val()) - .toBe(newInputFields.oauth_timestamp); - expect(lti.formEl.find("input[name='oauth_signature']").val()) - .toBe(newInputFields.oauth_signature); - }); - - it('invalid response for new OAuth signature', function () { - AjaxCallbackData.input_fields = 0; - AjaxCallbackData.error = 'error'; - - lti.newWindowBtnEl.click(); - - lti.submitFormHandler.reset(); - lti.handleAjaxUpdateSignature.reset(); - window.console.log.reset(); - lti.formEl.submit.reset(); - - lti.newWindowBtnEl.click(); - - expect(lti.submitFormHandler).toHaveBeenCalled(); - expect(lti.submitFormHandler.callCount).toBe(1); - - expect(lti.handleAjaxUpdateSignature).toHaveBeenCalled(); - expect(lti.handleAjaxUpdateSignature.callCount).toBe(1); - - expect(window.console.log).toHaveBeenCalledWith( - jasmine.any(String) - ); - - expect(lti.formEl.submit).not.toHaveBeenCalled(); - }); - - afterEach(function () { - lti = undefined; - oldInputFields = undefined; - newInputFields = undefined; - }); - }); - }); - - function setUpLtiElement(element, target, action) { - var container, form; - - container = element.find('.lti'); - form = container.find('.ltiLaunchForm'); - - if (target === IN_IFRAME) { - container.data('open_in_a_new_page', 'false'); - form.attr('target', 'ltiLaunchFrame'); - } - - form.attr('action', action); - - // If we have a new proper action (non-default), we create either - // a link that will submit the form, or an iframe that will contain - // the answer of auto submitted form. - if (action !== EMPTY_URL && action !== DEFAULT_URL) { - if (target === IN_NEW_WINDOW) { - $('', { - href: '#', - class: 'link_lti_new_window' - }).appendTo(container); - } else { - $('', { - name: 'ltiLaunchFrame', - class: 'ltiLaunchFrame', - src: '' - }).appendTo(container); - } - } - } -}()); diff --git a/common/lib/xmodule/xmodule/lti_module.py b/common/lib/xmodule/xmodule/lti_module.py index 8cee85ca36..519a681230 100644 --- a/common/lib/xmodule/xmodule/lti_module.py +++ b/common/lib/xmodule/xmodule/lti_module.py @@ -292,21 +292,6 @@ class LTIModule(LTIFields, XModule): """ return Response(self.get_form(), content_type='text/html') - def handle_ajax(self, dispatch, __): - """ - Ajax handler. - - Args: - dispatch: string request slug - - Returns: - json string - """ - if dispatch == 'regenerate_signature': - return json.dumps({ 'input_fields': self.get_input_fields() }) - else: # return error message - return json.dumps({ 'error': '[handle_ajax]: Unknown Command!' }) - def get_user_id(self): user_id = self.runtime.anonymous_student_id assert user_id is not None diff --git a/common/lib/xmodule/xmodule/tests/test_lti_unit.py b/common/lib/xmodule/xmodule/tests/test_lti_unit.py index 3dd6c011fd..4caa9c732a 100644 --- a/common/lib/xmodule/xmodule/tests/test_lti_unit.py +++ b/common/lib/xmodule/xmodule/tests/test_lti_unit.py @@ -229,6 +229,12 @@ class LTIModuleTest(LogicTest): real_outcome_service_url = self.xmodule.get_outcome_service_url() self.assertEqual(real_outcome_service_url, expected_outcome_service_url) + def test_get_form_path(self): + expected_form_path = self.xmodule.runtime.handler_url(self.xmodule, 'preview_handler').rstrip('/?') + + real_form_path = self.xmodule.get_form_path() + self.assertEqual(real_form_path, expected_form_path) + def test_resource_link_id(self): with patch('xmodule.lti_module.LTIModule.id', new_callable=PropertyMock) as mock_id: mock_id.return_value = self.module_id @@ -251,28 +257,6 @@ class LTIModuleTest(LogicTest): def test_client_key_secret(self): pass - def test_handle_ajax(self): - dispatch = 'regenerate_signature' - data = '' - self.xmodule.get_input_fields = Mock(return_value={'test_input_field_key': 'test_input_field_value'}) - json_dump = self.xmodule.handle_ajax(dispatch, data) - expected_json_dump = '{"input_fields": {"test_input_field_key": "test_input_field_value"}}' - self.assertEqual( - json.loads(json_dump), - json.loads(expected_json_dump) - ) - - def test_handle_ajax_bad_dispatch(self): - dispatch = 'bad_dispatch' - data = '' - self.xmodule.get_input_fields = Mock(return_value={'test_input_field_key': 'test_input_field_value'}) - json_dump = self.xmodule.handle_ajax(dispatch, data) - expected_json_dump = '{"error": "[handle_ajax]: Unknown Command!"}' - self.assertEqual( - json.loads(json_dump), - json.loads(expected_json_dump) - ) - def test_max_score(self): self.xmodule.weight = 100.0 diff --git a/lms/djangoapps/courseware/features/lti.py b/lms/djangoapps/courseware/features/lti.py index 47faa45e83..f2b80a8787 100644 --- a/lms/djangoapps/courseware/features/lti.py +++ b/lms/djangoapps/courseware/features/lti.py @@ -13,22 +13,22 @@ from courseware.tests.factories import InstructorFactory @step('I view the LTI and error is shown$') def lti_is_not_rendered(_step): # error is shown - assert world.is_css_present('.error_message') + assert world.is_css_present('.error_message', wait_time=0) # iframe is not presented - assert not world.is_css_present('iframe') + assert not world.is_css_present('iframe', wait_time=0) # link is not presented - assert not world.is_css_present('.link_lti_new_window') + assert not world.is_css_present('.link_lti_new_window', wait_time=0) def check_lti_iframe_content(text): #inside iframe test content is presented location = world.scenario_dict['LTI'].location.html_id() - iframe_name = 'ltiLaunchFrame-' + location + iframe_name = 'ltiFrame-' + location with world.browser.get_iframe(iframe_name) as iframe: # iframe does not contain functions from terrain/ui_helpers.py - assert iframe.is_element_present_by_css('.result', wait_time=5) + assert iframe.is_element_present_by_css('.result', wait_time=0) assert (text == world.retry_on_exception( lambda: iframe.find_by_css('.result')[0].text, max_attempts=5 @@ -38,18 +38,18 @@ def check_lti_iframe_content(text): @step('I view the LTI and it is rendered in (.*)$') def lti_is_rendered(_step, rendered_in): if rendered_in.strip() == 'iframe': - assert world.is_css_present('iframe') - assert not world.is_css_present('.link_lti_new_window') - assert not world.is_css_present('.error_message') + assert world.is_css_present('iframe', wait_time=2) + assert not world.is_css_present('.link_lti_new_window', wait_time=0) + assert not world.is_css_present('.error_message', wait_time=0) # iframe is visible assert world.css_visible('iframe') check_lti_iframe_content("This is LTI tool. Success.") elif rendered_in.strip() == 'new page': - assert not world.is_css_present('iframe') - assert world.is_css_present('.link_lti_new_window') - assert not world.is_css_present('.error_message') + assert not world.is_css_present('iframe', wait_time=2) + assert world.is_css_present('.link_lti_new_window', wait_time=0) + assert not world.is_css_present('.error_message', wait_time=0) check_lti_popup() else: # incorrent rendered_in parameter assert False @@ -57,9 +57,9 @@ def lti_is_rendered(_step, rendered_in): @step('I view the LTI but incorrect_signature warning is rendered$') def incorrect_lti_is_rendered(_step): - assert world.is_css_present('iframe') - assert not world.is_css_present('.link_lti_new_window') - assert not world.is_css_present('.error_message') + assert world.is_css_present('iframe', wait_time=2) + assert not world.is_css_present('.link_lti_new_window', wait_time=0) + assert not world.is_css_present('.error_message', wait_time=0) #inside iframe test content is presented check_lti_iframe_content("Wrong LTI signature") @@ -234,10 +234,11 @@ def check_progress(_step, text): @step('I see graph with total progress "([^"]*)"$') def see_graph(_step, progress): SELECTOR = 'grade-detail-graph' - node = world.browser.find_by_xpath('//div[@id="{parent}"]//div[text()="{progress}"]'.format( + XPATH = '//div[@id="{parent}"]//div[text()="{progress}"]'.format( parent=SELECTOR, progress=progress, - )) + ) + node = world.browser.find_by_xpath(XPATH) assert node @@ -259,7 +260,7 @@ def see_value_in_the_gradebook(_step, label, text): @step('I submit answer to LTI question$') def click_grade(_step): location = world.scenario_dict['LTI'].location.html_id() - iframe_name = 'ltiLaunchFrame-' + location + iframe_name = 'ltiFrame-' + location with world.browser.get_iframe(iframe_name) as iframe: iframe.find_by_name('submit-button').first.click() assert iframe.is_text_present('LTI consumer (edX) responded with XML content') diff --git a/lms/djangoapps/courseware/tests/test_lti_integration.py b/lms/djangoapps/courseware/tests/test_lti_integration.py index ab2a575a0e..7faa40d2f9 100644 --- a/lms/djangoapps/courseware/tests/test_lti_integration.py +++ b/lms/djangoapps/courseware/tests/test_lti_integration.py @@ -5,8 +5,6 @@ from . import BaseTestXmodule from collections import OrderedDict import mock import urllib -from xmodule.lti_module import LTIModule -from mock import Mock class TestLTI(BaseTestXmodule): @@ -85,7 +83,6 @@ class TestLTI(BaseTestXmodule): Makes sure that all parameters extracted. """ generated_context = self.item_module.render('student_view').content - expected_context = { 'display_name': self.item_module.display_name, 'input_fields': self.correct_headers, @@ -93,7 +90,7 @@ class TestLTI(BaseTestXmodule): 'element_id': self.item_module.location.html_id(), 'launch_url': 'http://www.example.com', # default value 'open_in_a_new_page': True, - 'ajax_url': self.item_descriptor.xmodule_runtime.ajax_url, + 'form_url': self.item_descriptor.xmodule_runtime.handler_url(self.item_module, 'preview_handler').rstrip('/?'), } self.assertEqual( diff --git a/lms/templates/lti.html b/lms/templates/lti.html index e892e7450c..48ea3d9a40 100644 --- a/lms/templates/lti.html +++ b/lms/templates/lti.html @@ -12,7 +12,7 @@
+ @@ -21,6 +21,7 @@ ## The result of the form submit will be rendered here. % endif