add lti2 models and registration endpoints
fixes PLAT-522 PLAT-429 PLAT-545 PLAT-540 PLAT-498 test-plan you can register an lti2 tool Change-Id: If045b694213e1019f8a595b63a7e33ea4ac28e64 Reviewed-on: https://gerrit.instructure.com/37095 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Bracken Mosbacker <bracken@instructure.com> Product-Review: Bracken Mosbacker <bracken@instructure.com> QA-Review: Clare Strong <clare@instructure.com> Reviewed-by: Brad Humphrey <brad@instructure.com>
This commit is contained in:
parent
0212b59646
commit
a91d9c09d0
|
@ -148,4 +148,5 @@ gem 'paginated_collection', :path => 'gems/paginated_collection'
|
|||
gem 'twitter', :path => 'gems/twitter'
|
||||
gem 'utf8_cleaner', :path => 'gems/utf8_cleaner'
|
||||
gem 'workflow', :path => 'gems/workflow'
|
||||
gem 'ims-lti', '~> 2.0.0.beta.1'
|
||||
|
||||
|
|
|
@ -1075,9 +1075,10 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
API_REQUEST_REGEX = %r{\A/api/v\d}
|
||||
LTI_API_REQUEST_REGEX = %r{\A/api/lti/}
|
||||
|
||||
def api_request?
|
||||
@api_request ||= !!request.path.match(API_REQUEST_REGEX)
|
||||
@api_request ||= !!request.path.match(API_REQUEST_REGEX) || !!request.path.match(LTI_API_REQUEST_REGEX)
|
||||
end
|
||||
|
||||
def session_loaded?
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
#
|
||||
# Copyright (C) 2014 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 Lti
|
||||
class ToolConsumerProfileController < ApplicationController
|
||||
before_filter :require_context
|
||||
skip_before_filter :require_user
|
||||
skip_before_filter :load_user
|
||||
|
||||
def show
|
||||
profile = Lti::ToolConsumerProfileCreator.new(@account, tool_proxy_url).create
|
||||
render json: profile.to_json, :content_type => 'application/json'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tool_proxy_url
|
||||
case context
|
||||
when Course
|
||||
create_course_lti_tool_proxy_url(context)
|
||||
when Account
|
||||
create_account_lti_tool_proxy_url(context)
|
||||
else
|
||||
raise "Unsupported context"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,107 @@
|
|||
# Copyright (C) 2014 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 Lti
|
||||
class ToolProxyController < ApplicationController
|
||||
before_filter :require_context, :except => [:show]
|
||||
skip_before_filter :require_user, only: [:create, :show]
|
||||
skip_before_filter :load_user, only: [:create, :show]
|
||||
|
||||
def show
|
||||
tool_proxy = ToolProxy.where(guid: params['tool_proxy_guid']).first
|
||||
if tool_proxy && authorized_request?(tool_proxy.shared_secret)
|
||||
render json: tool_proxy.raw_data
|
||||
else
|
||||
render json: {error: 'unauthorized'}, status: :unauthorized
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
secret = RegistrationRequestService.retrieve_registration_password(oauth_consumer_key)
|
||||
if authorized_request?(secret)
|
||||
tool_proxy = ToolProxyService.new.process_tool_proxy_json(request.body.read, context, oauth_consumer_key)
|
||||
json = {
|
||||
"@context" => "http://purl.imsglobal.org/ctx/lti/v2/ToolProxyId",
|
||||
"@type" => "ToolProxy",
|
||||
"@id" => nil,
|
||||
"tool_proxy_guid" => tool_proxy.guid
|
||||
}
|
||||
|
||||
render json: json, status: :created
|
||||
else
|
||||
render json: {error: 'unauthorized'}, status: :unauthorized
|
||||
end
|
||||
end
|
||||
|
||||
def register
|
||||
@lti_launch = Launch.new
|
||||
@lti_launch.resource_url = params[:tool_consumer_url]
|
||||
message = RegistrationRequestService.create_request(tool_consumer_profile_url, registration_return_url)
|
||||
@lti_launch.params = message.post_params
|
||||
@lti_launch.link_text = I18n.t('lti2.register_tool', 'Register Tool')
|
||||
@lti_launch.launch_type = message.launch_presentation_document_target
|
||||
@lti_launch.message_type = message.lti_message_type
|
||||
|
||||
render template: 'lti/framed_launch'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authorized_request?(secret)
|
||||
OAuth::Signature.build(request, :consumer_secret => secret).verify()
|
||||
end
|
||||
|
||||
def oauth_consumer_key
|
||||
@oauth_consumer_key ||= OAuth::Helper.parse_header(authorization_header(request))['oauth_consumer_key']
|
||||
end
|
||||
|
||||
def tool_consumer_profile_url
|
||||
tp_id = SecureRandom.uuid
|
||||
case context
|
||||
when Course
|
||||
course_tool_consumer_profile_url(context, tp_id)
|
||||
when Account
|
||||
account_tool_consumer_profile_url(context, tp_id)
|
||||
else
|
||||
raise "Unsupported context"
|
||||
end
|
||||
end
|
||||
|
||||
def registration_return_url
|
||||
case context
|
||||
when Course
|
||||
course_settings_url(context)
|
||||
when Account
|
||||
account_settings_url(context)
|
||||
else
|
||||
raise "Unsupported context"
|
||||
end
|
||||
end
|
||||
|
||||
def authorization_header(request)
|
||||
if CANVAS_RAILS3
|
||||
request.authorization
|
||||
else
|
||||
request.env['HTTP_AUTHORIZATION'] ||
|
||||
request.env['X-HTTP_AUTHORIZATION'] ||
|
||||
request.env['X_HTTP_AUTHORIZATION'] ||
|
||||
request.env['REDIRECT_X_HTTP_AUTHORIZATION']
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -19,6 +19,8 @@
|
|||
require 'oauth/client/action_pack'
|
||||
|
||||
class LtiApiController < ApplicationController
|
||||
skip_before_filter :require_user
|
||||
skip_before_filter :load_user
|
||||
skip_before_filter :verify_authenticity_token
|
||||
|
||||
# this API endpoint passes all the existing tests for the LTI v1.1 outcome service specification
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
module Lti
|
||||
def self.table_name_prefix
|
||||
'lti_'
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
module Lti
|
||||
class Launch
|
||||
|
||||
attr_writer :analytics_id
|
||||
attr_accessor :link_text, :resource_url, :params, :launch_type, :message_type
|
||||
|
||||
|
||||
def resource_path
|
||||
url = URI.parse(resource_url)
|
||||
url.path.empty? ? '/' : url.path
|
||||
end
|
||||
|
||||
def analytics_id
|
||||
@analytics_id || URI.parse(resource_url).host || 'unknown'
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,32 @@
|
|||
#
|
||||
# Copyright (C) 2014 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 Lti
|
||||
class MessageHandler< ActiveRecord::Base
|
||||
|
||||
attr_accessible :message_type, :launch_path, :capabilities, :parameters, :resource
|
||||
|
||||
belongs_to :resource, class_name: "Lti::ResourceHandler", :foreign_key => :resource_handler_id
|
||||
|
||||
serialize :capabilities
|
||||
serialize :parameters
|
||||
|
||||
validates_presence_of :message_type, :resource, :launch_path
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,32 @@
|
|||
#
|
||||
# Copyright (C) 2014 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 Lti
|
||||
class ProductFamily < ActiveRecord::Base
|
||||
|
||||
attr_accessible :vendor_code, :product_code, :vendor_name, :vendor_description, :website, :vendor_email, :root_account
|
||||
|
||||
belongs_to :root_account, class_name: 'Account'
|
||||
has_many :tool_proxies, class_name: "Lti::ToolProxy"
|
||||
|
||||
validates_presence_of :vendor_code, :product_code, :vendor_name, :root_account
|
||||
validates_uniqueness_of :product_code, scope: [:vendor_code, :root_account_id]
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
module Lti
|
||||
class RegistrationRequestService
|
||||
|
||||
def self.retrieve_registration_password(guid)
|
||||
Rails.cache.read(req_cache_key(guid))
|
||||
end
|
||||
|
||||
def self.create_request(tc_profile_url, return_url)
|
||||
registration_request = IMS::LTI::Models::Messages::RegistrationRequest.new(
|
||||
lti_version: IMS::LTI::Models::LTIModel::LTI_VERSION_2P0,
|
||||
launch_presentation_document_target: IMS::LTI::Models::Messages::Message::LAUNCH_TARGET_IFRAME,
|
||||
tc_profile_url: tc_profile_url,
|
||||
launch_presentation_return_url: return_url
|
||||
)
|
||||
reg_key, reg_password = registration_request.generate_key_and_password
|
||||
cache_registration(reg_key, reg_password)
|
||||
|
||||
registration_request
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def self.cache_registration(reg_key, reg_password)
|
||||
Rails.cache.write(req_cache_key(reg_key), reg_password, :expires_in => 1.hour)
|
||||
end
|
||||
|
||||
def self.req_cache_key(reg_key)
|
||||
['lti_registration_request', reg_key].cache_key
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,33 @@
|
|||
#
|
||||
# Copyright (C) 2014 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 Lti
|
||||
class ResourceHandler < ActiveRecord::Base
|
||||
|
||||
attr_accessible :resource_type_code, :placements, :name, :description, :icon_info, :tool_proxy
|
||||
|
||||
belongs_to :tool_proxy, class_name: 'Lti::ToolProxy'
|
||||
has_many :message_handlers, class_name: 'Lti::MessageHandler', :foreign_key => :resource_handler_id
|
||||
has_many :placements, class_name: 'Lti::ResourcePlacement'
|
||||
|
||||
serialize :icon_info
|
||||
|
||||
validates_presence_of :resource_type_code, :name, :tool_proxy
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
#
|
||||
# Copyright (C) 2014 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 Lti
|
||||
class ResourcePlacement < ActiveRecord::Base
|
||||
|
||||
attr_accessible :placement, :resource
|
||||
|
||||
belongs_to :resource, class_name: 'Lti::ResourceHandler'
|
||||
validates_presence_of :resource, :placement
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,66 @@
|
|||
module Lti
|
||||
class ToolConsumerProfileCreator
|
||||
|
||||
def initialize(account, tp_registration_url)
|
||||
@root_account = account.root_account
|
||||
@tp_registration_url = tp_registration_url
|
||||
end
|
||||
|
||||
def create
|
||||
profile = IMS::LTI::Models::ToolConsumerProfile.new
|
||||
profile.lti_version = IMS::LTI::Models::ToolConsumerProfile::LTI_VERSION_2P0
|
||||
profile.product_instance = create_product_instance
|
||||
profile.service_offered = [create_tp_registration_service]
|
||||
profile.capability_offered = capabilities
|
||||
|
||||
profile
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_product_instance
|
||||
product_instance = IMS::LTI::Models::ProductInstance.new
|
||||
product_instance.guid = @root_account.lti_guid
|
||||
product_instance.product_info = create_product_info
|
||||
product_instance
|
||||
end
|
||||
|
||||
def create_product_info
|
||||
product_info = IMS::LTI::Models::ProductInfo.new
|
||||
product_info.create_product_name('Canvas by Instructure')
|
||||
product_info.product_version = 'none'
|
||||
product_info.product_family = create_product_family
|
||||
product_info
|
||||
end
|
||||
|
||||
def create_product_family
|
||||
product_family = IMS::LTI::Models::ProductFamily.new
|
||||
product_family.code = 'canvas'
|
||||
product_family.vendor = create_vendor
|
||||
product_family
|
||||
end
|
||||
|
||||
def create_vendor
|
||||
vendor = IMS::LTI::Models::Vendor.new
|
||||
vendor.code = 'https://instructure.com'
|
||||
vendor.create_vendor_name('Instructure')
|
||||
vendor.timestamp = Time.parse('2008-03-27 00:00:00 -0600')
|
||||
vendor
|
||||
end
|
||||
|
||||
def create_tp_registration_service
|
||||
reg_srv = IMS::LTI::Models::RestService.new
|
||||
reg_srv.id = 'tcp:ToolProxy.collection'
|
||||
reg_srv.endpoint = @tp_registration_url
|
||||
reg_srv.type = 'RestService'
|
||||
reg_srv.format = ['application/vnd.ims.lti.v2.toolproxy+json']
|
||||
reg_srv.action = 'POST'
|
||||
reg_srv
|
||||
end
|
||||
|
||||
def capabilities
|
||||
['basic-lti-launch-request']
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,35 @@
|
|||
#
|
||||
# Copyright (C) 2014 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 Lti
|
||||
class ToolProxy < ActiveRecord::Base
|
||||
|
||||
attr_accessible :shared_secret, :guid, :product_version, :lti_version, :product_family, :root_account, :workflow_state, :raw_data
|
||||
|
||||
has_many :bindings, class_name: 'Lti::ToolProxyBinding'
|
||||
has_many :resources, class_name: 'Lti::ResourceHandler'
|
||||
belongs_to :root_account, class_name: 'Account'
|
||||
belongs_to :product_family, class_name: 'Lti::ProductFamily'
|
||||
|
||||
serialize :raw_data
|
||||
|
||||
validates_presence_of :shared_secret, :guid, :product_version, :lti_version, :product_family_id, :root_account_id, :workflow_state, :raw_data
|
||||
validates_uniqueness_of :guid
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,32 @@
|
|||
# Copyright (C) 2014 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 Lti
|
||||
class ToolProxyBinding < ActiveRecord::Base
|
||||
|
||||
attr_accessible :context, :tool_proxy
|
||||
|
||||
belongs_to :tool_proxy, class_name: 'Lti::ToolProxy'
|
||||
validates_inclusion_of :context_type, :allow_nil => true, :in => ['Course', 'Account']
|
||||
belongs_to :context, :polymorphic => true
|
||||
|
||||
validates_presence_of :tool_proxy, :context
|
||||
|
||||
after_save :touch_context
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,113 @@
|
|||
#
|
||||
# Copyright (C) 2014 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 Lti
|
||||
class ToolProxyService
|
||||
|
||||
def process_tool_proxy_json(json, context, guid)
|
||||
tp = IMS::LTI::Models::ToolProxy.new.from_json(json)
|
||||
tp.tool_proxy_guid = guid
|
||||
tool_proxy = nil
|
||||
ToolProxy.transaction do
|
||||
product_family = create_product_family(tp, context.root_account)
|
||||
tool_proxy = create_tool_proxy(tp, context.root_account, product_family)
|
||||
process_resources(tp, tool_proxy)
|
||||
create_proxy_binding(tool_proxy, context)
|
||||
end
|
||||
tool_proxy
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_tool_proxy(tp, account, product_family)
|
||||
tool_proxy = ToolProxy.new
|
||||
tool_proxy.product_family = product_family
|
||||
tool_proxy.guid = tp.tool_proxy_guid
|
||||
tool_proxy.shared_secret = tp.security_contract.shared_secret
|
||||
tool_proxy.product_version = tp.tool_profile.product_instance.product_info.product_version
|
||||
tool_proxy.lti_version = tp.tool_profile.lti_version
|
||||
tool_proxy.root_account = account.root_account
|
||||
tool_proxy.workflow_state = 'disabled'
|
||||
tool_proxy.raw_data = tp.as_json
|
||||
tool_proxy.save!
|
||||
tool_proxy
|
||||
end
|
||||
|
||||
def create_product_family(tp, account)
|
||||
vendor_code = tp.tool_profile.product_instance.product_info.product_family.vendor.code
|
||||
product_code = tp.tool_profile.product_instance.product_info.product_family.code
|
||||
unless product_family = ProductFamily.where(vendor_code: vendor_code, product_code: product_code).first
|
||||
product_family = ProductFamily.new
|
||||
product_family.vendor_code = vendor_code
|
||||
product_family.product_code = product_code
|
||||
product_family.vendor_name = tp.tool_profile.product_instance.product_info.product_family.vendor.default_name
|
||||
product_family.vendor_description = tp.tool_profile.product_instance.product_info.product_family.vendor.default_description
|
||||
product_family.website = tp.tool_profile.product_instance.product_info.product_family.vendor.website
|
||||
product_family.vendor_email = tp.tool_profile.product_instance.product_info.product_family.vendor.contact.email
|
||||
product_family.root_account = account.root_account
|
||||
product_family.save!
|
||||
end
|
||||
product_family
|
||||
end
|
||||
|
||||
def create_message_handler(mh, base_path, resource)
|
||||
message_handler = MessageHandler.new
|
||||
message_handler.message_type = mh.message_type
|
||||
message_handler.launch_path = "#{base_path}#{mh.path}"
|
||||
message_handler.capabilities = create_json(mh.enabled_capability)
|
||||
message_handler.parameters = create_json(mh.parameter.as_json)
|
||||
message_handler.resource = resource
|
||||
message_handler.save!
|
||||
message_handler
|
||||
end
|
||||
|
||||
def create_resource_handler(rh, tool_proxy)
|
||||
resource_handler = ResourceHandler.new
|
||||
resource_handler.resource_type_code = rh.resource_type.code
|
||||
resource_handler.name = rh.default_name
|
||||
resource_handler.description = rh.default_description
|
||||
resource_handler.icon_info = create_json(rh.icon_info)
|
||||
resource_handler.tool_proxy = tool_proxy
|
||||
resource_handler.save!
|
||||
resource_handler
|
||||
end
|
||||
|
||||
def create_proxy_binding(tool_proxy, context)
|
||||
binding = ToolProxyBinding.new
|
||||
binding.context = context
|
||||
binding.tool_proxy = tool_proxy
|
||||
binding.save!
|
||||
binding
|
||||
end
|
||||
|
||||
def process_resources(tp, tool_proxy)
|
||||
tp.tool_profile.resource_handler.each do |rh|
|
||||
resource = create_resource_handler(rh, tool_proxy)
|
||||
rh.message.each do |mh|
|
||||
create_message_handler(mh, tp.tool_profile.base_message_url, resource)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_json(obj)
|
||||
obj.kind_of?(Array) ? obj.map(&:as_json) : obj.as_json
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
<% content_for :page_title do %>
|
||||
<%= @lti_launch.link_text %>
|
||||
<% end %>
|
||||
|
||||
<% js_bundle 'legacy/external_tools_tool_show' %>
|
||||
|
||||
<% if @lti_launch.resource_url.start_with?('http://') %>
|
||||
<div class="alert alert-error" id="insecure_content_msg">
|
||||
<p>You are trying to launch insecure content from within a secure site (canvas). Some internet browsers may
|
||||
prevent this content from loading.</p>
|
||||
|
||||
<p id="load_failure" class="hide">It looks like your content might not load. You can use this button to try
|
||||
launching it in a new tab.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<form action="<%= @lti_launch.resource_url %>"
|
||||
class="hide"
|
||||
method="POST" target=tool_content
|
||||
id="tool_form"
|
||||
data-tool-launch-type="<%= @lti_launch.launch_type %>"
|
||||
data-tool-id="<%= @lti_launch.analytics_id %>"
|
||||
data-tool-path="<%= @lti_launch.resource_path %>"
|
||||
data-message-type="<%= @lti_launch.message_type %>">
|
||||
<% @lti_launch.params.each do |key, value| %>
|
||||
<%= hidden_field_tag key, value %>
|
||||
<% end %>
|
||||
<div style="margin-bottom: 20px;">
|
||||
<div class="load_tab">
|
||||
<%= t :new_tab, "This tool needs to be loaded in a new browser window" %>
|
||||
<div style="margin: 10px 0;">
|
||||
<button class="btn" type="submit"
|
||||
data-expired_message="<%= t :new_tab_expired, "The session for this tool has expired. Please reload the page to access the tool again" %>">
|
||||
<%= t :load_tool_new_tab_button, "Load %{tool} in a new window", :tool => @lti_launch.link_text %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab_loaded" style="display: none;">
|
||||
<%= t :new_tab_loaded, "This tool was successfully loaded in a new browser window. Reload the page to access the tool again." %>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<%= iframe("about:blank", :name => 'tool_content', :id => 'tool_content', :width => '100%', :height => '400') %>
|
|
@ -0,0 +1 @@
|
|||
<%= render 'lti/lti_message' %>
|
|
@ -289,6 +289,7 @@ routes.draw do
|
|||
match 'homework_submission' => 'external_tools#homework_submission', :as => :homework_submission
|
||||
match 'finished' => 'external_tools#finished', :as => :finished
|
||||
collection do
|
||||
get :tool_proxy_registration, controller: 'lti/tool_proxy', action: 'register', :as => :tool_proxy_registration
|
||||
get :retrieve
|
||||
get :homework_submissions
|
||||
end
|
||||
|
@ -551,6 +552,9 @@ routes.draw do
|
|||
resources :external_tools do
|
||||
match 'finished' => 'external_tools#finished', :as => :finished
|
||||
match 'resource_selection' => 'external_tools#resource_selection', :as => :resource_selection
|
||||
collection do
|
||||
get :tool_proxy_registration, controller: 'lti/tool_proxy', action: 'register', :as => :tool_proxy_registration
|
||||
end
|
||||
end
|
||||
|
||||
match 'outcomes/users/:user_id' => 'outcomes#user_outcome_results', :as => :user_outcomes_results
|
||||
|
@ -1567,5 +1571,14 @@ routes.draw do
|
|||
post "tools/:tool_id/ext_grade_passback", :controller => :lti_api, :action => :legacy_grade_passback, :path_name => "blti_legacy_grade_passback_api"
|
||||
end
|
||||
|
||||
ApiRouteSet.draw(self, "/api/lti") do
|
||||
['course', 'account'].each do |context|
|
||||
prefix = "#{context}s/:#{context}_id"
|
||||
get "#{prefix}/tool_consumer_profile/:tool_consumer_profile_id", controller: 'lti/tool_consumer_profile', action: 'show', :as => "#{context}_tool_consumer_profile"
|
||||
post "#{prefix}/tool_proxy", :controller => 'lti/tool_proxy', :action => :create, :path_name => "create_#{context}_lti_tool_proxy"
|
||||
end
|
||||
get "tool_proxy/:tool_proxy_guid", :controller => 'lti/tool_proxy', :action => :show, :path_name => "show_lti_tool_proxy"
|
||||
end
|
||||
|
||||
match '/assets/:package.:extension' => 'jammit#package', :as => :jammit if defined?(Jammit)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
class AddLti2Tables < ActiveRecord::Migration
|
||||
tag :predeploy
|
||||
|
||||
def self.up
|
||||
|
||||
create_table :lti_product_families do |t|
|
||||
t.string :vendor_code, null: false
|
||||
t.string :product_code, null: false
|
||||
t.string :vendor_name, null: false
|
||||
t.text :vendor_description
|
||||
t.string :website
|
||||
t.string :vendor_email
|
||||
t.integer :root_account_id, limit: 8, null: false
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :lti_message_handlers do |t|
|
||||
t.string :message_type, null: false
|
||||
t.string :launch_path, null: false
|
||||
t.text :capabilities
|
||||
t.text :parameters
|
||||
t.integer :resource_handler_id, limit: 8, null: false
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :lti_resource_handlers do |t|
|
||||
t.string :resource_type_code, null: false
|
||||
t.string :placements
|
||||
t.string :name, null: false
|
||||
t.text :description
|
||||
t.text :icon_info
|
||||
t.integer :tool_proxy_id, limit: 8, null: false
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :lti_resource_placements do |t|
|
||||
t.integer :resource_handler_id, limit: 8, null: false
|
||||
t.string :placement, null: false
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :lti_tool_proxies do |t|
|
||||
t.string :shared_secret, null: false
|
||||
t.string :guid, null: false
|
||||
t.string :product_version, null: false
|
||||
t.string :lti_version, null: false
|
||||
t.integer :product_family_id, limit: 8, null: false
|
||||
t.integer :root_account_id, limit: 8, null: false
|
||||
t.string :workflow_state, null: false
|
||||
t.text :raw_data, null: false
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :lti_tool_proxy_bindings do |t|
|
||||
t.integer :context_id, limit: 8, null: false
|
||||
t.string :context_type, null: false
|
||||
t.integer :tool_proxy_id, limit:8, null: false
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :lti_product_families, [:root_account_id, :vendor_code, :product_code], name: 'index_lti_product_families_on_root_account_vend_code_prod_code', unique: true
|
||||
add_index :lti_message_handlers, [:resource_handler_id, :message_type], name: 'index_lti_message_handlers_on_resource_handler_and_type', unique: true
|
||||
add_index :lti_resource_handlers, [:tool_proxy_id, :resource_type_code], name: 'index_lti_resource_handlers_on_tool_proxy_and_type_code', unique: true
|
||||
add_index :lti_resource_placements, [:placement, :resource_handler_id], name: 'index_lti_resource_placements_on_placement_and_handler', unique: true
|
||||
add_index :lti_tool_proxies, [:root_account_id, :product_family_id, :product_version], name: 'index_lti_tool_proxies_on_root_account_prod_fam_and_prod_ver', unique: true
|
||||
add_index :lti_tool_proxy_bindings, [:context_id, :context_type, :tool_proxy_id], name: 'index_lti_tool_proxy_bindings_on_context_and_tool_proxy', unique: true
|
||||
add_index :lti_tool_proxies, [:guid]
|
||||
|
||||
add_foreign_key :lti_product_families, :accounts, column: :root_account_id
|
||||
add_foreign_key :lti_message_handlers, :lti_resource_handlers, column: :resource_handler_id
|
||||
add_foreign_key :lti_resource_handlers, :lti_tool_proxies, column: :tool_proxy_id
|
||||
add_foreign_key :lti_resource_placements, :lti_resource_handlers, column: :resource_handler_id
|
||||
add_foreign_key :lti_tool_proxies, :lti_product_families, column: :product_family_id
|
||||
add_foreign_key :lti_tool_proxies, :accounts, column: :root_account_id
|
||||
add_foreign_key :lti_tool_proxy_bindings, :lti_tool_proxies, column: :tool_proxy_id
|
||||
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :lti_tool_proxy_bindings
|
||||
drop_table :lti_message_handlers
|
||||
drop_table :lti_resource_placements
|
||||
drop_table :lti_resource_handlers
|
||||
drop_table :lti_tool_proxies
|
||||
drop_table :lti_product_families
|
||||
end
|
||||
|
||||
end
|
|
@ -77,7 +77,8 @@ switch($toolForm.data('tool-launch-type')){
|
|||
//Google analytics tracking code
|
||||
var toolName = $toolForm.data('tool-id') || "unknown";
|
||||
var toolPath = $toolForm.data('tool-path');
|
||||
$.trackEvent('tool_launch', toolName, toolPath);
|
||||
var messageType = $toolForm.data('message-type') || 'tool_launch';
|
||||
$.trackEvent(messageType, toolName, toolPath);
|
||||
|
||||
//Iframe resize handler
|
||||
$(document).ready(function() {
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
#
|
||||
# Copyright (C) 2014 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__) + '/../api_spec_helper')
|
||||
|
||||
module Lti
|
||||
describe ToolConsumerProfileController, type: :request do
|
||||
|
||||
describe "GET 'tool_consumer_profile'" do
|
||||
|
||||
let(:account) { Account.create! }
|
||||
|
||||
it 'renders "application/json"' do
|
||||
tool_consumer_profile_id = 'a_made_up_id'
|
||||
get "/api/lti/accounts/#{account.id}/tool_consumer_profile/#{tool_consumer_profile_id}", tool_consumer_profile_id: tool_consumer_profile_id, account_id: account.id
|
||||
response.content_type.should == 'application/json'
|
||||
end
|
||||
|
||||
it 'returns the consumer profile JSON' do
|
||||
tool_consumer_profile_id = 'a_made_up_id'
|
||||
get "/api/lti/accounts/#{account.id}/tool_consumer_profile/#{tool_consumer_profile_id}", tool_consumer_profile_id: tool_consumer_profile_id, account_id: account.id
|
||||
profile = IMS::LTI::Models::ToolConsumerProfile.new.from_json(response.body)
|
||||
profile.type.should == 'ToolConsumerProfile'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,68 @@
|
|||
#
|
||||
# Copyright (C) 2014 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__) + '/../api_spec_helper')
|
||||
|
||||
module Lti
|
||||
describe ToolProxyController, type: :request do
|
||||
|
||||
let(:account) { Account.new }
|
||||
let (:product_family) {ProductFamily.create(vendor_code: '123', product_code:'abc', vendor_name:'acme', root_account:account)}
|
||||
let(:tool_proxy) do
|
||||
ToolProxy.create(
|
||||
guid: SecureRandom.uuid,
|
||||
shared_secret: 'abc',
|
||||
product_family: product_family,
|
||||
root_account: account,
|
||||
product_version: '1',
|
||||
workflow_state: 'disabled',
|
||||
raw_data: {'proxy' => 'value'},
|
||||
lti_version: '1'
|
||||
)
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
OAuth::Signature.stubs(:build).returns(mock(verify:true))
|
||||
OAuth::Helper.stubs(:parse_header).returns({'oauth_consumer_key' => 'key'})
|
||||
end
|
||||
|
||||
describe "Get #show" do
|
||||
it 'the tool proxy raw data' do
|
||||
get "api/lti/tool_proxy/#{tool_proxy.guid}", tool_proxy_guid: tool_proxy.guid
|
||||
JSON.parse(body).should == tool_proxy.raw_data
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST #create" do
|
||||
it 'returns a tool_proxy id object' do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
tool_proxy_fixture = File.read(File.join(Rails.root, 'spec', 'fixtures', 'lti', 'tool_proxy.json'))
|
||||
json = JSON.parse(tool_proxy_fixture)
|
||||
json[:format] = 'json'
|
||||
json[:account_id] = @course.account.id
|
||||
headers = { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
|
||||
response = post "/api/lti/accounts/#{@course.account.id}/tool_proxy.json", tool_proxy_fixture, headers
|
||||
response.should == 201
|
||||
JSON.parse(body).keys.should =~ ["@context", "@type", "@id", "tool_proxy_guid"]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,64 @@
|
|||
#
|
||||
# Copyright (C) 2014 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')
|
||||
|
||||
module Lti
|
||||
describe ToolProxyController do
|
||||
|
||||
describe "GET #register" do
|
||||
context 'course' do
|
||||
it 'initiates a tool proxy registration request' do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
get 'register', course_id: @course.id, tool_consumer_url: 'http://tool.consumer.url'
|
||||
lti_launch = assigns[:lti_launch]
|
||||
lti_launch.resource_url.should == 'http://tool.consumer.url'
|
||||
launch_params = lti_launch.params
|
||||
launch_params['lti_message_type'].should == 'ToolProxyRegistrationRequest'
|
||||
launch_params['lti_version'].should == 'LTI-2p0'
|
||||
launch_params['launch_presentation_document_target'].should == 'iframe'
|
||||
launch_params['reg_key'].should_not be_empty
|
||||
launch_params['reg_password'].should_not be_empty
|
||||
|
||||
account_tp_url_stub = course_tool_consumer_profile_url(@course, 'abc123').gsub('abc123', '')
|
||||
launch_params['tc_profile_url'].should include(account_tp_url_stub)
|
||||
end
|
||||
end
|
||||
|
||||
context 'account' do
|
||||
it 'initiates a tool proxy registration request' do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
get 'register', account_id: @course.root_account.id, tool_consumer_url: 'http://tool.consumer.url'
|
||||
lti_launch = assigns[:lti_launch]
|
||||
lti_launch.resource_url.should == 'http://tool.consumer.url'
|
||||
launch_params = lti_launch.params
|
||||
launch_params['lti_message_type'].should == 'ToolProxyRegistrationRequest'
|
||||
launch_params['lti_version'].should == 'LTI-2p0'
|
||||
launch_params['launch_presentation_document_target'].should == 'iframe'
|
||||
launch_params['reg_key'].should_not be_empty
|
||||
launch_params['reg_password'].should_not be_empty
|
||||
account_tp_url_stub = account_tool_consumer_profile_url(@course.root_account, 'abc123').gsub('abc123', '')
|
||||
launch_params['tc_profile_url'].should include(account_tp_url_stub)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,176 @@
|
|||
{
|
||||
"@context": [
|
||||
"http://purl.imsglobal.org/ctx/lti/v2/ToolProxy",
|
||||
"http://purl.org/blackboard/ctx/v1/iconStyle"
|
||||
],
|
||||
"@type": "ToolProxy",
|
||||
"@id": "http://lms.example.com/ToolProxy/869e5ce5-214c-4e85-86c6-b99e8458a592",
|
||||
"lti_version": "LTI-2p0",
|
||||
"tool_proxy_guid": "869e5ce5-214c-4e85-86c6-b99e8458a592",
|
||||
"tool_consumer_profile": "http://lms.example.com/profile/b6ffa601-ce1d-4549-9ccf-145670a964d4",
|
||||
"tool_profile": {
|
||||
"lti_version": "LTI-2p0",
|
||||
"product_instance": {
|
||||
"guid": "fd75124a-140e-470f-944c-114d2d92bb40",
|
||||
"product_info": {
|
||||
"product_name": {
|
||||
"default_value": "Acme Assessments",
|
||||
"key": "tool.name"
|
||||
},
|
||||
"description": {
|
||||
"default_value": "Acme Assessments provide an interactive test format.",
|
||||
"key": "tool.description"
|
||||
},
|
||||
"product_version": "10.3",
|
||||
"technical_description": {
|
||||
"default_value": "Support provided for all LTI 1 extensions as well as LTI 2",
|
||||
"key": "tool.technical"
|
||||
},
|
||||
"product_family": {
|
||||
"@id": "http://toolprovider.example.com/vendor/acme.com/product/assessment-tool",
|
||||
"code": "assessment-tool",
|
||||
"vendor": {
|
||||
"code": "acme.com",
|
||||
"vendor_name": {
|
||||
"default_value": "Acme",
|
||||
"key": "tool.vendor.name"
|
||||
},
|
||||
"description": {
|
||||
"default_value": "Acme is a leading provider of interactive tools for education",
|
||||
"key": "tool.vendor.description"
|
||||
},
|
||||
"website": "http://acme.example.com",
|
||||
"timestamp": "2012-04-05T09:08:16-04:00",
|
||||
"contact": {
|
||||
"email": "info@example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"support": {
|
||||
"email": "helpdesk@example.com"
|
||||
},
|
||||
"service_provider": {
|
||||
"guid": "18e7ea50-3d6d-4f6b-aff2-ed3ab577716c",
|
||||
"service_provider_name": {
|
||||
"default_value": "Acme Hosting",
|
||||
"key": "service_provider.name"
|
||||
},
|
||||
"description": {
|
||||
"default_value": "Provider of high performance managed hosting environments",
|
||||
"key": "service_provider.description"
|
||||
},
|
||||
"support": {
|
||||
"email": "support@acme-hosting.example.com"
|
||||
},
|
||||
"timestamp": "2012-04-05T09:08:16-04:00"
|
||||
},
|
||||
"service_owner": {
|
||||
"service_owner_name": {
|
||||
"default_value": "Acme Institution",
|
||||
"key": "service_owner.name"
|
||||
},
|
||||
"description": {
|
||||
"default_value": "Provider of high quality education",
|
||||
"key": "service_owner.description"
|
||||
},
|
||||
"timestamp": "2012-04-05T09:08:16-04:00"
|
||||
}
|
||||
},
|
||||
"base_url_choice": [
|
||||
{
|
||||
"default_base_url": "http://acme.example.com/",
|
||||
"secure_base_url": "https://acme.example.com/",
|
||||
"selector": {
|
||||
"applies_to": ["IconEndpoint", "MessageHandler"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"resource_handler": [
|
||||
{
|
||||
"resource_type": {
|
||||
"code": "asmt"
|
||||
},
|
||||
"resource_name": {
|
||||
"default_value": "Acme Assessment",
|
||||
"key": "assessment.resource.name"
|
||||
},
|
||||
"description": {
|
||||
"default_value": "An interactive assessment using the Acme scale.",
|
||||
"key": "assessment.resource.description"
|
||||
},
|
||||
"message": [
|
||||
{
|
||||
"message_type": "basic-lti-launch-request",
|
||||
"path": "handler/launchRequest",
|
||||
"enabled_capability": [
|
||||
"Result.autocreate"
|
||||
],
|
||||
"parameter": [
|
||||
{
|
||||
"name": "result_url",
|
||||
"variable": "Result.url"
|
||||
},
|
||||
{
|
||||
"name": "discipline",
|
||||
"fixed": "chemistry"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"icon_info": [
|
||||
{
|
||||
"default_location": {
|
||||
"path": "images/bb/en/icon.png"
|
||||
},
|
||||
"key": "iconStyle.default.path"
|
||||
},
|
||||
{
|
||||
"icon_style": ["BbListElementIcon"],
|
||||
"default_location": {
|
||||
"path": "images/bb/en/listElement.png"
|
||||
},
|
||||
"key": "iconStyle.bb.listElement.path"
|
||||
},
|
||||
{
|
||||
"icon_style": ["BbPushButtonIcon"],
|
||||
"default_location": {
|
||||
"path": "images/bb/en/pushButton.png"
|
||||
},
|
||||
"key": "iconStyle.bb.pushButton.path"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"custom": {
|
||||
"customerId": "394892759526"
|
||||
},
|
||||
"security_contract": {
|
||||
"shared_secret": "ThisIsASecret!",
|
||||
"tool_service": [
|
||||
{
|
||||
"@type": "RestServiceProfile",
|
||||
"service": "http://lms.example.com/profile/b6ffa601-ce1d-4549-9ccf-145670a964d4#ToolProxy.collection",
|
||||
"action": ["POST"]
|
||||
},
|
||||
{
|
||||
"@type": "RestServiceProfile",
|
||||
"service": "http://lms.example.com/profile/b6ffa601-ce1d-4549-9ccf-145670a964d4#ToolProxy.item",
|
||||
"action": ["GET", "PUT"]
|
||||
},
|
||||
{
|
||||
"@type": "RestServiceProfile",
|
||||
"service": "http://lms.example.com/profile/b6ffa601-ce1d-4549-9ccf-145670a964d4#Result.item",
|
||||
"action": ["GET", "PUT"]
|
||||
}
|
||||
],
|
||||
"end_user_service": [
|
||||
{
|
||||
"@type": "RestServiceProfile",
|
||||
"service": "http://lms.example.com/profile/b6ffa601-ce1d-4549-9ccf-145670a964d4#Result.item",
|
||||
"action": ["PUT"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
#
|
||||
# Copyright (C) 2014 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')
|
||||
|
||||
module Lti
|
||||
describe MessageHandler do
|
||||
|
||||
describe 'validations' do
|
||||
before(:each) do
|
||||
subject.message_type = 'message_type'
|
||||
subject.launch_path = 'launch_path'
|
||||
subject.resource = ResourceHandler.new
|
||||
end
|
||||
|
||||
it 'requires the message type' do
|
||||
subject.message_type = nil
|
||||
subject.save
|
||||
subject.errors.first.should == [:message_type, "can't be blank"]
|
||||
end
|
||||
|
||||
it 'requires the launch path' do
|
||||
subject.launch_path = nil
|
||||
subject.save
|
||||
subject.errors.first.should == [:launch_path, "can't be blank"]
|
||||
end
|
||||
|
||||
it 'requires a resource_handler' do
|
||||
subject.resource = nil
|
||||
subject.save
|
||||
subject.errors.first.should == [:resource, "can't be blank"]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,61 @@
|
|||
#
|
||||
# Copyright (C) 2014 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')
|
||||
|
||||
module Lti
|
||||
describe ProductFamily do
|
||||
let(:account){Account.new}
|
||||
|
||||
describe 'validations' do
|
||||
|
||||
before(:each) do
|
||||
subject.vendor_code = 'vendor_code'
|
||||
subject.product_code = 'product_code'
|
||||
subject.vendor_name = 'vendor_name'
|
||||
subject.root_account_id = account
|
||||
end
|
||||
|
||||
it 'requires a vendor_code' do
|
||||
subject.vendor_code = nil
|
||||
subject.save
|
||||
subject.errors.first.should == [:vendor_code, "can't be blank"]
|
||||
end
|
||||
|
||||
it 'requires a product_code' do
|
||||
subject.product_code = nil
|
||||
subject.save
|
||||
subject.errors.first.should == [:product_code, "can't be blank"]
|
||||
end
|
||||
|
||||
it 'requires a vendor_name' do
|
||||
subject.vendor_name = nil
|
||||
subject.save
|
||||
subject.errors.first.should == [:vendor_name, "can't be blank"]
|
||||
end
|
||||
|
||||
it 'requires a root_account' do
|
||||
subject.root_account = nil
|
||||
subject.save
|
||||
subject.errors.first.should == [:root_account, "can't be blank"]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,34 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Lti
|
||||
describe RegistrationRequestService do
|
||||
|
||||
describe '#create' do
|
||||
|
||||
it 'creates a RegistrationRequest' do
|
||||
enable_cache do
|
||||
reg_request = described_class.create_request('profile_url', 'return_url')
|
||||
reg_request.lti_version.should == 'LTI-2p0'
|
||||
reg_request.launch_presentation_document_target.should == 'iframe'
|
||||
reg_request.tc_profile_url.should == 'profile_url'
|
||||
end
|
||||
end
|
||||
|
||||
it 'writes the reg password to the cache' do
|
||||
enable_cache do
|
||||
IMS::LTI::Models::Messages::RegistrationRequest.any_instance.expects(:generate_key_and_password).
|
||||
returns(['key', 'password'])
|
||||
Rails.cache.expects(:write).with('lti_registration_request/key', 'password', anything)
|
||||
|
||||
described_class.create_request('profile_url', 'return_url')
|
||||
end
|
||||
end
|
||||
|
||||
it 'generates the cache key' do
|
||||
described_class.req_cache_key('reg_key').should == 'lti_registration_request/reg_key'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,47 @@
|
|||
#
|
||||
# Copyright (C) 2014 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')
|
||||
|
||||
module Lti
|
||||
describe ResourceHandler do
|
||||
|
||||
describe 'validations' do
|
||||
before(:each) do
|
||||
subject.resource_type_code = 'code'
|
||||
subject.name = 'name'
|
||||
subject.tool_proxy = ToolProxy.new
|
||||
|
||||
end
|
||||
|
||||
it 'requires the name' do
|
||||
subject.name = nil
|
||||
subject.save
|
||||
subject.errors.first.should == [:name, "can't be blank"]
|
||||
end
|
||||
|
||||
it 'requires a tool proxy' do
|
||||
subject.tool_proxy = nil
|
||||
subject.save
|
||||
subject.errors.first.should == [:tool_proxy, "can't be blank"]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,34 @@
|
|||
#
|
||||
# Copyright (C) 2014 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')
|
||||
|
||||
module Lti
|
||||
describe ResourcePlacement do
|
||||
|
||||
describe 'validations' do
|
||||
|
||||
it 'requires a resource' do
|
||||
subject.save
|
||||
subject.errors.first.should == [:resource, "can't be blank"]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,65 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Lti
|
||||
describe ToolConsumerProfileCreator do
|
||||
|
||||
let(:root_account) { mock('root account', lti_guid: 'my_guid') }
|
||||
let(:account) { mock('account', root_account: root_account) }
|
||||
# let(:root_account) {mock('root account').stubs(:lti_guid).returns('my_guid')}
|
||||
# let(:account) {mock('account').stubs(:root_account).returns(root_account)}
|
||||
subject { ToolConsumerProfileCreator.new(account, 'http://tool-consumer.com/tp/reg') }
|
||||
|
||||
describe '#create' do
|
||||
|
||||
it 'creates the tool consumer profile' do
|
||||
profile = subject.create
|
||||
profile.lti_version.should == 'LTI-2p0'
|
||||
profile.product_instance.should be_an_instance_of IMS::LTI::Models::ProductInstance
|
||||
end
|
||||
|
||||
it 'creates the product instance' do
|
||||
product_instance = subject.create.product_instance
|
||||
product_instance.guid.should == 'my_guid'
|
||||
product_instance.product_info.should be_an IMS::LTI::Models::ProductInfo
|
||||
|
||||
end
|
||||
|
||||
it 'creates the product info' do
|
||||
product_info = subject.create.product_instance.product_info
|
||||
product_info.product_name.default_value.should == 'Canvas by Instructure'
|
||||
product_info.product_version.should == 'none'
|
||||
product_info.product_family.should be_a IMS::LTI::Models::ProductFamily
|
||||
end
|
||||
|
||||
it 'creates the product family' do
|
||||
product_family = subject.create.product_instance.product_info.product_family
|
||||
product_family.code.should == 'canvas'
|
||||
product_family.vendor.should be_a IMS::LTI::Models::Vendor
|
||||
|
||||
end
|
||||
|
||||
it 'creates the vendor' do
|
||||
vendor = subject.create.product_instance.product_info.product_family.vendor
|
||||
vendor.code.should == 'https://instructure.com'
|
||||
vendor.vendor_name.default_value.should == 'Instructure'
|
||||
vendor.vendor_name.key.should == 'vendor.name'
|
||||
vendor.timestamp.to_i.should == Time.parse('2008-03-27 00:00:00 -0600').to_i
|
||||
end
|
||||
|
||||
it 'creates the registration service' do
|
||||
profile = subject.create
|
||||
reg_srv = profile.service_offered.find {|srv| srv.id == 'tcp:ToolProxy.collection'}
|
||||
reg_srv.endpoint.should == 'http://tool-consumer.com/tp/reg'
|
||||
reg_srv.type.should == 'RestService'
|
||||
reg_srv.format.should == ["application/vnd.ims.lti.v2.toolproxy+json"]
|
||||
reg_srv.action.should include 'POST'
|
||||
|
||||
end
|
||||
|
||||
it 'add the basic_launch capability' do
|
||||
subject.create.capability_offered.should include 'basic-lti-launch-request'
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,49 @@
|
|||
#
|
||||
# Copyright (C) 2014 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')
|
||||
|
||||
module Lti
|
||||
describe ToolProxyBinding do
|
||||
|
||||
let(:account) {Account.new}
|
||||
let(:tool_proxy) {ToolProxy.new}
|
||||
|
||||
describe 'validations' do
|
||||
|
||||
before(:each) do
|
||||
subject.context = account
|
||||
subject.tool_proxy = tool_proxy
|
||||
end
|
||||
|
||||
it 'requires a context' do
|
||||
subject.context = nil
|
||||
subject.save
|
||||
subject.errors.first.should == [:context, "can't be blank"]
|
||||
end
|
||||
|
||||
it 'requires a tool_proxy' do
|
||||
subject.tool_proxy = nil
|
||||
subject.save
|
||||
subject.errors.first.should == [:tool_proxy, "can't be blank"]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,108 @@
|
|||
#
|
||||
# Copyright (C) 2014 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')
|
||||
|
||||
module Lti
|
||||
describe ToolProxyService do
|
||||
|
||||
describe '#process_tool_proxy_json' do
|
||||
|
||||
let(:tool_proxy_fixture){File.read(File.join(Rails.root, 'spec', 'fixtures', 'lti', 'tool_proxy.json'))}
|
||||
let(:tool_proxy_guid){'guid'}
|
||||
let(:account){Account.new}
|
||||
|
||||
|
||||
it "creates the product_family if it doesn't exist" do
|
||||
tool_proxy = subject.process_tool_proxy_json(tool_proxy_fixture, account, tool_proxy_guid)
|
||||
pf = tool_proxy.product_family
|
||||
pf.vendor_code.should == 'acme.com'
|
||||
pf.product_code.should == 'assessment-tool'
|
||||
pf.vendor_name.should == 'Acme'
|
||||
pf.website.should == 'http://acme.example.com'
|
||||
pf.vendor_description.should == 'Acme is a leading provider of interactive tools for education'
|
||||
pf.vendor_email.should == 'info@example.com'
|
||||
pf.root_account.should == account
|
||||
end
|
||||
|
||||
it "uses an exisiting product family if it can" do
|
||||
pf = ProductFamily.new
|
||||
pf.vendor_code = 'acme.com'
|
||||
pf.product_code = 'assessment-tool'
|
||||
pf.vendor_name = 'Acme'
|
||||
pf.root_account = account.root_account
|
||||
pf.save!
|
||||
tool_proxy = subject.process_tool_proxy_json(tool_proxy_fixture, account, tool_proxy_guid)
|
||||
tool_proxy.product_family.id.should == pf.id
|
||||
end
|
||||
|
||||
it "creates the resource handler" do
|
||||
tool_proxy = subject.process_tool_proxy_json(tool_proxy_fixture, account, tool_proxy_guid)
|
||||
rh = tool_proxy.resources.first
|
||||
rh.resource_type_code.should == 'asmt'
|
||||
rh.name.should == 'Acme Assessment'
|
||||
rh.description.should == 'An interactive assessment using the Acme scale.'
|
||||
rh.icon_info.should == [
|
||||
{
|
||||
'default_location' => {'path' => 'images/bb/en/icon.png'},
|
||||
'key' => 'iconStyle.default.path'
|
||||
},
|
||||
{
|
||||
'icon_style' => ['BbListElementIcon'],
|
||||
'default_location' => {'path' => 'images/bb/en/listElement.png'},
|
||||
'key' => 'iconStyle.bb.listElement.path'
|
||||
},
|
||||
{
|
||||
'icon_style' => ['BbPushButtonIcon'],
|
||||
'default_location' => {'path' => 'images/bb/en/pushButton.png'},
|
||||
'key' => 'iconStyle.bb.pushButton.path'
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
it "creates the message_handlers" do
|
||||
tool_proxy = subject.process_tool_proxy_json(tool_proxy_fixture, account, tool_proxy_guid)
|
||||
mh = tool_proxy.resources.first.message_handlers.first
|
||||
mh.message_type.should == 'basic-lti-launch-request'
|
||||
mh.launch_path.should == 'https://acme.example.com/handler/launchRequest'
|
||||
mh.capabilities.should == [ "Result.autocreate" ]
|
||||
mh.parameters.should == [{'name' => 'result_url', 'variable' => 'Result.url'}, {'name' => 'discipline', 'fixed' => 'chemistry'}]
|
||||
end
|
||||
|
||||
it 'creates a tool proxy biding' do
|
||||
tool_proxy = subject.process_tool_proxy_json(tool_proxy_fixture, account, tool_proxy_guid)
|
||||
tool_proxy.bindings.count.should == 1
|
||||
binding = tool_proxy.bindings.first
|
||||
binding.context.should == account
|
||||
end
|
||||
|
||||
it 'creates a tool_proxy' do
|
||||
SecureRandom.stubs(:uuid).returns('my_uuid')
|
||||
tool_proxy = subject.process_tool_proxy_json(tool_proxy_fixture, account, tool_proxy_guid)
|
||||
tool_proxy.shared_secret.should == 'ThisIsASecret!'
|
||||
tool_proxy.guid.should == tool_proxy_guid
|
||||
tool_proxy.product_version.should == '10.3'
|
||||
tool_proxy.lti_version.should == 'LTI-2p0'
|
||||
tool_proxy.root_account.should == account
|
||||
tool_proxy.workflow_state.should == 'disabled'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,108 @@
|
|||
#
|
||||
# Copyright (C) 2014 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')
|
||||
|
||||
module Lti
|
||||
describe ToolProxy do
|
||||
let (:account) {Account.new}
|
||||
let (:product_family) {ProductFamily.create(vendor_code: '123', product_code:'abc', vendor_name:'acme', root_account:account)}
|
||||
let (:resource_handler) {ResourceHandler.new}
|
||||
|
||||
describe 'validations' do
|
||||
|
||||
before(:each) do
|
||||
subject.shared_secret = 'shared_secret'
|
||||
subject.guid = 'guid'
|
||||
subject.product_version = '1.0beta'
|
||||
subject.lti_version = 'LTI-2p0'
|
||||
subject.product_family = product_family
|
||||
subject.root_account = account
|
||||
subject.workflow_state = 'active'
|
||||
subject.raw_data = 'some raw data'
|
||||
end
|
||||
|
||||
it 'requires a shared_secret' do
|
||||
subject.shared_secret = nil
|
||||
subject.save
|
||||
error = subject.errors.find {|e| e == [:shared_secret, "can't be blank"]}
|
||||
error.should_not == nil
|
||||
end
|
||||
|
||||
it 'requires a guid' do
|
||||
subject.guid = nil
|
||||
subject.save
|
||||
error = subject.errors.find {|e| e == [:guid, "can't be blank"]}
|
||||
error.should_not == nil
|
||||
end
|
||||
|
||||
it 'must have a unique guid' do
|
||||
tool_proxy = described_class.new
|
||||
tool_proxy.shared_secret = 'foo'
|
||||
tool_proxy.guid = 'guid'
|
||||
tool_proxy.product_version = '2.0_beta'
|
||||
tool_proxy.lti_version = 'LTI-2p0'
|
||||
tool_proxy.product_family = product_family
|
||||
tool_proxy.root_account = account
|
||||
tool_proxy.workflow_state = 'active'
|
||||
tool_proxy.raw_data = 'raw_data'
|
||||
tool_proxy.save
|
||||
subject.save
|
||||
subject.errors[:guid].should include("has already been taken")
|
||||
end
|
||||
|
||||
it 'requires a product_version' do
|
||||
subject.product_version = nil
|
||||
subject.save
|
||||
subject.errors[:product_version].should include("can't be blank")
|
||||
end
|
||||
|
||||
it 'requires a lti_version' do
|
||||
subject.lti_version = nil
|
||||
subject.save
|
||||
subject.errors[:lti_version].should include("can't be blank")
|
||||
end
|
||||
|
||||
it 'requires a product_family' do
|
||||
subject.product_family = nil
|
||||
subject.save
|
||||
error = subject.errors.find {|e| e == [:product_family, "can't be blank"]}
|
||||
end
|
||||
|
||||
it 'requires a root_account' do
|
||||
subject.root_account = nil
|
||||
subject.save
|
||||
subject.errors[:root_account_id].should include("can't be blank")
|
||||
end
|
||||
|
||||
it 'require a workflow_state' do
|
||||
subject.workflow_state = nil
|
||||
subject.save
|
||||
subject.errors[:workflow_state].should include("can't be blank")
|
||||
end
|
||||
|
||||
it 'requires raw_data' do
|
||||
subject.raw_data = nil
|
||||
subject.save
|
||||
subject.errors[:raw_data].should include("can't be blank")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue