remove address book specs/plugin

Test plan: specs pass

Change-Id: Id3394d6da6c738db078754c97398eb5112458550
Reviewed-on: https://gerrit.instructure.com/205138
Tested-by: Jenkins
Reviewed-by: Steven Burnett <sburnett@instructure.com>
QA-Review: Steven Burnett <sburnett@instructure.com>
Product-Review: Cameron Matheson <cameron@instructure.com>
This commit is contained in:
Cameron Matheson 2019-08-14 15:01:04 -06:00
parent d43c5389e0
commit ed30b3dce9
7 changed files with 1 additions and 571 deletions

View File

@ -1,28 +0,0 @@
<%
# Copyright (C) 2016 - 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/>.
%>
<%= fields_for :settings, OpenObject.new(settings) do |f| %>
<table style="width: 500px;" class="formtable">
<tr>
<td><%= f.blabel :strategy, t('Answer address book queries through') %></td>
<td>
<%= f.select :strategy, AddressBook::STRATEGIES.map{ |key,strategy| [strategy[:label].call(), key] }, {}, :class => "select_action" %>
</td>
</tr>
</table>
<% end %>

View File

@ -23,9 +23,6 @@ require_relative 'address_book/messageable_user'
module AddressBook
STRATEGIES = {
'messageable_user' => { implementation: AddressBook::MessageableUser, label: lambda{ I18n.t('MessageableUser library') } }.freeze,
'microservice' => { implementation: AddressBook::Service, label: lambda{ I18n.t('AddressBook microservice') } }.freeze,
'performance_tap' => { implementation: AddressBook::PerformanceTap, label: lambda{ I18n.t('AddressBook performance tap') } }.freeze,
'empty' => { implementation: AddressBook::Empty, label: lambda{ I18n.t('Empty stub (for testing only)') } }.freeze
}.freeze
DEFAULT_STRATEGY = 'messageable_user'
@ -35,13 +32,7 @@ module AddressBook
# choose the implementation of address book according to the plugin setting
def self.strategy
strategy = Canvas::Plugin.find('address_book').settings[:strategy]
unless STRATEGIES.has_key?(strategy)
# plugin setting specifies an invalid strategy. (TODO: logger.warn or
# something.) gracefully fall back on default
strategy = DEFAULT_STRATEGY
end
strategy
DEFAULT_STRATEGY
end
def self.implementation

View File

@ -1,28 +0,0 @@
#
# Copyright (C) 2016 - 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/>.
Canvas::Plugin.register('address_book', nil, {
:name => lambda{ t :name, 'Address Book' },
:description => lambda{ t :description, 'Configure how to answer address book queries.' },
:author => 'Instructure',
:author_website => 'http://www.instructure.com',
:version => '0.1.0',
:settings_partial => 'plugins/address_book_settings',
:validator => 'AddressBookValidator',
# default settings
:settings => { strategy: AddressBook::DEFAULT_STRATEGY }
})

View File

@ -407,4 +407,3 @@ Canvas::Plugin.register('unsplash', nil, {
settings: { access_key: nil },
settings_partial: 'plugins/unsplash_settings'
})
require_dependency 'canvas/plugins/address_book'

View File

@ -1,29 +0,0 @@
#
# Copyright (C) 2011 - 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 Canvas::Plugins::Validators::AddressBookValidator
def self.validate(settings, plugin_setting)
strategy = settings[:strategy]
if AddressBook::STRATEGIES.has_key?(strategy)
settings.to_hash.with_indifferent_access
else
plugin_setting.errors.add(:base, I18n.t('Invalid address book strategy.'))
false
end
end
end

View File

@ -20,20 +20,6 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
describe AddressBook do
describe "for" do
it "returns an instance of AddressBook::MessageableUser for 'messageable_user' strategy" do
plugin = double(settings: {strategy: 'messageable_user'})
allow(Canvas::Plugin).to receive(:find).and_return(plugin)
expect(AddressBook.for(user_model)).to be_a(AddressBook::MessageableUser)
end
it "returns an instance of AddressBook::Empty for 'empty' strategy" do
plugin = double(settings: {strategy: 'empty'})
allow(Canvas::Plugin).to receive(:find).and_return(plugin)
expect(AddressBook.for(user_model)).to be_a(AddressBook::Empty)
end
it "defaults to an instance of AddressBook::MessageableUser for invalid strategy" do
plugin = double(settings: {strategy: 'invalid'})
allow(Canvas::Plugin).to receive(:find).and_return(plugin)
expect(AddressBook.for(user_model)).to be_a(AddressBook::MessageableUser)
end

View File

@ -1,461 +0,0 @@
#
# Copyright (C) 2016 - 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_relative "../../spec_helper"
require_dependency "services/address_book"
module Services
describe AddressBook do
before do
@app_host = "address-book"
@secret = "opensesame"
allow(Canvas::DynamicSettings).to receive(:find).with(any_args).and_call_original
allow(Canvas::DynamicSettings).to receive(:find).
with("address-book", default_ttl: 5.minutes).
and_return({ "app-host" => @app_host, "secret" => Canvas::Security.base64_encode(@secret) })
@sender = user_model
@course = course_model
@course2 = course_model
end
def expect_request(url_matcher, options={})
body = options[:body] || { records: [] }
header_matcher = options[:headers] || anything
expect(CanvasHttp).to receive(:get).
with(url_matcher, header_matcher).
and_return(double(body: body.to_json, code: 200))
end
def stub_response(url_matcher, body, options={})
status = options[:status] || 200
header_matcher = options[:headers] || anything
allow(CanvasHttp).to receive(:get).
with(url_matcher, header_matcher).
and_return(double(body: body.to_json, code: status))
end
let(:example_response) do
{
records: [
{ user_id: '10000000000002', cursor: 8, contexts: [
{ 'context_type' => 'course', 'context_id' => '10000000000001', 'roles' => ['TeacherEnrollment'] }
]},
{ user_id: '10000000000005', cursor: 12, contexts: [
{ 'context_type' => 'course', 'context_id' => '10000000000002', 'roles' => ['StudentEnrollment'] },
{ 'context_type' => 'group', 'context_id' => '10000000000001', 'roles' => ['Member'] }
]}
]
}
end
def not_match(*args)
::RSpec::Matchers::AliasedNegatedMatcher.new(match(*args), ->{})
end
matcher :with_param do |param, value|
match do |url|
if value.is_a?(Array)
integers = value.first.is_a?(Integer)
char = integers ? '\d' : '\w'
return false unless url =~ %r{[?&]#{param}=(#{char}+(%2C#{char}+)*)(?:&|$)}
actual = $1.split('%2C')
actual = actual.map(&:to_i) if value.first.is_a?(Integer)
return actual.sort == value.sort
else
url =~ %r{[?&]#{param}=#{value}(?:&|$)}
end
end
end
matcher :with_param_present do |param|
match do |url|
url =~ %r{[?&]#{param}=}
end
end
describe "jwt" do
it "signs with the base64 decoded secret from the configuration" do
jwt = Services::AddressBook.jwt
expect(lambda{ Canvas::Security.decode_jwt(jwt, [@secret]) }).not_to raise_exception
end
it "includes current time as ait claim" do
Timecop.freeze do
jwt = Services::AddressBook.jwt
claims = Canvas::Security.decode_jwt(jwt, [@secret])
expect(Time.at(claims[:iat])).to be_within(1).of(Time.now)
end
end
end
describe "recipients" do
it "includes an Authorization header with JWT in request" do
Timecop.freeze do
jwt = Services::AddressBook.jwt
expect_request(anything, headers: hash_including('Authorization' => %r{Bearer #{jwt}}))
Services::AddressBook.recipients(sender: @sender)
end
end
it "makes request from /recipients in service" do
expect_request(%r{^#{@app_host}/recipients\?})
Services::AddressBook.recipients(sender: @sender)
end
it "normalizes sender from a User to its global ID as for_sender param" do
expect_request(with_param(:for_sender, @sender.global_id))
Services::AddressBook.recipients(sender: @sender)
end
it "normalizes sender from an ID to a global ID as for_sender param" do
expect_request(with_param(:for_sender, @sender.global_id))
Services::AddressBook.recipients(sender: @sender.id)
end
it "includes the sender's visible accounts" do
account1 = account_model
account2 = account_model
account_admin_user(user: @sender, account: account1)
account_admin_user(user: @sender, account: account2)
expect_request(with_param(:visible_account_ids, [account1.global_id, account2.global_id]))
Services::AddressBook.recipients(sender: @sender)
end
it "includes the sender's restricted courses" do
course1 = course_with_observer(user: @sender, active_all: true).course
course2 = course_with_observer(user: @sender, active_all: true).course
expect_request(with_param(:restricted_course_ids, [course1.global_id, course2.global_id]))
Services::AddressBook.recipients(sender: @sender)
end
it "normalizes context from e.g. a Course to its global asset string as in_context param" do
expect_request(with_param(:in_context, @course.global_asset_string))
Services::AddressBook.recipients(context: @course)
end
it "normalizes context from an asset string to a global asset string as in_context param" do
expect_request(with_param(:in_context, @course.global_asset_string))
Services::AddressBook.recipients(context: @course.asset_string)
end
it "normalizes context from a scoped asset string to a scoped global asset string as in_context param" do
expect_request(with_param(:in_context, "#{@course.global_asset_string}_students"))
Services::AddressBook.recipients(context: "#{@course.asset_string}_students")
end
it "normalizes user_ids from Users to a comma-separated list of their global IDs as user_ids param" do
recipient1 = user_model
recipient2 = user_model
expect_request(with_param(:user_ids, [recipient1.global_id, recipient2.global_id]))
Services::AddressBook.recipients(user_ids: [recipient1, recipient2])
end
it "normalizes user_ids from IDs to a comma-separated list of global IDs as user_ids param" do
recipient1 = user_model
recipient2 = user_model
expect_request(with_param(:user_ids, [recipient1.global_id, recipient2.global_id]))
Services::AddressBook.recipients(user_ids: [recipient1.id, recipient2.id])
end
it "normalizes exclude_ids from Users to a comma-separated list of their global IDs as exclude_ids param" do
recipient1 = user_model
recipient2 = user_model
expect_request(with_param(:exclude_ids, [recipient1.global_id, recipient2.global_id]))
Services::AddressBook.recipients(exclude_ids: [recipient1, recipient2])
end
it "normalizes exclude_ids from IDs to a comma-separated list of global IDs as exclude_ids param" do
recipient1 = user_model
recipient2 = user_model
expect_request(with_param(:exclude_ids, [recipient1.global_id, recipient2.global_id]))
Services::AddressBook.recipients(exclude_ids: [recipient1.id, recipient2.id])
end
it "normalizes weak_checks to 1 if truthy" do
expect_request(with_param(:weak_checks, 1))
Services::AddressBook.recipients(weak_checks: true)
end
it "omits weak_checks if falsey" do
expect_request(not_match(with_param_present(:weak_checks)))
Services::AddressBook.recipients(weak_checks: false)
end
it "normalizes ignore_result to 1 if truthy" do
expect_request(with_param(:ignore_result, 1))
Services::AddressBook.recipients(ignore_result: true)
end
it "reshapes results returned from service endpoint" do
stub_response(anything, example_response)
result = Services::AddressBook.recipients(@sender)
expect(result.user_ids).to eql([ 10000000000002, 10000000000005 ])
expect(result.common_contexts).to eql({
10000000000002 => {
courses: { 10000000000001 => ['TeacherEnrollment'] },
groups: {}
},
10000000000005 => {
courses: { 10000000000002 => ['StudentEnrollment'] },
groups: { 10000000000001 => ['Member'] }
}
})
expect(result.cursors).to eql([ 8, 12 ])
end
it "uses timeout protection and returns sane value on timeout" do
allow(Canvas).to receive(:redis_enabled?).and_return(true)
allow(Canvas).to receive(:redis).and_return(double)
allow(Canvas.redis).to receive(:get).with("service:timeouts:address_book:error_count").and_return(4)
expect(Rails.logger).to receive(:error).with("Skipping service call due to error count: address_book 4")
result = nil
expect { result = Services::AddressBook.recipients(@sender) }.not_to raise_error
expect(result.user_ids).to eq([])
expect(result.common_contexts).to eq({})
expect(result.cursors).to eq([])
end
it "reads separate timeout setting when ignoring result (for performance tapping)" do
allow(Canvas).to receive(:redis_enabled?).and_return(true)
allow(Canvas).to receive(:redis).and_return(double)
allow(Canvas.redis).to receive(:get).with("service:timeouts:address_book_performance_tap:error_count").and_return(4)
expect(Rails.logger).to receive(:error).with("Skipping service call due to error count: address_book_performance_tap 4")
result = nil
expect { result = Services::AddressBook.recipients(sender: @sender, ignore_result: true) }.not_to raise_error
expect(result.user_ids).to eq([])
expect(result.common_contexts).to eq({})
expect(result.cursors).to eq([])
end
it "returns empty response when ignoring result, regardless of what service returns" do
stub_response(anything, example_response)
result = Services::AddressBook.recipients(sender: @sender, ignore_result: true)
expect(result.user_ids).to eql([])
expect(result.common_contexts).to eql({})
expect(result.cursors).to eql([])
end
it "reports errors in service request but then returns sane value" do
stub_response(anything, { 'errors' => { 'something' => 'went wrong' } }, status: 400)
expect(Canvas::Errors).to receive(:capture)
result = nil
expect { result = Services::AddressBook.recipients(@sender) }.not_to raise_error
expect(result.user_ids).to eq([])
expect(result.common_contexts).to eq({})
expect(result.cursors).to eq([])
end
end
describe "count_recipients" do
before do
@count = 5
@response = { 'counts' => { @course.global_asset_string => @count } }
end
it "makes request from /recipients/counts in service" do
expect_request(%r{^#{@app_host}/recipients/counts\?})
Services::AddressBook.count_recipients(sender: @sender, contexts: [@course])
end
it "normalizes sender same as recipients" do
expect_request(with_param(:for_sender, @sender.global_id))
Services::AddressBook.count_recipients(sender: @sender, contexts: [@course])
end
it "normalizes contexts comma separated" do
expect_request(with_param(:in_contexts, [@course.global_asset_string, @course2.global_asset_string]))
Services::AddressBook.count_recipients(contexts: [@course, @course2])
end
it "extracts counts from response from service endpoint" do
stub_response(anything, @response)
counts = Services::AddressBook.count_recipients(sender: @sender, contexts: [@course])
expect(counts).to eql({ @course.global_asset_string => @count })
end
end
describe "common_contexts" do
it "makes a recipient request" do
expect_request(%r{/recipients\?})
Services::AddressBook.common_contexts(@sender, [1, 2, 3])
end
it "passes the sender to the recipients call" do
expect_request(with_param_present(:for_sender))
Services::AddressBook.common_contexts(@sender, [1, 2, 3])
end
it "passes the user_ids to the recipients call" do
recipient1 = user_model
recipient2 = user_model
expect_request(with_param(:user_ids, [recipient1.global_id, recipient2.global_id]))
Services::AddressBook.common_contexts(@sender, [recipient1.id, recipient2.id])
end
end
describe "roles_in_context" do
it "makes a recipient request with no sender" do
expect_request(%r{/recipients\?})
expect_request(with_param_present(:for_sender)).never
Services::AddressBook.roles_in_context(@course, [1, 2, 3])
end
it "passes the user_ids to the recipients call" do
recipient1 = user_model
recipient2 = user_model
expect(recipient1.global_id).to eql(Shard.global_id_for(recipient1.id))
expect_request(with_param(:user_ids, [recipient1.global_id, recipient2.global_id]))
Services::AddressBook.roles_in_context(@course, [recipient1.id, recipient2.id])
end
it "passes the context to the recipients call" do
expect_request(with_param(:in_context, @course.global_asset_string))
Services::AddressBook.roles_in_context(@course, [1, 2, 3])
end
it "uses the course as the context for a course section" do
expect_request(with_param(:in_context, @course.global_asset_string))
Services::AddressBook.roles_in_context(@course.default_section, [1, 2, 3])
end
end
describe "known_in_context" do
it "makes a recipient request" do
expect_request(%r{/recipients\?})
Services::AddressBook.known_in_context(@sender, @course.asset_string)
end
it "passes the sender to the recipients call" do
expect_request(with_param(:for_sender, @sender.global_id))
Services::AddressBook.known_in_context(@sender, @course.asset_string)
end
it "passes the user_ids, if any, to the recipients call" do
recipient1 = user_model
recipient2 = user_model
expect_request(with_param(:user_ids, [recipient1.global_id, recipient2.global_id]))
Services::AddressBook.known_in_context(@sender, @course.asset_string, [recipient1, recipient2])
end
it "passes the context to the recipients call" do
expect_request(with_param(:in_context, @course.global_asset_string))
Services::AddressBook.known_in_context(@sender, @course.asset_string)
end
it "returns an ordered list of ids and a hash of contexts per id" do
stub_response(anything, example_response)
user_ids, common_contexts = Services::AddressBook.known_in_context(@sender, @course.asset_string)
expect(user_ids).to eql([ 10000000000002, 10000000000005 ])
expect(common_contexts).to eql({
10000000000002 => {
courses: { 10000000000001 => ['TeacherEnrollment'] },
groups: {}
},
10000000000005 => {
courses: { 10000000000002 => ['StudentEnrollment'] },
groups: { 10000000000001 => ['Member'] }
}
})
end
end
describe "count_in_contexts" do
before do
@count = 5
@response = { 'counts' => { @course.global_asset_string => @count } }
end
it "makes a recipient/counts request" do
expect_request(%r{/recipients/counts\?})
Services::AddressBook.count_in_contexts(@sender, [@course.asset_string])
end
it "passes the sender to the count_recipients call" do
expect_request(with_param(:for_sender, @sender.global_id))
Services::AddressBook.count_in_contexts(@sender, [@course.asset_string])
end
it "passes the contexts to the count_recipients call" do
expect_request(with_param(:in_contexts, [@course.global_asset_string, @course2.global_asset_string]))
Services::AddressBook.count_in_contexts(@sender, [@course.asset_string, @course2.asset_string])
end
it "returns the counts from count_recipients call mapped to arguments" do
expect_request(anything, body: @response)
counts = Services::AddressBook.count_in_contexts(@sender, [@course.asset_string])
expect(counts).to eql({ @course.asset_string => @count })
end
end
describe "search_users" do
it "makes a recipient request" do
expect_request(%r{/recipients\?})
Services::AddressBook.search_users(@sender, {search: 'bob'}, {})
end
it "only has search parameter by default" do
expect_request(with_param(:search, 'bob'))
expect_request(with_param_present(:in_context)).never
expect_request(with_param_present(:exclude_ids)).never
expect_request(with_param_present(:weak_checks)).never
Services::AddressBook.search_users(@sender, {search: 'bob'}, {})
end
it "passes context to recipients call" do
expect_request(with_param_present(:in_context))
Services::AddressBook.search_users(@sender, {search: 'bob', context: @course}, {})
end
it "passes exclude_ids to recipients call" do
expect_request(with_param_present(:exclude_ids))
Services::AddressBook.search_users(@sender, {search: 'bob', exclude_ids: [1, 2, 3]}, {})
end
it "passes weak_checks flag along to recipients" do
expect_request(with_param_present(:weak_checks))
Services::AddressBook.search_users(@sender, {search: 'bob', weak_checks: true}, {})
end
it "returns ids, contexts, and cursors" do
stub_response(anything, example_response)
user_ids, common_contexts, cursors = Services::AddressBook.search_users(@sender, {search: 'bob'}, {})
expect(user_ids).to eql([ 10000000000002, 10000000000005 ])
expect(common_contexts).to eql({
10000000000002 => {
courses: { 10000000000001 => ['TeacherEnrollment'] },
groups: {}
},
10000000000005 => {
courses: { 10000000000002 => ['StudentEnrollment'] },
groups: { 10000000000001 => ['Member'] }
}
})
expect(cursors).to eql([ 8, 12 ])
end
it "passes cursor parameter to the service" do
expect_request(with_param(:cursor, 12))
Services::AddressBook.search_users(@sender, {search: 'bob'}, {cursor: 12})
end
it "passes per_page parameter to the service" do
expect_request(with_param(:per_page, 20))
Services::AddressBook.search_users(@sender, {search: 'bob'}, {per_page: 20})
end
end
end
end