Add ability to import tool profiles
fixes PLAT-2598 Test plan: * Install the plagiarism detection tool in a course locally using docker * Get a zip file from me with a bunch of exports * In the course that has the plagiarism detection tool installed * Import 'import.imscc' * Ensure that the import completes without warnings or errors * Import 'different_version.imscc' * Ensure that the import completes with a warning about finding a different version of the tool * Import 'missing_data.imscc' * Ensure that the import completes with a warning that has a link to an error report * In a course where the plagiarism detection tool is not installed * Import 'registration_url.imscc' * Ensure that the import completes with a warning that tells the user the registration url they can use to install the missing tool * Import 'import.imscc' * Ensure that the import completes with a warning that tells the user they need to install the tool but without a registration url Change-Id: I3836c2caf602487de135b5a8732bba00b96b9342 Reviewed-on: https://gerrit.instructure.com/114139 Tested-by: Jenkins Reviewed-by: Weston Dransfield <wdransfield@instructure.com> Reviewed-by: Jeremy Stanley <jeremy@instructure.com> Reviewed-by: Brad Humphrey <brad@instructure.com> QA-Review: August Thornton <august@instructure.com> Product-Review: Karl Lloyd <karl@instructure.com>
This commit is contained in:
parent
b308b24a29
commit
9f028d3d6d
|
@ -133,6 +133,7 @@ module Importers
|
|||
Importers::ExternalFeedImporter.process_migration(data, migration); migration.update_import_progress(56)
|
||||
Importers::GradingStandardImporter.process_migration(data, migration); migration.update_import_progress(58)
|
||||
Importers::ContextExternalToolImporter.process_migration(data, migration); migration.update_import_progress(60)
|
||||
Importers::ToolProfileImporter.process_migration(data, migration); migration.update_import_progress(61)
|
||||
Importers::QuizImporter.process_migration(data, migration, question_data); migration.update_import_progress(65)
|
||||
Importers::DiscussionTopicImporter.process_migration(data, migration); migration.update_import_progress(70)
|
||||
Importers::WikiPageImporter.process_migration(data, migration); migration.update_import_progress(75)
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
#
|
||||
# Copyright (C) 2017 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
module Importers
|
||||
class MissingRequiredToolProfileValuesError < StandardError; end
|
||||
|
||||
class ToolProfileImporter
|
||||
class << self
|
||||
def process_migration(data, migration)
|
||||
tool_profiles = data['tool_profiles'] || []
|
||||
|
||||
tool_profiles.each do |tool_profile|
|
||||
begin
|
||||
values = tease_out_required_values!(tool_profile)
|
||||
tool_proxies = Lti::ToolProxy.find_active_proxies_for_context_by_vendor_code_and_product_code(
|
||||
context: migration.context,
|
||||
vendor_code: values[:vendor_code],
|
||||
product_code: values[:product_code]
|
||||
)
|
||||
|
||||
if tool_proxies.empty?
|
||||
if values[:registration_url].blank?
|
||||
migration.add_warning(I18n.t("We were unable to find a tool profile match for \"%{product_name}\".", product_name: values[:product_name]))
|
||||
else
|
||||
migration.add_warning(I18n.t("We were unable to find a tool profile match for \"%{product_name}\". If you would like to use this tool please install it using the following registration url: %{registration_url}", product_name: values[:product_name], registration_url: values[:registration_url]))
|
||||
end
|
||||
elsif tool_proxies.none? { |tool_proxy| tool_proxy.matching_tool_profile?(tool_profile['tool_profile']) }
|
||||
migration.add_warning(I18n.t("We found a different version of \"%{product_name}\" installed for your course. If this tool fails to work as intended, try reregistering or reinstalling it.", product_name: values[:product_name]))
|
||||
end
|
||||
rescue MissingRequiredToolProfileValuesError => e
|
||||
migration.add_import_warning('tool_profile', tool_profile['resource_href'], e)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tease_out_required_values!(tool_profile)
|
||||
values = {
|
||||
vendor_code: tool_profile.dig('tool_profile', 'product_instance', 'product_info', 'product_family', 'vendor', 'code'),
|
||||
product_code: tool_profile.dig('tool_profile', 'product_instance', 'product_info', 'product_family', 'code'),
|
||||
registration_url: tool_profile.dig('meta', 'registration_url'),
|
||||
product_name: tool_profile.dig('tool_profile', 'product_instance', 'product_info', 'product_name', 'default_value'),
|
||||
}
|
||||
|
||||
missing_keys = values.select { |_, v| v.nil? }.keys
|
||||
|
||||
if missing_keys.present?
|
||||
fail MissingRequiredToolProfileValuesError, I18n.t("Missing required values: %{missing_values}", missing_values: missing_keys.join(','))
|
||||
else
|
||||
values
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -38,6 +38,12 @@ module Lti
|
|||
self.class.find_active_proxies_for_context(context).include?(self)
|
||||
end
|
||||
|
||||
def self.find_active_proxies_for_context_by_vendor_code_and_product_code(context:, vendor_code:, product_code:)
|
||||
find_active_proxies_for_context(context)
|
||||
.eager_load(:product_family)
|
||||
.where('lti_product_families.vendor_code = ? AND lti_product_families.product_code = ?', vendor_code, product_code)
|
||||
end
|
||||
|
||||
def self.find_active_proxies_for_context(context)
|
||||
find_all_proxies_for_context(context).where('lti_tool_proxies.workflow_state = ?', 'active')
|
||||
end
|
||||
|
@ -80,5 +86,23 @@ module Lti
|
|||
ims_tool_proxy.enabled_capabilities
|
||||
end
|
||||
|
||||
def matching_tool_profile?(other_profile)
|
||||
profile = raw_data['tool_profile']
|
||||
|
||||
return false if profile.dig('product_instance', 'product_info', 'product_family', 'vendor', 'code') !=
|
||||
other_profile.dig('product_instance', 'product_info', 'product_family', 'vendor', 'code')
|
||||
|
||||
return false if profile.dig('product_instance', 'product_info', 'product_family', 'code') !=
|
||||
other_profile.dig('product_instance', 'product_info', 'product_family', 'code')
|
||||
|
||||
resource_handlers = profile['resource_handler']
|
||||
other_resource_handlers = other_profile['resource_handler']
|
||||
|
||||
rh_names = resource_handlers.map { |rh| rh.dig('resource_type', 'code') }
|
||||
other_rh_names = other_resource_handlers.map { |rh| rh.dig('resource_type', 'code') }
|
||||
return false if rh_names.sort != other_rh_names.sort
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,6 +22,7 @@ module CC::Importer::Canvas
|
|||
include WikiConverter
|
||||
include AssignmentConverter
|
||||
include TopicConverter
|
||||
include ToolProfileConverter
|
||||
include WebcontentConverter
|
||||
include QuizConverter
|
||||
include MediaTrackConverter
|
||||
|
@ -58,6 +59,8 @@ module CC::Importer::Canvas
|
|||
res = lti.get_blti_resources(@manifest)
|
||||
@course[:external_tools] = lti.convert_blti_links(res, self)
|
||||
set_progress(50)
|
||||
@course[:tool_profiles] = convert_tool_profiles
|
||||
set_progress(52)
|
||||
@course[:file_map] = create_file_map
|
||||
set_progress(60)
|
||||
@course[:all_files_zip] = package_course_files
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
#
|
||||
# Copyright (C) 2017 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
module CC::Importer::Canvas
|
||||
module ToolProfileConverter
|
||||
include CC::Importer
|
||||
|
||||
def convert_tool_profiles
|
||||
tool_profiles = []
|
||||
|
||||
@manifest.css('resource[type=tool_profile]').each do |res|
|
||||
file = res.at_css('file')
|
||||
next unless file
|
||||
file_path = File.join @unzipped_file_path, file['href']
|
||||
json = JSON.parse(File.read(file_path))
|
||||
json['resource_href'] = file['href']
|
||||
tool_profiles << json
|
||||
end
|
||||
|
||||
tool_profiles
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,65 @@
|
|||
{
|
||||
"tool_profiles": [
|
||||
{
|
||||
"meta": {
|
||||
"registration_url": "https://www.samplelaunch.com/register"
|
||||
},
|
||||
"resource_href": "href",
|
||||
"tool_profile": {
|
||||
"lti_version": "LTI-2p0",
|
||||
"product_instance": {
|
||||
"guid": "be42ae52-23fe-48f5-a783-40ecc7ef6d5c",
|
||||
"product_info": {
|
||||
"product_version": "1.0",
|
||||
"product_family": {
|
||||
"code": "abc",
|
||||
"vendor": {
|
||||
"code": "123",
|
||||
"vendor_name": {
|
||||
"default_value": "acme"
|
||||
},
|
||||
"description": {
|
||||
"default_value": "example vendor"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"default_value": "example product"
|
||||
},
|
||||
"product_name": {
|
||||
"default_value": "learn abc's"
|
||||
}
|
||||
}
|
||||
},
|
||||
"base_url_choice": [
|
||||
{
|
||||
"default_base_url": "https://www.samplelaunch.com",
|
||||
"selector": {
|
||||
"applies_to": [
|
||||
"MessageHandler"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"resource_handler": [
|
||||
{
|
||||
"resource_type": {
|
||||
"code": "code"
|
||||
},
|
||||
"resource_name": {
|
||||
"default_value": "resource name",
|
||||
"key": ""
|
||||
},
|
||||
"message": [
|
||||
{
|
||||
"message_type": "message_type",
|
||||
"path": "https://www.samplelaunch.com/blti"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"service_offered": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
{
|
||||
"tool_profiles": [
|
||||
{
|
||||
"meta": {
|
||||
"registration_url": "https://www.samplelaunch.com/register"
|
||||
},
|
||||
"resource_href": "href",
|
||||
"tool_profile": {
|
||||
"lti_version": "LTI-2p0",
|
||||
"product_instance": {
|
||||
"guid": "be42ae52-23fe-48f5-a783-40ecc7ef6d5c",
|
||||
"product_info": {
|
||||
"product_version": "1.0",
|
||||
"product_family": {
|
||||
"code": "abc",
|
||||
"vendor": {
|
||||
"code": "123",
|
||||
"vendor_name": {
|
||||
"default_value": "acme"
|
||||
},
|
||||
"description": {
|
||||
"default_value": "example vendor"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"default_value": "example product"
|
||||
},
|
||||
"product_name": {
|
||||
"default_value": "learn abc's"
|
||||
}
|
||||
}
|
||||
},
|
||||
"base_url_choice": [
|
||||
{
|
||||
"default_base_url": "https://www.samplelaunch.com",
|
||||
"selector": {
|
||||
"applies_to": [
|
||||
"MessageHandler"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"resource_handler": [
|
||||
{
|
||||
"resource_type": {
|
||||
"code": "different_code"
|
||||
},
|
||||
"resource_name": {
|
||||
"default_value": "resource name",
|
||||
"key": ""
|
||||
},
|
||||
"message": [
|
||||
{
|
||||
"message_type": "message_type",
|
||||
"path": "https://www.samplelaunch.com/blti"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"service_offered": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<assignmentGroups xmlns="http://canvas.instructure.com/xsd/cccv1p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://canvas.instructure.com/xsd/cccv1p0 http://canvas.instructure.com/xsd/cccv1p0.xsd">
|
||||
<assignmentGroup identifier="i9b9077e9f08ef701b2b956092c195f8b">
|
||||
<title>Assignments</title>
|
||||
<position>1</position>
|
||||
<group_weight>0.0</group_weight>
|
||||
</assignmentGroup>
|
||||
</assignmentGroups>
|
|
@ -0,0 +1,2 @@
|
|||
Q: What did the panda say when he was forced out of his natural habitat?
|
||||
A: This is un-BEAR-able
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<course identifier="i15448fbdf54a2fc2013e32f908227288" xmlns="http://canvas.instructure.com/xsd/cccv1p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://canvas.instructure.com/xsd/cccv1p0 http://canvas.instructure.com/xsd/cccv1p0.xsd">
|
||||
<title>Import/Export Test</title>
|
||||
<course_code>Import/Export</course_code>
|
||||
<start_at>2017-06-12T22:35:22</start_at>
|
||||
<is_public>true</is_public>
|
||||
<allow_student_wiki_edits>false</allow_student_wiki_edits>
|
||||
<allow_student_forum_attachments>false</allow_student_forum_attachments>
|
||||
<lock_all_announcements>false</lock_all_announcements>
|
||||
<allow_student_organized_groups>true</allow_student_organized_groups>
|
||||
<default_view>feed</default_view>
|
||||
<license>public_domain</license>
|
||||
<lock_all_announcements>false</lock_all_announcements>
|
||||
<grading_standard_enabled>false</grading_standard_enabled>
|
||||
<storage_quota>524288000</storage_quota>
|
||||
<root_account_uuid>pd3ANgn1DnNxn04Gqwi2FGBdRNiULEbEC8L1ltGc</root_account_uuid>
|
||||
</course>
|
|
@ -0,0 +1,3 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<fileMeta xmlns="http://canvas.instructure.com/xsd/cccv1p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://canvas.instructure.com/xsd/cccv1p0 http://canvas.instructure.com/xsd/cccv1p0.xsd">
|
||||
</fileMeta>
|
|
@ -0,0 +1 @@
|
|||
{"tool_profile":{"lti_version":"LTI-2p0","product_instance":{"guid":"be42ae52-23fe-48f5-a783-40ecc7ef6d5c","product_info":{"product_version":"1.0","product_family":{"code":"similarity detection reference tool","vendor":{"code":"Instructure.com","vendor_name":{"default_value":"Instructure"},"description":{"default_value":"Canvas Learning Management System"}}},"description":{"default_value":"LTI 2.1 tool provider reference implementation"},"product_name":{"default_value":"similarity detection reference tool"}}},"base_url_choice":[{"default_base_url":"http://originality.docker","selector":{"applies_to":["MessageHandler"]}}],"resource_handler":[{"resource_type":{"code":"sumbissionsz"},"resource_name":{"default_value":"Similarity Detection Tool","key":""},"message":[{"message_type":"basic-lti-launch-request","path":"/submission/index","enabled_capability":["Canvas.placements.accountNavigation","Canvas.placements.courseNavigation"]}]},{"resource_type":{"code":"placements"},"resource_name":{"default_value":"Similarity Detection Tool","key":""},"message":[{"message_type":"basic-lti-launch-request","path":"/assignments/configure","enabled_capability":["Canvas.placements.similarityDetection"]}]}],"service_offered":[{"endpoint":"http://originality.docker/event/submission","action":["POST"],"@id":"http://originality.docker/lti/v2/services#vnd.Canvas.SubmissionEvent","@type":"RestService"}]},"meta":{"registration_url":"https://register.me/register"}}
|
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<manifest identifier="ie6041ef31d35509f367c4b8464cc131e" xmlns="http://www.imsglobal.org/xsd/imsccv1p1/imscp_v1p1" xmlns:lom="http://ltsc.ieee.org/xsd/imsccv1p1/LOM/resource" xmlns:lomimscc="http://ltsc.ieee.org/xsd/imsccv1p1/LOM/manifest" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsccv1p1/imscp_v1p1 http://www.imsglobal.org/profile/cc/ccv1p1/ccv1p1_imscp_v1p2_v1p0.xsd http://ltsc.ieee.org/xsd/imsccv1p1/LOM/resource http://www.imsglobal.org/profile/cc/ccv1p1/LOM/ccv1p1_lomresource_v1p0.xsd http://ltsc.ieee.org/xsd/imsccv1p1/LOM/manifest http://www.imsglobal.org/profile/cc/ccv1p1/LOM/ccv1p1_lommanifest_v1p0.xsd">
|
||||
<metadata>
|
||||
<schema>IMS Common Cartridge</schema>
|
||||
<schemaversion>1.1.0</schemaversion>
|
||||
<lomimscc:lom>
|
||||
<lomimscc:general>
|
||||
<lomimscc:title>
|
||||
<lomimscc:string>Import/Export Test</lomimscc:string>
|
||||
</lomimscc:title>
|
||||
</lomimscc:general>
|
||||
<lomimscc:lifeCycle>
|
||||
<lomimscc:contribute>
|
||||
<lomimscc:date>
|
||||
<lomimscc:dateTime>2017-06-12</lomimscc:dateTime>
|
||||
</lomimscc:date>
|
||||
</lomimscc:contribute>
|
||||
</lomimscc:lifeCycle>
|
||||
<lomimscc:rights>
|
||||
<lomimscc:copyrightAndOtherRestrictions>
|
||||
<lomimscc:value>yes</lomimscc:value>
|
||||
</lomimscc:copyrightAndOtherRestrictions>
|
||||
<lomimscc:description>
|
||||
<lomimscc:string>Public Domain - http://en.wikipedia.org/wiki/Public_domain</lomimscc:string>
|
||||
</lomimscc:description>
|
||||
</lomimscc:rights>
|
||||
</lomimscc:lom>
|
||||
</metadata>
|
||||
<organizations>
|
||||
<organization identifier="org_1" structure="rooted-hierarchy">
|
||||
<item identifier="LearningModules">
|
||||
</item>
|
||||
</organization>
|
||||
</organizations>
|
||||
<resources>
|
||||
<resource identifier="i15448fbdf54a2fc2013e32f908227288" type="associatedcontent/imscc_xmlv1p1/learning-application-resource" href="course_settings/canvas_export.txt">
|
||||
<file href="course_settings/course_settings.xml"/>
|
||||
<file href="course_settings/assignment_groups.xml"/>
|
||||
<file href="course_settings/files_meta.xml"/>
|
||||
<file href="course_settings/media_tracks.xml"/>
|
||||
<file href="course_settings/canvas_export.txt"/>
|
||||
</resource>
|
||||
<resource identifier="i964fd8107ac2c2e75e9a142971693976" type="tool_profile">
|
||||
<file href="i964fd8107ac2c2e75e9a142971693976.json"/>
|
||||
</resource>
|
||||
</resources>
|
||||
</manifest>
|
|
@ -67,3 +67,9 @@ def get_import_context(system=nil)
|
|||
|
||||
context
|
||||
end
|
||||
|
||||
class ImportHelper
|
||||
def self.get_import_data_xml(sub_folder, file_name)
|
||||
File.open(File.join(IMPORT_JSON_DIR, sub_folder, "#{file_name}.xml")) { |f| Nokogiri::XML(f) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
#
|
||||
# Copyright (C) 2017 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../../../../import_helper')
|
||||
|
||||
describe CC::Importer::Canvas::ToolProfileConverter do
|
||||
let(:manifest) { ImportHelper.get_import_data_xml('unzipped', 'imsmanifest') }
|
||||
let(:path) { File.expand_path(File.dirname(__FILE__) + '/../../../../fixtures/importer/unzipped') }
|
||||
let(:converter) do
|
||||
Class.new do
|
||||
include CC::Importer::Canvas::ToolProfileConverter
|
||||
|
||||
def initialize(manifest, path)
|
||||
@manifest = manifest
|
||||
@unzipped_file_path = path
|
||||
end
|
||||
end.new(manifest, path)
|
||||
end
|
||||
|
||||
describe '#convert_tool_profiles' do
|
||||
it 'unpacks tool profiles in the common cartridge' do
|
||||
tool_profiles = converter.convert_tool_profiles
|
||||
expect(tool_profiles.size).to eq(1)
|
||||
expect(tool_profiles.first).to eq({
|
||||
"tool_profile" => {
|
||||
"lti_version" => "LTI-2p0",
|
||||
"product_instance" => {
|
||||
"guid" => "be42ae52-23fe-48f5-a783-40ecc7ef6d5c",
|
||||
"product_info" => {
|
||||
"product_version" => "1.0",
|
||||
"product_family" => {
|
||||
"code" => "similarity detection reference tool",
|
||||
"vendor" => {
|
||||
"code" => "Instructure.com",
|
||||
"vendor_name" => {
|
||||
"default_value" => "Instructure"
|
||||
},
|
||||
"description" => {
|
||||
"default_value" => "Canvas Learning Management System"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description" => {
|
||||
"default_value" => "LTI 2.1 tool provider reference implementation"
|
||||
},
|
||||
"product_name" => {
|
||||
"default_value" => "similarity detection reference tool"
|
||||
}
|
||||
}
|
||||
},
|
||||
"base_url_choice" => [
|
||||
{
|
||||
"default_base_url" => "http://originality.docker",
|
||||
"selector" => {
|
||||
"applies_to" => [
|
||||
"MessageHandler"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"resource_handler" => [
|
||||
{
|
||||
"resource_type" => {
|
||||
"code" => "sumbissionsz"
|
||||
},
|
||||
"resource_name" => {
|
||||
"default_value" => "Similarity Detection Tool",
|
||||
"key" => ""
|
||||
},
|
||||
"message" => [
|
||||
{
|
||||
"message_type" => "basic-lti-launch-request",
|
||||
"path" => "/submission/index",
|
||||
"enabled_capability" => [
|
||||
"Canvas.placements.accountNavigation",
|
||||
"Canvas.placements.courseNavigation"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"resource_type" => {
|
||||
"code" => "placements"
|
||||
},
|
||||
"resource_name" => {
|
||||
"default_value" => "Similarity Detection Tool",
|
||||
"key" => ""
|
||||
},
|
||||
"message" => [
|
||||
{
|
||||
"message_type" => "basic-lti-launch-request",
|
||||
"path" => "/assignments/configure",
|
||||
"enabled_capability" => [
|
||||
"Canvas.placements.similarityDetection"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"service_offered" => [
|
||||
{
|
||||
"endpoint" => "http://originality.docker/event/submission",
|
||||
"action" => [
|
||||
"POST"
|
||||
],
|
||||
"@id" => "http://originality.docker/lti/v2/services#vnd.Canvas.SubmissionEvent",
|
||||
"@type" => "RestService"
|
||||
}
|
||||
]
|
||||
},
|
||||
"meta" => {
|
||||
"registration_url" => "https://register.me/register"
|
||||
},
|
||||
"resource_href" => "i964fd8107ac2c2e75e9a142971693976.json"
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,125 @@
|
|||
#
|
||||
# Copyright (C) 2017 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/spec_helper.rb')
|
||||
|
||||
RSpec.shared_context "lti2_course_spec_helper", :shared_context => :metadata do
|
||||
|
||||
let(:account) { Account.create! }
|
||||
let(:course) { Course.create!(account: account) }
|
||||
let(:developer_key) {DeveloperKey.create!(redirect_uri: 'http://www.example.com/redirect')}
|
||||
let(:product_family) do
|
||||
Lti::ProductFamily.create!(
|
||||
vendor_code: '123',
|
||||
product_code: 'abc',
|
||||
vendor_name: 'acme',
|
||||
root_account: account,
|
||||
developer_key: developer_key
|
||||
)
|
||||
end
|
||||
let(:tool_proxy) do
|
||||
tp = Lti::ToolProxy.create!(
|
||||
context: course,
|
||||
guid: SecureRandom.uuid,
|
||||
shared_secret: 'abc',
|
||||
product_family: product_family,
|
||||
product_version: '1',
|
||||
workflow_state: 'active',
|
||||
raw_data: {
|
||||
'enabled_capability' => ['Security.splitSecret'],
|
||||
'tool_profile' => {
|
||||
'lti_version' => 'LTI-2p0',
|
||||
'product_instance' => {
|
||||
'guid' => 'be42ae52-23fe-48f5-a783-40ecc7ef6d5c',
|
||||
'product_info' => {
|
||||
'product_version' => '1.0',
|
||||
'product_family' => {
|
||||
'code' => 'abc',
|
||||
'vendor' => {
|
||||
'code' => '123',
|
||||
'vendor_name' => {
|
||||
'default_value' => 'acme'
|
||||
},
|
||||
'description' => {
|
||||
'default_value' => 'example vendor'
|
||||
}
|
||||
}
|
||||
},
|
||||
'description' => {
|
||||
'default_value' => 'example product'
|
||||
},
|
||||
'product_name' => {
|
||||
'default_value' => "learn abc's"
|
||||
}
|
||||
}
|
||||
},
|
||||
'base_url_choice' => [
|
||||
{
|
||||
'default_base_url' => 'https://www.samplelaunch.com',
|
||||
'selector' => {
|
||||
'applies_to' => [
|
||||
'MessageHandler'
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
'resource_handler' => [
|
||||
{
|
||||
'resource_type' => {
|
||||
'code' => 'code'
|
||||
},
|
||||
'resource_name' => {
|
||||
'default_value' => 'resource name',
|
||||
'key' => ''
|
||||
},
|
||||
'message' => [
|
||||
{
|
||||
'message_type' => 'message_type',
|
||||
'path' => 'https://www.samplelaunch.com/blti'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
'service_offered' => []
|
||||
}
|
||||
},
|
||||
lti_version: '1'
|
||||
)
|
||||
Lti::ToolProxyBinding.where(context_id: account, context_type: account.class.to_s,
|
||||
tool_proxy_id: tp).first_or_create!
|
||||
tp
|
||||
end
|
||||
let(:resource_handler) do
|
||||
Lti::ResourceHandler.create!(
|
||||
resource_type_code: 'code',
|
||||
name: 'resource name',
|
||||
tool_proxy: tool_proxy
|
||||
)
|
||||
end
|
||||
let(:message_handler) do
|
||||
Lti::MessageHandler.create!(
|
||||
message_type: 'message_type',
|
||||
launch_path: 'https://www.samplelaunch.com/blti',
|
||||
resource_handler: resource_handler,
|
||||
tool_proxy: tool_proxy
|
||||
)
|
||||
end
|
||||
let(:tool_proxy_binding) do
|
||||
Lti::ToolProxyBinding.where(context_id: account, context_type: account.class.to_s,
|
||||
tool_proxy_id: tool_proxy).first_or_create!
|
||||
end
|
||||
end
|
|
@ -39,7 +39,64 @@ RSpec.shared_context "lti2_spec_helper", :shared_context => :metadata do
|
|||
product_family: product_family,
|
||||
product_version: '1',
|
||||
workflow_state: 'active',
|
||||
raw_data: {'enabled_capability' => ['Security.splitSecret']},
|
||||
raw_data: {
|
||||
'enabled_capability' => ['Security.splitSecret'],
|
||||
'tool_profile' => {
|
||||
'lti_version' => 'LTI-2p0',
|
||||
'product_instance' => {
|
||||
'guid' => 'be42ae52-23fe-48f5-a783-40ecc7ef6d5c',
|
||||
'product_info' => {
|
||||
'product_version' => '1.0',
|
||||
'product_family' => {
|
||||
'code' => 'abc',
|
||||
'vendor' => {
|
||||
'code' => '123',
|
||||
'vendor_name' => {
|
||||
'default_value' => 'acme'
|
||||
},
|
||||
'description' => {
|
||||
'default_value' => 'example vendor'
|
||||
}
|
||||
}
|
||||
},
|
||||
'description' => {
|
||||
'default_value' => 'example product'
|
||||
},
|
||||
'product_name' => {
|
||||
'default_value' => "learn abc's"
|
||||
}
|
||||
}
|
||||
},
|
||||
'base_url_choice' => [
|
||||
{
|
||||
'default_base_url' => 'https://www.samplelaunch.com',
|
||||
'selector' => {
|
||||
'applies_to' => [
|
||||
'MessageHandler'
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
'resource_handler' => [
|
||||
{
|
||||
'resource_type' => {
|
||||
'code' => 'code'
|
||||
},
|
||||
'resource_name' => {
|
||||
'default_value' => 'resource name',
|
||||
'key' => ''
|
||||
},
|
||||
'message' => [
|
||||
{
|
||||
'message_type' => 'message_type',
|
||||
'path' => 'https://www.samplelaunch.com/blti'
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
'service_offered' => []
|
||||
}
|
||||
},
|
||||
lti_version: '1'
|
||||
)
|
||||
Lti::ToolProxyBinding.where(context_id: account, context_type: account.class.to_s,
|
||||
|
|
|
@ -60,6 +60,9 @@ describe Course do
|
|||
}}.with_indifferent_access
|
||||
migration.migration_ids_to_import = params
|
||||
|
||||
# tool profile tests
|
||||
Importers::ToolProfileImporter.expects(:process_migration)
|
||||
|
||||
Importers::CourseContentImporter.import_content(@course, data, params, migration)
|
||||
@course.reload
|
||||
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
#
|
||||
# Copyright (C) 2017 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '../../../import_helper')
|
||||
require File.expand_path(File.dirname(__FILE__) + '../../../lti2_course_spec_helper')
|
||||
|
||||
describe Importers::ToolProfileImporter do
|
||||
|
||||
describe '#process_migration' do
|
||||
context 'no tool profiles' do
|
||||
let(:data) { {} }
|
||||
let(:migration) { double }
|
||||
|
||||
it 'does nothing' do
|
||||
expect { Importers::ToolProfileImporter.process_migration(data, migration) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'malformed tool profile' do
|
||||
let(:data) { { "tool_profiles" => [{ 'resource_href' => 'href' }] } }
|
||||
let(:migration) { double }
|
||||
|
||||
it 'adds an import warning' do
|
||||
expect(migration).to receive(:add_import_warning).with('tool_profile', 'href', instance_of(Importers::MissingRequiredToolProfileValuesError))
|
||||
Importers::ToolProfileImporter.process_migration(data, migration)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with tool profile and no tool proxies' do
|
||||
let(:data) { get_import_data('', 'matching_tool_profiles') }
|
||||
let(:context) { get_import_context }
|
||||
let(:migration) { context.content_migrations.create! }
|
||||
|
||||
it 'adds a warning to the migration' do
|
||||
expect(migration).to receive(:add_warning).with("We were unable to find a tool profile match for \"learn abc's\". If you would like to use this tool please install it using the following registration url: https://www.samplelaunch.com/register")
|
||||
Importers::ToolProfileImporter.process_migration(data, migration)
|
||||
end
|
||||
|
||||
it 'adds a warning to the migration without registration url' do
|
||||
data['tool_profiles'].first['meta']['registration_url'] = ''
|
||||
expect(migration).to receive(:add_warning).with("We were unable to find a tool profile match for \"learn abc's\".")
|
||||
Importers::ToolProfileImporter.process_migration(data, migration)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with tool profile and different version tool proxies' do
|
||||
include_context 'lti2_course_spec_helper'
|
||||
|
||||
let(:data) { get_import_data('', 'nonmatching_tool_profiles') }
|
||||
let(:migration) { double(context: course) }
|
||||
|
||||
it 'does nothing' do
|
||||
tool_proxy # necessary to instantiate tool_proxy
|
||||
expect(migration).to receive(:add_warning).with("We found a different version of \"learn abc's\" installed for your course. If this tool fails to work as intended, try reregistering or reinstalling it.")
|
||||
Importers::ToolProfileImporter.process_migration(data, migration)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with tool profile and matching tool proxies' do
|
||||
include_context 'lti2_course_spec_helper'
|
||||
|
||||
let(:data) { get_import_data('', 'matching_tool_profiles') }
|
||||
let(:migration) { double(context: course) }
|
||||
|
||||
it 'does nothing' do
|
||||
tool_proxy # necessary to instantiate tool_proxy
|
||||
expect { Importers::ToolProfileImporter.process_migration(data, migration) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -17,6 +17,7 @@
|
|||
#
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb')
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../../lti2_spec_helper.rb')
|
||||
require_dependency "lti/tool_proxy"
|
||||
|
||||
module Lti
|
||||
|
@ -173,6 +174,37 @@ module Lti
|
|||
end
|
||||
end
|
||||
|
||||
describe "#find_active_proxies_for_context_by_vendor_code_and_product_code" do
|
||||
it "doesn't return tool_proxies that are disabled" do
|
||||
tool_proxy = create_tool_proxy(context: sub_account_2_1, workflow_state: 'disabled')
|
||||
tool_proxy.bindings.create!(context: sub_account_2_1)
|
||||
proxies = described_class.find_active_proxies_for_context_by_vendor_code_and_product_code(context: sub_account_2_1, vendor_code: '123', product_code: 'abc')
|
||||
expect(proxies.count).to eq 0
|
||||
end
|
||||
|
||||
it "doesn't return tool_proxies that don't have a matching vendor_code" do
|
||||
tool_proxy = create_tool_proxy(context: sub_account_2_1)
|
||||
tool_proxy.bindings.create!(context: sub_account_2_1)
|
||||
proxies = described_class.find_active_proxies_for_context_by_vendor_code_and_product_code(context: sub_account_2_1, vendor_code: '1234', product_code: 'abc')
|
||||
expect(proxies.count).to eq 0
|
||||
end
|
||||
|
||||
it "doesn't return tool_proxies that don't have a matching product_code" do
|
||||
tool_proxy = create_tool_proxy(context: sub_account_2_1)
|
||||
tool_proxy.bindings.create!(context: sub_account_2_1)
|
||||
proxies = described_class.find_active_proxies_for_context_by_vendor_code_and_product_code(context: sub_account_2_1, vendor_code: '123', product_code: 'abcd')
|
||||
expect(proxies.count).to eq 0
|
||||
end
|
||||
|
||||
it "returns tool proxies that match" do
|
||||
tool_proxy = create_tool_proxy(context: sub_account_2_1)
|
||||
tool_proxy.bindings.create!(context: sub_account_2_1)
|
||||
proxies = described_class.find_active_proxies_for_context_by_vendor_code_and_product_code(context: sub_account_2_1, vendor_code: '123', product_code: 'abc')
|
||||
expect(proxies.count).to eq 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "#find_active_proxies_for_context" do
|
||||
it "doesn't return tool_proxies that are disabled" do
|
||||
tool_proxy = create_tool_proxy(context: sub_account_2_1, workflow_state: 'disabled')
|
||||
|
@ -282,5 +314,123 @@ module Lti
|
|||
end
|
||||
end
|
||||
|
||||
describe "#matching_tool_profile?" do
|
||||
include_context 'lti2_spec_helper'
|
||||
|
||||
it 'returns true when there is a match' do
|
||||
expect(tool_proxy.matching_tool_profile?({
|
||||
"product_instance" => {
|
||||
"product_info" => {
|
||||
"product_family" => {
|
||||
"vendor" => {
|
||||
"code" => "123"
|
||||
},
|
||||
"code" => "abc"
|
||||
},
|
||||
}
|
||||
},
|
||||
"resource_handler" => [
|
||||
{
|
||||
"resource_type" => {
|
||||
"code" => "code"
|
||||
}
|
||||
}
|
||||
]
|
||||
})).to eq(true)
|
||||
end
|
||||
|
||||
it "returns false when the vendor_code doesn't match" do
|
||||
expect(tool_proxy.matching_tool_profile?({
|
||||
"product_instance" => {
|
||||
"product_info" => {
|
||||
"product_family" => {
|
||||
"vendor" => {
|
||||
"code" => "1234"
|
||||
},
|
||||
"code" => "abc"
|
||||
},
|
||||
}
|
||||
},
|
||||
"resource_handler" => [
|
||||
{
|
||||
"resource_type" => {
|
||||
"code" => "code"
|
||||
}
|
||||
}
|
||||
]
|
||||
})).to eq(false)
|
||||
end
|
||||
|
||||
it "returns false when the product_code doesn't match" do
|
||||
expect(tool_proxy.matching_tool_profile?({
|
||||
"product_instance" => {
|
||||
"product_info" => {
|
||||
"product_family" => {
|
||||
"vendor" => {
|
||||
"code" => "123"
|
||||
},
|
||||
"code" => "abcd"
|
||||
},
|
||||
}
|
||||
},
|
||||
"resource_handler" => [
|
||||
{
|
||||
"resource_type" => {
|
||||
"code" => "code"
|
||||
}
|
||||
}
|
||||
]
|
||||
})).to eq(false)
|
||||
end
|
||||
|
||||
it "returns false when the resource type codes do not match" do
|
||||
expect(tool_proxy.matching_tool_profile?({
|
||||
"product_instance" => {
|
||||
"product_info" => {
|
||||
"product_family" => {
|
||||
"vendor" => {
|
||||
"code" => "123"
|
||||
},
|
||||
"code" => "abc"
|
||||
},
|
||||
}
|
||||
},
|
||||
"resource_handler" => [
|
||||
{
|
||||
"resource_type" => {
|
||||
"code" => "different_code"
|
||||
}
|
||||
}
|
||||
]
|
||||
})).to eq(false)
|
||||
end
|
||||
|
||||
it "returns false when the resource handlers differ in number" do
|
||||
expect(tool_proxy.matching_tool_profile?({
|
||||
"product_instance" => {
|
||||
"product_info" => {
|
||||
"product_family" => {
|
||||
"vendor" => {
|
||||
"code" => "123"
|
||||
},
|
||||
"code" => "abc"
|
||||
},
|
||||
}
|
||||
},
|
||||
"resource_handler" => [
|
||||
{
|
||||
"resource_type" => {
|
||||
"code" => "different_code"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resource_type" => {
|
||||
"code" => "code"
|
||||
}
|
||||
}
|
||||
]
|
||||
})).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue