panda_pal 5.19.0.beta1 → 5.19.0.beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +13 -0
  3. data/app/models/panda_pal/platform/canvas.rb +25 -0
  4. data/lib/panda_pal/version.rb +1 -1
  5. data/panda_pal.gemspec +1 -1
  6. metadata +2 -60
  7. data/spec/controllers/panda_pal/api_call_controller_spec.rb +0 -25
  8. data/spec/controllers/panda_pal/lti_v1_p0_controller_spec.rb +0 -194
  9. data/spec/controllers/panda_pal/lti_v1_p3_controller_spec.rb +0 -108
  10. data/spec/controllers/panda_pal/lti_v1_p3_tools_spec.rb +0 -144
  11. data/spec/core/apartment_multidb_spec.rb +0 -166
  12. data/spec/factories/panda_pal_organizations.rb +0 -9
  13. data/spec/factories/panda_pal_sessions.rb +0 -6
  14. data/spec/internal/app/models/account.rb +0 -7
  15. data/spec/internal/app/models/admin.rb +0 -6
  16. data/spec/internal/config/database.yml +0 -18
  17. data/spec/internal/config/routes.rb +0 -5
  18. data/spec/internal/config/storage.yml +0 -3
  19. data/spec/internal/db/schema.rb +0 -30
  20. data/spec/jobs/panda_pal/jobs/grade_passback_job_spec.rb +0 -83
  21. data/spec/lib/panda_pal/ags_client_spec.rb +0 -385
  22. data/spec/lib/panda_pal/helpers/secure_headers_spec.rb +0 -77
  23. data/spec/lib/panda_pal/helpers/session_replacement_spec.rb +0 -72
  24. data/spec/lib/panda_pal/lti_tools_spec.rb +0 -153
  25. data/spec/models/panda_pal/api_call_spec.rb +0 -30
  26. data/spec/models/panda_pal/lti_registration_spec.rb +0 -72
  27. data/spec/models/panda_pal/organization/settings_validation_spec.rb +0 -179
  28. data/spec/models/panda_pal/organization/task_scheduling_spec.rb +0 -145
  29. data/spec/models/panda_pal/organization_spec.rb +0 -50
  30. data/spec/models/panda_pal/platform/canvas_install_spec.rb +0 -115
  31. data/spec/models/panda_pal/platform_spec.rb +0 -349
  32. data/spec/models/panda_pal/session_account_roles_spec.rb +0 -182
  33. data/spec/models/panda_pal/session_spec.rb +0 -202
  34. data/spec/spec_helper.rb +0 -159
  35. data/spec/support/isolated_lti_config.rb +0 -24
@@ -1,30 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module PandaPal
4
- RSpec.describe ApiCall, type: :model do
5
- let!(:subject) { ApiCall.create!(logic: '"#{p[:foo]}_bar"') }
6
- let!(:jwt) { subject.jwt_token }
7
-
8
- describe ".from_jwt" do
9
- it "finds an ApiCall from a JWT" do
10
- expect(ApiCall.from_jwt(jwt)).to eq subject
11
- end
12
-
13
- it "fails if the JWT is invalid" do
14
- expect{ ApiCall.from_jwt("really.bad.jwt") }.to raise_error(JWT::DecodeError)
15
- end
16
- end
17
-
18
- describe "#call" do
19
- it "calls the function with parameters" do
20
- expect(subject.call({ foo: 'FOO' })).to eq 'FOO_bar'
21
- end
22
- end
23
-
24
- describe "#call_url" do
25
- it "generates a URL with the expected placholders" do
26
- expect(subject.call_url).to eq "/panda_pal/call_logic?foo=TODO&token=#{jwt}"
27
- end
28
- end
29
- end
30
- end
@@ -1,72 +0,0 @@
1
- require 'spec_helper'
2
-
3
- RSpec.describe PandaPal::LtiRegistration, :isolated_lti_config do
4
- let(:organization) { create(:panda_pal_organization) }
5
-
6
- before { PandaPal.define_tool(:translate) }
7
-
8
- def register(**attrs)
9
- organization.lti_registrations.create!(tool: 'translate', client_id: '10000000000001', **attrs)
10
- end
11
-
12
- describe '.for_launch' do
13
- it 'prefers a deployment-specific registration' do
14
- key_wide = register
15
- dedicated = register(deployment_id: 'dep-1')
16
-
17
- expect(described_class.for_launch('10000000000001', 'dep-1')).to eq(dedicated)
18
- expect(described_class.for_launch('10000000000001', 'dep-2')).to eq(key_wide)
19
- end
20
-
21
- it 'matches a key-wide registration when the launch has no deployment_id' do
22
- key_wide = register
23
-
24
- expect(described_class.for_launch('10000000000001', nil)).to eq(key_wide)
25
- end
26
-
27
- it 'matches numeric client_ids' do
28
- key_wide = register
29
-
30
- expect(described_class.for_launch(10000000000001, nil)).to eq(key_wide)
31
- end
32
-
33
- it 'returns nil for unknown or blank client_ids' do
34
- register
35
-
36
- expect(described_class.for_launch('99', nil)).to be_nil
37
- expect(described_class.for_launch(nil, 'dep-1')).to be_nil
38
- end
39
- end
40
-
41
- describe 'validations' do
42
- it 'rejects a tool that was never defined' do
43
- registration = organization.lti_registrations.build(tool: 'nope', client_id: '1')
44
-
45
- expect(registration).not_to be_valid
46
- expect(registration.errors[:tool]).to be_present
47
- end
48
-
49
- it 'allows only one key-wide registration per client_id' do
50
- register
51
-
52
- expect { register }.to raise_error(ActiveRecord::RecordInvalid)
53
- end
54
-
55
- it 'allows only one registration per client_id and deployment' do
56
- register(deployment_id: 'dep-1')
57
-
58
- expect { register(deployment_id: 'dep-1') }.to raise_error(ActiveRecord::RecordInvalid)
59
- expect { register(deployment_id: 'dep-2') }.not_to raise_error
60
- end
61
-
62
- it 'stores a blank deployment_id as nil' do
63
- expect(register(deployment_id: '').deployment_id).to be_nil
64
- end
65
- end
66
-
67
- it 'is destroyed with its organization' do
68
- register
69
-
70
- expect { organization.destroy }.to change(described_class, :count).by(-1)
71
- end
72
- end
@@ -1,179 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module PandaPal
4
- PandaPal::Organization
5
- RSpec.describe OrganizationConcerns::SettingsValidation, type: :model do
6
- let!(:org) { create :panda_pal_organization }
7
-
8
- after :all do
9
- PandaPal.lti_options[:settings_structure] = nil
10
- end
11
-
12
- def set_test_settings_structure
13
- PandaPal.lti_options = {
14
- title: 'Test App',
15
- settings_structure: structure,
16
- }
17
- end
18
-
19
- RSpec.shared_examples "shared stuff" do
20
- it 'does not perform any validations if settings is not defined' do
21
- PandaPal.lti_options = {}
22
- expect(org.valid?).to be_truthy
23
- end
24
-
25
- it 'does not perform any validations if options is not defined' do
26
- PandaPal.lti_options = nil
27
- expect(org.valid?).to be_truthy
28
- end
29
-
30
- it 'does perform validations if settings_structure is defined' do
31
- set_test_settings_structure
32
- org.valid?
33
- expect(org.valid?).to be_falsey
34
- end
35
-
36
- it 'will fail if a required setting is not present' do
37
- set_test_settings_structure
38
- expect(org.valid?).to be_falsey
39
- errors = org.errors.messages[:settings]
40
- expect(errors[0]).to eq("Entry [:canvas] is required")
41
- end
42
-
43
- it 'will fail if a setting is supplied but data_type is wrong' do
44
- set_test_settings_structure
45
- org.settings = {canvas: "Dog", reports: {}}
46
- expect(org.valid?).to be_falsey
47
- errors = org.errors.messages[:settings]
48
- expect(errors[0]).to eq("Expected [:canvas] to be a Hash. Was a String")
49
- end
50
-
51
- it 'will fail if a required subsetting is missing' do
52
- set_test_settings_structure
53
- org.settings = {canvas: {base_url: 'http://'}, reports: {}}
54
- expect(org.valid?).to be_falsey
55
- errors = org.errors.messages[:settings]
56
- expect(errors[0]).to eq("Entry [:canvas][:api_token] is required")
57
- end
58
-
59
- it 'will fail if extra options are specified' do
60
- set_test_settings_structure
61
- org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {}, unknown_option: "WHAT IS THIS?"}
62
- expect(org.valid?).to be_falsey
63
- errors = org.errors.messages[:settings]
64
- expect(errors[0]).to eq("Did not expect [:] to contain [unknown_option]")
65
- end
66
-
67
- it 'will pass if all structure is maintained' do
68
- set_test_settings_structure
69
- org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {submissions_report_time_length: 30.minutes, max_recheck_time: 10.hours}}
70
- expect(org.valid?).to be_truthy
71
- end
72
- end
73
-
74
- context 'new settings_structure' do
75
- let!(:properties) do
76
- {
77
- canvas: {
78
- required: true,
79
- type: 'Hash',
80
- properties: {
81
- api_token: { type: 'String', required: true },
82
- base_url: { type: 'String', required: true },
83
- },
84
- },
85
- reports: {
86
- required: true,
87
- type: 'Hash',
88
- allow_additional: true,
89
- }
90
- }
91
- end
92
-
93
- let!(:structure) do
94
- {
95
- properties: properties,
96
- }
97
- end
98
-
99
- it_behaves_like("shared stuff")
100
-
101
- describe ':allow_additional' do
102
- it 'passes extra properties if allow_additional is a matching Hash' do
103
- structure[:allow_additional] = { type: 'String' }
104
- set_test_settings_structure
105
- org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {}, unknown_option: "WHAT IS THIS?"}
106
- expect(org).to be_valid
107
- end
108
-
109
- it 'fails extra properties if allow_additional is an unmatching Hash' do
110
- structure[:allow_additional] = { type: 'Hash' }
111
- set_test_settings_structure
112
- org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {}, unknown_option: "WHAT IS THIS?"}
113
- expect(org).not_to be_valid
114
- end
115
-
116
- it 'passes extra properties if allow_additional is a blank Hash' do
117
- structure[:allow_additional] = { }
118
- set_test_settings_structure
119
- org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {}, unknown_option: "WHAT IS THIS?"}
120
- expect(org).to be_valid
121
- end
122
- end
123
-
124
- describe ':validate' do
125
- let!(:properties) do
126
- {
127
- blah: {
128
- type: 'String',
129
- validate: -> (v, *args) { v == 'blah' ? nil : '<path> failed validation' },
130
- }
131
- }
132
- end
133
-
134
- it 'supports a custom validation proc' do
135
- set_test_settings_structure
136
- org.settings = { blah: 'blah' }
137
- expect(org).to be_valid
138
- end
139
-
140
- it 'replaces <path> with the human readable path' do
141
- set_test_settings_structure
142
- org.settings = { blah: '' }
143
- expect(org.valid?).to be_falsey
144
- errors = org.errors.messages[:settings]
145
- expect(errors[0]).to eq("[:blah] failed validation")
146
- end
147
- end
148
- end
149
-
150
- context 'old settings_structure' do
151
- let!(:structure) do
152
- YAML.load("
153
- canvas:
154
- is_required: true
155
- data_type: Hash
156
- api_token:
157
- is_required: true
158
- data_type: String
159
- base_url:
160
- is_required: true
161
- data_type: String
162
- reports:
163
- is_required: true
164
- data_type: Hash
165
- active_term_allowance:
166
- submissions_report_time_length:
167
- is_required: false
168
- data_type: ActiveSupport::Duration
169
- recheck_wait:
170
- data_type: ActiveSupport::Duration
171
- max_recheck_time:
172
- is_required: false
173
- ").deep_symbolize_keys
174
- end
175
-
176
- it_behaves_like("shared stuff")
177
- end
178
- end
179
- end
@@ -1,145 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module PandaPal
4
- PandaPal::Organization
5
-
6
- RSpec.describe OrganizationConcerns::TaskScheduling, type: :model do
7
- let!(:organization) { create(:panda_pal_organization) }
8
- let(:schedules) { {} }
9
-
10
- before :each do
11
- Organization.instance_variable_set(:@_schedule_descriptors, nil)
12
- Organization.scheduled_task('0 0 0 * * *', :ident) { }
13
-
14
- allow(Sidekiq).to receive(:remove_schedule)
15
- allow(Sidekiq).to receive(:get_schedule) { schedules }
16
- end
17
-
18
- def descriptors
19
- Organization.instance_variable_get(:@_schedule_descriptors)
20
- end
21
-
22
- def descriptor
23
- descriptors[descriptors.keys[0]]
24
- end
25
-
26
- describe '.scheduled_task' do
27
- it 'adds to the set of descriptors' do
28
- expect(descriptors).to match(
29
- 'ident' => {
30
- :key=>"ident",
31
- :schedule=>"0 0 0 * * *",
32
- :worker=>Proc,
33
- :queue=>"default",
34
- }
35
- )
36
- end
37
- end
38
-
39
- describe '.sync_schedules' do
40
- it 'adds new schedules' do
41
- expect(Sidekiq).to receive(:set_schedule).with(/org:\w+-ident/, anything)
42
- Organization.sync_schedules
43
- end
44
-
45
- it 'removes schedules for deleted Orgs' do
46
- schedules['org:deleted_org-schedule'] = {}
47
- expect(Sidekiq).to receive(:remove_schedule).with('org:deleted_org-schedule')
48
- Organization.sync_schedules
49
- end
50
-
51
- it 'keeps schedules created from other sources' do
52
- schedules['other-schedule'] = {}
53
- expect(Sidekiq).not_to receive(:remove_schedule).with('other-schedule')
54
- Organization.sync_schedules
55
- end
56
- end
57
-
58
- describe '#generate_schedule' do
59
- it 'generates the expected schedule' do
60
- expect(organization.generate_schedule).to eq({
61
- "org:#{organization.name}-ident" => {
62
- "cron"=>"0 0 0 * * *",
63
- "queue"=>"default",
64
- "class"=>"PandaPal::OrganizationConcerns::TaskScheduling::ScheduledTaskExecutor",
65
- "args"=>[organization.name, "ident"],
66
- "enabled" => true,
67
- }
68
- })
69
- end
70
- end
71
-
72
- describe '#schedule_task_cron_time' do
73
- before :each do
74
- organization.settings = { timezone: 'America/Denver' }
75
- end
76
-
77
- it 'includes timezone if in settings' do
78
- expect(organization.send(:schedule_task_cron_time, descriptor)).to eq '0 0 0 * * * America/Denver'
79
- end
80
-
81
- it 'does not re-append timezone if already present' do
82
- descriptor[:schedule] = '1 1 1 * * * America/Chicago'
83
- expect(organization.send(:schedule_task_cron_time, descriptor)).to eq '1 1 1 * * * America/Chicago'
84
- end
85
- end
86
-
87
- describe '#sync_schedule' do
88
- it 'adds new schedules' do
89
- expect(Sidekiq).to receive(:set_schedule).with(/org:\w+-ident/, anything)
90
- organization.sync_schedule
91
- end
92
-
93
- it 'removes old jobs' do
94
- schedules["org:#{organization.name}-old_schedule"] = {}
95
- expect(Sidekiq).to receive(:remove_schedule).with("org:#{organization.name}-old_schedule")
96
- organization.sync_schedule
97
- end
98
- end
99
-
100
- describe 'SettingsValidation' do
101
- before :each do
102
- organization.settings = {}
103
- end
104
-
105
- it 'passes a valid timezone' do
106
- organization.settings[:timezone] = 'America/Denver'
107
- expect(organization).to be_valid
108
- end
109
-
110
- it 'fails an invalid timezone' do
111
- organization.settings[:timezone] = 'Timezone/Blorg'
112
- expect(organization).not_to be_valid
113
- end
114
-
115
- it 'allows [:task_schedules] entry to be missing' do
116
- expect(organization).to be_valid
117
- end
118
-
119
- it 'does not require [:task_schedules] sub-entries' do
120
- organization.settings[:task_schedules] = {}
121
- expect(organization).to be_valid
122
- end
123
-
124
- it 'allows task entry to be false' do
125
- organization.settings[:task_schedules] = { ident: false }
126
- expect(organization).to be_valid
127
- end
128
-
129
- it 'allows task entry to be a valid cron string' do
130
- organization.settings[:task_schedules] = { ident: '0 0 0 * * * America/Denver' }
131
- expect(organization).to be_valid
132
- end
133
-
134
- it 'does not allow task entry to be an invalid cron string' do
135
- organization.settings[:task_schedules] = { ident: 'blort' }
136
- expect(organization).not_to be_valid
137
- end
138
-
139
- it 'does not allow sub-entries for unknown tasks' do
140
- organization.settings[:task_schedules] = { missing_ident: '0 0 0 * * * America/Denver' }
141
- expect(organization).not_to be_valid
142
- end
143
- end
144
- end
145
- end
@@ -1,50 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module PandaPal
4
- RSpec.describe Organization, type: :model do
5
- it 'creates a schema upon creation' do
6
- expect(Apartment::Tenant).to receive(:create).and_call_original
7
- create :panda_pal_organization
8
- end
9
-
10
- it 'deletes a schema upon deletion' do
11
- expect(Apartment::Tenant).to receive(:create).and_call_original
12
- expect(Apartment::Tenant).to receive(:drop).and_call_original
13
- org = create :panda_pal_organization
14
- org.destroy
15
- end
16
-
17
- it 'does not allow the name to be changed after creation' do
18
- org = create :panda_pal_organization
19
- org.name = 'test123'
20
- expect(org.valid?).to be_falsey
21
- end
22
-
23
- it 'requires a salesforce_id' do
24
- org = build :panda_pal_organization, salesforce_id: nil
25
- expect(org.valid?).to be_falsey
26
- end
27
-
28
- it 'does not allow duplicate salesforce ids' do
29
- org = create :panda_pal_organization, salesforce_id: 'salesforce'
30
- org2 = build :panda_pal_organization, salesforce_id: 'salesforce'
31
- expect(org2.valid?).to be_falsey
32
- end
33
-
34
- it 'stores setting as an indifferent Hash' do
35
- org = create :panda_pal_organization
36
- org.update!(settings: { a: 1 })
37
-
38
- expect(Organization.last.settings).to be_a (ActiveSupport::HashWithIndifferentAccess)
39
-
40
- org.settings = {}
41
- expect(org.settings).to be_a (ActiveSupport::HashWithIndifferentAccess)
42
- end
43
-
44
- it 'stores settings' do
45
- org = create :panda_pal_organization
46
- org.update!(settings: { a: 1 })
47
- expect(Organization.last.settings[:a]).to eql(1)
48
- end
49
- end
50
- end
@@ -1,115 +0,0 @@
1
- require 'spec_helper'
2
-
3
- RSpec.describe 'PandaPal::Platform::Canvas::OrgExtension#install_lti', :isolated_lti_config do
4
- let(:organization) { create(:panda_pal_organization, key: '10000000000001') }
5
- let(:client) { double('Bearcat::Client') }
6
- let(:host) { 'https://tool.example.com' }
7
- let(:installed_tools) { [] }
8
-
9
- before do
10
- # _install_lti only checks that Bearcat exists; the client itself is stubbed.
11
- stub_const('Bearcat', Module.new)
12
- c = client
13
- organization.define_singleton_method(:bearcat_client) { c }
14
-
15
- PandaPal.lti_environments = { domain: 'tool.example.com' }
16
- PandaPal.lti_options = { title: 'Shared Title' }
17
- PandaPal.define_tool(:translate, title: 'Translate Title')
18
- PandaPal.stage_placement(:editor_button, tool: :translate, url: :panda_pal)
19
-
20
- allow(client).to receive(:account_external_tools).with('self').and_return(double(all_pages_each: installed_tools))
21
- end
22
-
23
- after { Apartment::Tenant.reset }
24
-
25
- describe 'LTI 1.3' do
26
- let(:developer_key) do
27
- { id: 10000000000002, api_key: 'dev-key-secret', developer_key_account_binding: { workflow_state: 'on' } }
28
- end
29
-
30
- before do
31
- allow(client).to receive(:get).and_return([])
32
- allow(HTTParty).to receive(:get) do |url, **|
33
- double(body: { target_link_uri: "#{host}/panda_pal/v1p3/resource_link_request", extensions: [] }.to_json, url: url)
34
- end
35
- end
36
-
37
- it 'creates a Developer Key from the tool config, installs it, and registers it without touching the org key' do
38
- expect(client).to receive(:post).with('api/lti/accounts/self/developer_keys/tool_configuration', hash_including(
39
- developer_key: hash_including(name: 'Translate Title'),
40
- )).and_return({ developer_key: developer_key })
41
- expect(client).to receive(:create_account_external_tool).with('self', { client_id: 10000000000002 }).and_return({ id: 7, deployment_id: 'dep-9' })
42
-
43
- organization.install_lti(host: host, tool: :translate)
44
-
45
- expect(HTTParty).to have_received(:get).with(a_string_including('v1p3/config?tool=translate'), format: :plain)
46
- expect(organization.reload.key).to eq('10000000000001')
47
- expect(organization.lti_registrations.pluck(:tool, :client_id, :deployment_id)).to eq([['translate', '10000000000002', nil]])
48
- end
49
-
50
- it 'records the deployment_id for a dedicated deployment' do
51
- allow(client).to receive(:post).and_return({ developer_key: developer_key })
52
- allow(client).to receive(:create_account_external_tool).and_return({ id: 7, deployment_id: 'dep-9' })
53
-
54
- organization.install_lti(host: host, tool: :translate, dedicated_deployment: true)
55
-
56
- expect(organization.lti_registrations.pluck(:deployment_id)).to eq(['dep-9'])
57
- end
58
-
59
- context 'when the tool is already registered and installed' do
60
- let(:installed_tools) { [{ id: 7, developer_key_id: 2, deployment_id: 'dep-9' }] }
61
-
62
- before do
63
- organization.lti_registrations.create!(tool: 'translate', client_id: '10000000000002')
64
- allow(client).to receive(:get).and_return([developer_key])
65
- end
66
-
67
- it 'updates the registered Developer Key in place and reuses its install' do
68
- expect(client).not_to receive(:post)
69
- expect(client).not_to receive(:create_account_external_tool)
70
- expect(client).to receive(:put).with('api/lti/developer_keys/10000000000002/tool_configuration', anything).and_return(developer_key)
71
-
72
- organization.install_lti(host: host, tool: :translate, exists: :update)
73
-
74
- expect(organization.lti_registrations.count).to eq(1)
75
- end
76
- end
77
-
78
- it 'parses dotted versions (1.3 used to resolve to v1p1)' do
79
- allow(client).to receive(:post).and_return({ developer_key: developer_key })
80
- allow(client).to receive(:create_account_external_tool).and_return({ id: 7, deployment_id: 'dep-9' })
81
-
82
- expect { organization.install_lti(host: host, tool: :translate, version: '1.3') }.not_to raise_error
83
- expect(organization.lti_registrations.count).to eq(1)
84
- end
85
-
86
- it 'raises for a tool that was never defined' do
87
- expect { organization.install_lti(host: host, tool: :nope) }.to raise_error(PandaPal::UnknownLtiTool)
88
- end
89
- end
90
-
91
- describe 'LTI 1.0' do
92
- let(:installed_tools) do
93
- [
94
- { id: 1, consumer_key: organization.key, custom_fields: {} },
95
- { id: 2, consumer_key: organization.key, custom_fields: { PandaPal::TOOL_CUSTOM_FIELD => 'translate' } },
96
- ]
97
- end
98
-
99
- it "updates only the matching tool's install, pointing it at that tool's config" do
100
- expect(client).to receive(:edit_account_external_tool).with('self', 2, hash_including(
101
- name: 'Translate Title',
102
- consumer_key: organization.key,
103
- config_url: a_string_including('v1p0/config?tool=translate'),
104
- ))
105
-
106
- organization.install_lti(host: host, tool: :translate, version: '1.0', exists: :update)
107
- end
108
-
109
- it 'still matches the default install by consumer key alone' do
110
- expect(client).to receive(:edit_account_external_tool).with('self', 1, hash_including(name: 'Shared Title'))
111
-
112
- organization.install_lti(host: host, version: 'v1p0', exists: :update)
113
- end
114
- end
115
- end