Add Lti Link model for LTI 2 launches

refs PLAT-2724

Test plan:
* Regression test plagiarism platform

Change-Id: I98fd4efc5f259bd73747337d07a7e2ec2508dbb0
Reviewed-on: https://gerrit.instructure.com/122906
Reviewed-by: Jeremy Neander <jneander@instructure.com>
Reviewed-by: Nathan Mills <nathanm@instructure.com>
Reviewed-by: Weston Dransfield <wdransfield@instructure.com>
Tested-by: Jenkins
Reviewed-by: Cody Cutrer <cody@instructure.com>
Reviewed-by: Keith T. Garner <kgarner@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Product-Review: Andrew Butterfield <abutterfield@instructure.com>
This commit is contained in:
Andrew Butterfield 2017-08-15 17:38:29 -07:00
parent 1be4796345
commit d29aaaf6a1
17 changed files with 536 additions and 210 deletions

View File

@ -81,9 +81,9 @@ module Lti
private :reregistration_message
def resource
tool_setting = ToolSetting.find_by(resource_link_id: params[:resource_link_id])
return not_found if tool_setting.blank?
basic_launch_by_tool_setting(tool_setting)
lti_link = Link.find_by(resource_link_id: params[:resource_link_id])
return not_found if lti_link.blank?
basic_launch_by_lti_link(lti_link)
end
def basic_lti_launch_request
@ -133,26 +133,26 @@ module Lti
message_handler.launch_path
end
def basic_launch_by_tool_setting(tool_setting)
message_handler = tool_setting.message_handler(@context)
def basic_launch_by_lti_link(lti_link)
message_handler = lti_link.message_handler(@context)
if message_handler.present?
return lti2_basic_launch(message_handler, tool_setting.resource_url, tool_setting.resource_link_id)
return lti2_basic_launch(message_handler, lti_link)
end
not_found
rescue InvalidDomain => e
return render json: {errors: {invalid_launch_url: {message: e.message}}}, status: 400
end
def lti2_basic_launch(message_handler, resource_url = nil, resource_link_id = nil)
def lti2_basic_launch(message_handler, lti_link = nil)
resource_handler = message_handler.resource_handler
tool_proxy = resource_handler.tool_proxy
# TODO: create scope for query
if tool_proxy.workflow_state == 'active'
launch_attrs = {
launch_url: launch_url(resource_url, message_handler),
launch_url: launch_url(lti_link&.resource_url, message_handler),
oauth_consumer_key: tool_proxy.guid,
lti_version: IMS::LTI::Models::LTIModel::LTI_VERSION_2P0,
resource_link_id: resource_link_id || message_handler.build_resource_link_id(context: @context,
resource_link_id: lti_link&.resource_link_id || message_handler.build_resource_link_id(context: @context,
link_fragment: params[:resource_link_fragment]),
}
@ -164,9 +164,8 @@ module Lti
custom_param_opts = prep_tool_settings(message_handler.parameters, tool_proxy, launch_attrs[:resource_link_id])
custom_param_opts[:content_tag] = tag if tag
custom_param_opts[:secure_params] = params[:secure_params] if params[:secure_params].present?
tool_setting = ToolSetting.find_by(resource_link_id: resource_link_id)
variable_expander = create_variable_expander(custom_param_opts.merge(tool: tool_proxy,
tool_setting: tool_setting,
originality_report: lti_link&.originality_report,
launch: @lti_launch))
launch_attrs.merge! enabled_parameters(tool_proxy, message_handler, variable_expander)

View File

@ -109,15 +109,12 @@ module Lti
}.freeze
].freeze
rescue_from Lti::Errors::UnauthorizedToolError do |e|
Lti::Errors::ErrorLogger.log_error(e)
render_unauthorized_action
end
skip_before_action :load_user
before_action :authorized_lti2_tool, :plagiarism_feature_flag_enabled
before_action :attachment_in_context, only: [:create]
before_action :report_in_context, only: [:show]
before_action :find_originality_report
before_action :report_in_context, only: [:show, :update]
before_action :ensure_tool_proxy_associated
# @API Create an Originality Report
# Create a new OriginalityReport for the specified file
@ -156,8 +153,8 @@ module Lti
#
# @returns OriginalityReport
def create
raise Lti::Errors::UnauthorizedToolError unless tool_proxy_associated?
if originality_report.present?
begin
if @report.present?
update
else
@report = OriginalityReport.new(create_report_params)
@ -167,6 +164,9 @@ module Lti
render json: @report.errors, status: :bad_request
end
end
rescue StandError => e
puts e.message
end
end
# @API Edit an Originality Report
@ -204,12 +204,10 @@ module Lti
#
# @returns OriginalityReport
def update
report_in_context
raise Lti::Errors::UnauthorizedToolError unless tool_proxy_associated?
if originality_report.update_attributes(update_report_params)
render json: api_json(originality_report, @current_user, session)
if @report.update_attributes(update_report_params)
render json: api_json(@report, @current_user, session)
else
render json: originality_report.errors, status: :bad_request
render json: @report.errors, status: :bad_request
end
end
@ -218,8 +216,7 @@ module Lti
#
# @returns OriginalityReport
def show
raise Lti::Errors::UnauthorizedToolError unless tool_proxy_associated?
render json: api_json(originality_report, @current_user, session)
render json: api_json(@report, @current_user, session)
end
def lti2_service_name
@ -228,19 +225,8 @@ module Lti
private
def link_id(tool_setting_params)
resource_type_code = tool_setting_params&.dig('resource_type_code')
if resource_type_code.present?
rh = tool_proxy.resources.find_by(resource_type_code: resource_type_code)
resource_link_id(rh, tool_setting_params['resource_url'])
end
end
def resource_link_id(resource_handler, lti_url = nil)
tool_setting = resource_handler.find_or_create_tool_setting(resource_url: lti_url,
link_fragment: link_fragment,
context: attachment_association)
tool_setting.resource_link_id
def ensure_tool_proxy_associated
render_unauthorized_action unless tool_proxy_associated?
end
def link_fragment
@ -260,16 +246,18 @@ module Lti
:file_id,
:originality_report_file_id,
:originality_report_url,
:workflow_state,
tool_setting: %i(resource_url resource_type_code)].freeze
:workflow_state].freeze
end
def update_attributes
[:originality_report_file_id,
:originality_report_url,
:originality_score,
:workflow_state,
tool_setting: %i(resource_url resource_type_code)].freeze
:workflow_state].freeze
end
def lti_link_attributes
[tool_setting: %i(resource_url resource_type_code)].freeze
end
def assignment
@ -308,7 +296,7 @@ module Lti
report_attributes = params.require(:originality_report).permit(create_attributes).to_unsafe_h.merge(
{submission_id: params.require(:submission_id)}
)
report_attributes['link_id'] = link_id(report_attributes.delete('tool_setting'))
report_attributes[:lti_link_attributes] = lti_link_params
report_attributes
end
end
@ -316,32 +304,46 @@ module Lti
def update_report_params
@_update_report_params ||= begin
report_attributes = params.require(:originality_report).permit(update_attributes)
report_attributes['link_id'] = link_id(report_attributes.delete('tool_setting'))
report_attributes[:lti_link_attributes] = lti_link_params
report_attributes
end
end
def lti_link_params
@_lti_link_params ||= begin
tool_settings = params.require(:originality_report).permit(lti_link_attributes).to_unsafe_h
if tool_settings&.dig('tool_setting', 'resource_type_code')
tool_settings['tool_setting'].merge({
id: @report&.lti_link&.id,
product_code: tool_proxy.product_family.product_code,
vendor_code: tool_proxy.product_family.vendor_code
})
else
{
id: @report&.lti_link&.id,
_destroy: true
}
end
end
end
def attachment_in_context
verify_submission_attachment(attachment, submission)
end
def originality_report
@_originality_report ||= begin
OriginalityReport.find_by(id: params[:id]) ||
OriginalityReport.find_by(attachment_id: attachment&.id)
end
def find_originality_report
@report = OriginalityReport.find_by(id: params[:id]) || OriginalityReport.find_by(attachment_id: attachment&.id)
end
def report_in_context
raise ActiveRecord::RecordNotFound if originality_report.blank?
verify_submission_attachment(originality_report.attachment, submission)
raise ActiveRecord::RecordNotFound if @report.blank?
verify_submission_attachment(@report.attachment, submission)
end
def verify_submission_attachment(attachment, submission)
raise ActiveRecord::RecordNotFound unless attachment.present? && submission.present?
unless submission.assignment == assignment && submission.attachments.include?(attachment)
raise Lti::Errors::UnauthorizedToolError
end
render_unauthorized_action unless submission.assignment == assignment && submission.attachments.include?(attachment)
end
# @!appendix Originality Report UI Locations

View File

@ -0,0 +1,21 @@
#
# 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/>.
#
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end

46
app/models/lti/link.rb Normal file
View File

@ -0,0 +1,46 @@
#
# 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 Lti
class Link < ApplicationRecord
belongs_to :linkable, polymorphic: [:originality_report]
validates :vendor_code, :product_code, :resource_type_code, :resource_link_id, presence: true
validates :resource_link_id, uniqueness: true
before_validation :generate_resource_link_id, on: :create
serialize :custom_parameters
def message_handler(context)
MessageHandler.by_resource_codes(vendor_code: vendor_code,
product_code: product_code,
resource_type_code: resource_type_code,
context: context)
end
def originality_report
if linkable.is_a?(OriginalityReport)
linkable
end
end
def generate_resource_link_id
self.resource_link_id ||= SecureRandom.uuid
end
end
end

View File

@ -49,21 +49,5 @@ module Lti
possible_handlers = ResourceHandler.by_product_family(product_families, context)
possible_handlers.select { |rh| rh.resource_type_code == resource_type_code}
end
def find_or_create_tool_setting(context: nil, resource_url: nil, link_fragment: nil)
context ||= tool_proxy.context
mh = message_handlers.find_by(message_type: MessageHandler::BASIC_LTI_LAUNCH_REQUEST)
resource_link_id = mh.build_resource_link_id(context: context, link_fragment: link_fragment)
tool_setting = Lti::ToolSetting.find_by(resource_link_id: resource_link_id)
tool_setting ||= ToolSetting.new
tool_setting.update_attributes(resource_link_id: resource_link_id,
context: context,
product_code: tool_proxy.product_family.product_code,
vendor_code: tool_proxy.product_family.vendor_code,
resource_type_code: resource_type_code,
resource_url: resource_url)
tool_setting
end
end
end

View File

@ -21,6 +21,10 @@ class OriginalityReport < ActiveRecord::Base
belongs_to :submission
belongs_to :attachment
belongs_to :originality_report_attachment, class_name: "Attachment"
has_one :lti_link, class_name: Lti::Link, as: :linkable, inverse_of: :linkable, dependent: :destroy
accepts_nested_attributes_for :lti_link, allow_destroy: true
validates :attachment, :submission, presence: true
validates :workflow_state, inclusion: { in: ['scored', 'error', 'pending'] }
validates :originality_score, inclusion: { in: 0..100, message: 'score must be between 0 and 100' }, allow_nil: true
@ -42,19 +46,18 @@ class OriginalityReport < ActiveRecord::Base
super(options).tap do |h|
h[:file_id] = h.delete :attachment_id
h[:originality_report_file_id] = h.delete :originality_report_attachment_id
if h[:link_id].present?
tool_setting = Lti::ToolSetting.find_by(resource_link_id: h.delete(:link_id))
h[:tool_setting] = { resource_url: tool_setting.resource_url,
resource_type_code:tool_setting.resource_type_code }
if lti_link.present?
h[:tool_setting] = { resource_url: lti_link.resource_url,
resource_type_code:lti_link.resource_type_code }
end
end
end
def report_launch_url
if link_id.present?
if lti_link.present?
course_assignment_resource_link_id_url(course_id: assignment.context_id,
assignment_id: assignment.id,
resource_link_id: link_id,
resource_link_id: lti_link.resource_link_id,
host: HostUrl.context_host(assignment.context),
display: 'borderless')
else

View File

@ -0,0 +1,39 @@
#
# 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/>.
#
class CreateLtiLinks < ActiveRecord::Migration[5.0]
tag :predeploy
def change
create_table :lti_links do |t|
t.string :resource_link_id, null: false
t.string :vendor_code, null: false
t.string :product_code, null: false
t.string :resource_type_code, null: false
t.integer :linkable_id, limit: 8
t.string :linkable_type
t.text :custom_parameters
t.text :resource_url
t.timestamps
end
add_index :lti_links, [:linkable_id, :linkable_type]
add_index :lti_links, :resource_link_id, unique: true
end
end

View File

@ -0,0 +1,32 @@
#
# 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/>.
#
class CreateLtiLinksForLegacyLtiToolSettings < ActiveRecord::Migration[5.0]
tag :postdeploy
def up
DataFixup::CreateLtiLinksForLegacyLtiToolSettings.send_later_if_production_enqueue_args(:run,
priority: Delayed::LOW_PRIORITY,
max_attempts: 1,
n_strand: 'long_datafixups'
)
end
def down
end
end

View File

@ -0,0 +1,36 @@
#
# 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 DataFixup::CreateLtiLinksForLegacyLtiToolSettings
def self.run
Lti::ToolSetting.where.not(product_code: nil, vendor_code: nil, resource_type_code: nil, resource_link_id: nil).find_each do |tool_setting|
Lti::Link.transaction do
originality_report = OriginalityReport.find_by(link_id: tool_setting.resource_link_id)
link = Lti::Link.create_with({
product_code: tool_setting.product_code,
vendor_code: tool_setting.vendor_code,
resource_type_code: tool_setting.resource_type_code,
custom_parameters: tool_setting.custom_parameters,
resource_url: tool_setting.resource_url,
linkable: originality_report
}).find_or_create_by!(resource_link_id: tool_setting.resource_link_id)
end
end
end
end

View File

@ -65,9 +65,9 @@ module Lti
MEDIA_OBJECT_ID_GUARD = -> {@attachment && (@attachment.media_object || @attachment.media_entry_id )}
LTI1_GUARD = -> { @tool.is_a?(ContextExternalTool) }
MASQUERADING_GUARD = -> { !!@controller && @controller.logged_in_user != @current_user }
ATTACHMENT_ASSOCIATION_GUARD = -> { @tool_setting&.context&.is_a?(AttachmentAssociation) }
LTI_ASSIGN_ID = -> { @assignment.present? || @tool_setting&.context&.is_a?(AttachmentAssociation) || @secure_params.present? }
MESSAGE_TOKEN_GUARD = -> { @post_message_token.present? || @launch&.instance_of?(Lti::Launch) }
ORIGINALITY_REPORT_GUARD = -> { @originality_report.present? }
LTI_ASSIGN_ID = -> { @assignment.present? || @originality_report.present? || @secure_params.present? }
def initialize(root_account, context, controller, opts = {})
@root_account = root_account
@ -147,8 +147,8 @@ module Lti
-> do
if @assignment
@assignment.lti_context_id
elsif @tool_setting&.context&.is_a?(AttachmentAssociation)
@tool_setting.context.context.assignment.lti_context_id
elsif @originality_report
@originality_report.submission.assignment.lti_context_id
elsif @secure_params.present?
Lti::Security.decoded_lti_assignment_id(@secure_params)
end
@ -165,11 +165,9 @@ module Lti
# ```
register_expansion 'com.instructure.OriginalityReport.id', [],
-> do
@tool_setting.context.context.originality_reports.find do |r|
r.attachment_id == @tool_setting.context.attachment_id
end.id
@originality_report.id
end,
ATTACHMENT_ASSOCIATION_GUARD,
ORIGINALITY_REPORT_GUARD,
default_name: 'com_instructure_originality_report_id'
# The Canvas id of the submission associated with the
@ -180,8 +178,8 @@ module Lti
# 23
# ```
register_expansion 'com.instructure.Submission.id', [],
-> { @tool_setting.context.context_id },
ATTACHMENT_ASSOCIATION_GUARD,
-> { @originality_report.submission.id },
ORIGINALITY_REPORT_GUARD,
default_name: 'com_instructure_submission_id'
# The Canvas id of the file associated with the submission
@ -192,8 +190,8 @@ module Lti
# 23
# ```
register_expansion 'com.instructure.File.id', [],
-> { @tool_setting.context.attachment_id },
ATTACHMENT_ASSOCIATION_GUARD,
-> { @originality_report.attachment.id },
ORIGINALITY_REPORT_GUARD,
default_name: 'com_instructure_file_id'
# the LIS identifier for the course offering

View File

@ -19,6 +19,7 @@
require File.expand_path(File.dirname(__FILE__) + '/lti2_api_spec_helper')
require File.expand_path(File.dirname(__FILE__) + '/../../sharding_spec_helper')
require_dependency "lti/ims/access_token_helper"
module Lti
describe 'Originality Reports API', type: :request do
specs_require_sharding
@ -28,8 +29,8 @@ module Lti
let(:aud) { host }
before(:once) { attachment_model }
before :each do
message_handler.update_attributes(message_type: 'basic-lti-launch-request')
course_factory(active_all: true)
message_handler.update_attributes(message_type: 'basic-lti-launch-request')
student_in_course active_all: true
teacher_in_course active_all: true
@ -343,10 +344,38 @@ module Lti
},
headers: request_headers
expect(response).to be_success
tool_setting = Lti::ToolSetting.find_by(resource_link_id: OriginalityReport.find(@report.id).link_id)
tool_setting = OriginalityReport.find(@report.id).lti_link
expect(tool_setting.resource_url).to eq "http://www.lti-test.com"
end
it "removes the lti link when tool_setting is not supplied" do
put @endpoints[:update],
params: {
originality_report: {
originality_score: 5,
tool_setting: {
resource_url: 'http://www.lti-test.com',
resource_type_code: 'code'
}
}
},
headers: request_headers
expect(response).to be_success
lti_link_id = OriginalityReport.find(@report.id).lti_link.id
put @endpoints[:update],
params: {
originality_report: {
originality_score: nil
}
},
headers: request_headers
expect(response).to be_success
expect(Lti::Link.find_by(id: lti_link_id)).to be_nil
report = OriginalityReport.find(@report.id)
expect(report.lti_link).to be_nil
expect(report.originality_score).to be_nil
end
it "requires the plagiarism feature flag" do
allow_any_instance_of(Account).to receive(:feature_enabled?).with(:plagiarism_detection_platform).and_return(false)
@ -389,26 +418,6 @@ module Lti
expect(response_body['tool_setting']['resource_type_code']).to eq resource_handler.resource_type_code
end
it 'sets the context for the associated tool setting' do
score = 0.25
put @endpoints[:update],
params: {
originality_report: {
file_id: @attachment.id,
originality_score: score,
tool_setting: {
resource_type_code: resource_handler.resource_type_code
}
}
},
headers: request_headers
response_body = JSON.parse(response.body)
report = OriginalityReport.find(response_body['id'])
tool_setting = Lti::ToolSetting.find_by(resource_link_id: report.link_id)
attachment_association = report.attachment.attachment_associations.first
expect(tool_setting.context).to eq attachment_association
end
it 'sets the workflow state' do
put @endpoints[:update],
params: {
@ -508,8 +517,8 @@ module Lti
},
headers: request_headers
expect(response).to be_success
tool_setting = Lti::ToolSetting.find_by(resource_link_id: OriginalityReport.find(@report.id).link_id)
expect(tool_setting.resource_url).to eq "http://www.lti-test.com"
lti_link = OriginalityReport.find(@report.id).lti_link
expect(lti_link.resource_url).to eq "http://www.lti-test.com"
end
it "requires the plagiarism feature flag" do
@ -552,26 +561,6 @@ module Lti
expect(response_body['tool_setting']['resource_type_code']).to eq resource_handler.resource_type_code
end
it 'sets the context for the associated tool setting' do
score = 0.25
put @endpoints[:update_alt],
params: {
originality_report: {
file_id: @attachment.id,
originality_score: score,
tool_setting: {
resource_type_code: resource_handler.resource_type_code
}
}
},
headers: request_headers
response_body = JSON.parse(response.body)
report = OriginalityReport.find(response_body['id'])
tool_setting = Lti::ToolSetting.find_by(resource_link_id: report.link_id)
attachment_association = AttachmentAssociation.where(submission: @submission).last
expect(tool_setting.context).to eq attachment_association
end
it 'sets the workflow state' do
put @endpoints[:update_alt],
params: {
@ -713,24 +702,6 @@ module Lti
expect(response_body['tool_setting']['resource_type_code']).to eq resource_handler.resource_type_code
end
it 'sets the context for the associated tool setting' do
post @endpoints[:create],
params: {
originality_report: {
file_id: @attachment.id,
tool_setting: {
resource_type_code: resource_handler.resource_type_code
}
}
},
headers: request_headers
response_body = JSON.parse(response.body)
report = OriginalityReport.find(response_body['id'])
tool_setting = Lti::ToolSetting.find_by(resource_link_id: report.link_id)
attachment_association = report.attachment.attachment_associations.first
expect(tool_setting.context).to eq attachment_association
end
it 'sets the workflow state' do
post @endpoints[:create],
params: {

View File

@ -208,20 +208,18 @@ module Lti
let(:link_id) {SecureRandom.uuid}
let(:tool_setting) do
ToolSetting.new(tool_proxy: tool_proxy,
context: course,
resource_link_id: link_id,
vendor_code: product_family.vendor_code,
product_code: product_family.product_code,
resource_type_code: resource_handler.resource_type_code)
let(:lti_link) do
Link.new(resource_link_id: link_id,
vendor_code: product_family.vendor_code,
product_code: product_family.product_code,
resource_type_code: resource_handler.resource_type_code)
end
before do
message_handler.update_attributes(message_type: MessageHandler::BASIC_LTI_LAUNCH_REQUEST)
resource_handler.message_handlers = [message_handler]
resource_handler.save!
tool_setting.save!
lti_link.save!
user_session(account_admin_user)
end
@ -245,14 +243,12 @@ module Lti
context 'resource_url' do
let(:custom_url) {'http://www.samplelaunch.com/custom-resource-url'}
let(:link_id) {SecureRandom.uuid}
let(:tool_setting) do
ToolSetting.create!(tool_proxy: tool_proxy,
context: course,
resource_link_id: link_id,
vendor_code: product_family.vendor_code,
product_code: product_family.product_code,
resource_type_code: resource_handler.resource_type_code,
resource_url: custom_url)
let(:lti_link) do
Link.create!(resource_link_id: link_id,
vendor_code: product_family.vendor_code,
product_code: product_family.product_code,
resource_type_code: resource_handler.resource_type_code,
resource_url: custom_url)
end
it "uses the 'resource_url' if provided in the 'link_id'" do

View File

@ -0,0 +1,62 @@
#
# 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 "spec_helper"
require File.expand_path(File.dirname(__FILE__) + '../../../lti2_course_spec_helper')
describe DataFixup::CreateLtiLinksForLegacyLtiToolSettings do
include_context 'lti2_course_spec_helper'
let(:link_tool_setting) do
Lti::ToolSetting.create!(
tool_proxy: tool_proxy,
context: course,
resource_link_id: SecureRandom.uuid,
vendor_code: product_family.vendor_code,
product_code: product_family.product_code,
resource_type_code: resource_handler.resource_type_code,
resource_url: 'http://example.com/resource',
custom_parameters: { foo: 'bar' }
)
end
let(:proxy_tool_setting) do
Lti::ToolSetting.create!(
tool_proxy: tool_proxy,
custom: { param: 42 }
)
end
before(:each) do
link_tool_setting
proxy_tool_setting
end
it 'creates lti links from link tool settings' do
DataFixup::CreateLtiLinksForLegacyLtiToolSettings.run
lti_links = Lti::Link.all
expect(lti_links.count).to eq 1
lti_link = lti_links.first
expect(lti_link.vendor_code).to eq link_tool_setting.vendor_code
expect(lti_link.product_code).to eq link_tool_setting.product_code
expect(lti_link.resource_type_code).to eq link_tool_setting.resource_type_code
expect(lti_link.resource_url).to eq link_tool_setting.resource_url
expect(lti_link.resource_link_id).to eq link_tool_setting.resource_link_id
expect(lti_link.custom_parameters).to eq link_tool_setting.custom_parameters
end
end

View File

@ -78,9 +78,7 @@ module Lti
submission: submission,
link_id: resource_link_id)
end
let(:attachment_association) { AttachmentAssociation.create!(context: submission, attachment: attachment) }
let(:tool_setting) { Lti::ToolSetting.create!(context: attachment_association, resource_link_id: resource_link_id) }
let(:variable_expander) { VariableExpander.new(root_account, account, controller, current_user: user, tool: tool, tool_setting: tool_setting) }
let(:variable_expander) { VariableExpander.new(root_account, account, controller, current_user: user, tool: tool, originality_report: originality_report) }
it 'clears the lti_helper instance variable when you set the current_user' do
expect(variable_expander.lti_helper).not_to be nil
@ -279,7 +277,7 @@ module Lti
it 'has a substitution for com.instructure.Assignment.lti.id' do
exp_hash = {test: '$com.instructure.Assignment.lti.id'}
variable_expander.expand_variables!(exp_hash)
expect(exp_hash[:test]).to eq tool_setting.context.context.assignment.lti_context_id
expect(exp_hash[:test]).to eq originality_report.submission.assignment.lti_context_id
end
it 'has a substitution for com.instructure.Assignment.lti.id when there is no tool setting' do
@ -348,6 +346,24 @@ module Lti
expect(exp_hash[:test]).to eq 'api/lti/assignments/{assignment_id}/submissions/{submission_id}/originality_report'
end
it 'has substitution for com.instructure.OriginalityReport.id' do
exp_hash = {test: '$com.instructure.OriginalityReport.id'}
variable_expander.expand_variables!(exp_hash)
expect(exp_hash[:test]).to eq originality_report.id
end
it 'has substitution for com.instructure.Submission.id' do
exp_hash = {test: '$com.instructure.Submission.id'}
variable_expander.expand_variables!(exp_hash)
expect(exp_hash[:test]).to eq originality_report.submission.id
end
it 'has substitution for com.instructure.File.id' do
exp_hash = {test: '$com.instructure.File.id'}
variable_expander.expand_variables!(exp_hash)
expect(exp_hash[:test]).to eq originality_report.attachment.id
end
it 'has substitution for vnd.Canvas.submission.url' do
exp_hash = {test: '$vnd.Canvas.submission.url'}
variable_expander.expand_variables!(exp_hash)

View File

@ -0,0 +1,105 @@
#
# 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')
require File.expand_path(File.dirname(__FILE__) + '/../../lti2_spec_helper.rb')
module Lti
RSpec.describe Link, type: :model do
include_context 'lti2_spec_helper'
describe "validations" do
let(:params) do
{
vendor_code: 'vendor_code',
product_code: 'product_code',
resource_type_code: 'resource_type_code',
resource_link_id: 'resource_link_id'
}
end
it "requires vendor_code" do
params.delete :vendor_code
link = Lti::Link.new(params)
expect(link.valid?).to eq false
end
it "requires product_code" do
params.delete :product_code
link = Lti::Link.new(params)
expect(link.valid?).to eq false
end
it "requires resource_type_code" do
params.delete :resource_type_code
link = Lti::Link.new(params)
expect(link.valid?).to eq false
end
it "populates resource_link_id if not present" do
params.delete :resource_link_id
link = Lti::Link.new(params)
expect(link.valid?).to eq true
end
it "requires resource_link_id to be unique" do
Lti::Link.create!(params)
link = Lti::Link.new(params)
expect(link.valid?).to eq false
end
end
describe '#message_handler' do
let(:lti_link) { subject }
before do
message_handler.update_attributes(message_type: MessageHandler::BASIC_LTI_LAUNCH_REQUEST)
resource_handler.message_handlers = [message_handler]
resource_handler.save!
lti_link.update_attributes(resource_type_code: resource_handler.resource_type_code,
product_code: product_family.product_code,
vendor_code: product_family.vendor_code)
end
it 'looks up the message handler identified by the codes' do
expect(lti_link.message_handler(account)).to eq message_handler
end
end
describe '#originality_report' do
it 'returns an originality_report if linkable is an OriginalityReport' do
report = OriginalityReport.new
lti_link = Lti::Link.new(linkable: report)
expect(lti_link.originality_report).to eq report
end
it 'returns nil if linkable is not an OriginalityReport ' do
lti_link = Lti::Link.new
expect(lti_link.originality_report).to be_nil
end
end
describe '#generate_resource_link_id' do
it 'sets the resource_link_id' do
lti_link = Lti::Link.new
expect(lti_link.resource_link_id).to be_nil
lti_link.generate_resource_link_id
expect(lti_link.resource_link_id).not_to be_nil
end
end
end
end

View File

@ -138,32 +138,5 @@ module Lti
expect(resource_handlers).to be_blank
end
end
describe '#find_or_create_tool_setting' do
before do
message_handler.update_attributes(message_type: MessageHandler::BASIC_LTI_LAUNCH_REQUEST)
resource_handler.message_handlers = [message_handler]
resource_handler.save!
user_session(account_admin_user)
end
it 'creates a new tool setting if one with the existing resource_link_id does not exist' do
expected_id = message_handler.build_resource_link_id(context: tool_proxy.context)
expect(resource_handler.find_or_create_tool_setting.resource_link_id).to eq expected_id
end
it 'reuses a tool setting if one with the same resource_link_id exists' do
tool_setting = resource_handler.find_or_create_tool_setting
expect(resource_handler.find_or_create_tool_setting).to eq tool_setting
end
it 'allows changing the settings of an originality report without affecting others' do
link_fragment = SecureRandom.uuid
resource_handler.find_or_create_tool_setting
setting_two = resource_handler.find_or_create_tool_setting(link_fragment: link_fragment)
setting_two.update_attributes(resource_url: 'http://www.test.com')
expect(resource_handler.find_or_create_tool_setting.resource_url).to be_nil
end
end
end
end

View File

@ -100,6 +100,52 @@ describe OriginalityReport do
expect(subject.state).to eq 'acceptable'
end
describe 'accepts nested attributes for lti link' do
let(:lti_link_attributes) do
{
product_code: 'product',
vendor_code: 'vendor',
resource_type_code: 'resource'
}
end
it 'creates an lti link' do
report = OriginalityReport.create!(
attachment: attachment,
originality_score: '1',
submission: submission,
workflow_state: 'pending',
lti_link_attributes: lti_link_attributes
)
expect(report.lti_link.product_code).to eq 'product'
end
it 'updates an lti link' do
report = OriginalityReport.create!(
attachment: attachment,
originality_score: '1',
submission: submission,
workflow_state: 'pending',
lti_link_attributes: lti_link_attributes
)
report.update_attributes(lti_link_attributes: { id: report.lti_link.id, resource_url: 'http://example.com' })
expect(report.lti_link.resource_url).to eq 'http://example.com'
end
it 'destroys an lti link' do
report = OriginalityReport.create!(
attachment: attachment,
originality_score: '1',
submission: submission,
workflow_state: 'pending',
lti_link_attributes: lti_link_attributes
)
link_id = report.lti_link.id
report.update_attributes!(lti_link_attributes: { id: link_id, _destroy: true })
expect(Lti::Link.find_by(id: link_id)).to be_nil
end
end
describe 'workflow_state transitions' do
let(:report_no_score){ OriginalityReport.new(attachment: attachment, submission: submission) }
let(:report_with_score){ OriginalityReport.new(attachment: attachment, submission: submission, originality_score: 23.2) }
@ -135,23 +181,20 @@ describe OriginalityReport do
describe '#report_launch_url' do
include_context 'lti2_spec_helper'
let(:link_id) { SecureRandom.uuid }
let(:tool_setting) do
ToolSetting.new(tool_proxy: tool_proxy,
context: course,
resource_link_id: link_id,
vendor_code: product_family.vendor_code,
product_code: product_family.product_code,
resource_type_code: resource_handler.resource_type_code)
let(:lti_link) do
Lti::Link.new(resource_link_id: SecureRandom.uuid,
vendor_code: product_family.vendor_code,
product_code: product_family.product_code,
resource_type_code: resource_handler.resource_type_code,
linkable: report)
end
let(:report) { subject }
it 'creates an LTI launch URL if a link_id is present' do
report.update_attributes(link_id: link_id)
it 'creates an LTI launch URL if a lti_link is present' do
report.update_attributes(lti_link: lti_link)
expected_url = "http://localhost/courses/"\
"#{submission.assignment.course.id}/assignments/"\
"#{submission.assignment.id}/lti/resource/#{link_id}?display=borderless"
"#{submission.assignment.id}/lti/resource/#{lti_link.resource_link_id}?display=borderless"
expect(report.report_launch_url).to eq expected_url
end