Add bulk communication channel confirm endpoints

Fixes CNVS-28150

Test plan:
 - Add an unconfirmed communication channel
 - Hit /api/v1/accounts/.../unconfirmed_communication_channels.csv
   and ensure the channel shows up
 - Post to
   /api/v1/accounts/.../unconfirmed_communication_channels/confirm
 - Ensure the channel is now confirmed
 - Add an unconfirmed channel with an email address that doesn't
   contain an "@" sign
 - Hit /api/v1/accounts/...unconfirmed_communication_channels.csv
   and ensure the channel does not show up
 - Hit the above URL with ?with_invalid_paths=true appended and
   ensure the channel now shows up

Change-Id: Id7c3844e06e3fe997aed8baa363b5a9deababe97
Reviewed-on: https://gerrit.instructure.com/74926
Tested-by: Jenkins
Reviewed-by: Joel Hough <joel@instructure.com>
QA-Review: Deepeeca Soundarrajan <dsoundarrajan@instructure.com>
Product-Review: Alex Boyd <aboyd@instructure.com>
This commit is contained in:
Alex Boyd 2016-03-18 16:41:46 -06:00
parent a7e547cd7d
commit 3fcec37017
7 changed files with 531 additions and 365 deletions

View File

@ -472,27 +472,50 @@ class CommunicationChannelsController < ApplicationController
end
def bouncing_channel_report
if authorized_action(Account.site_admin, @current_user, :read_messages)
res = BulkBounceCountResetter.new(bouncing_channel_args).bouncing_channel_report
send_data(res, type: 'text/csv')
generate_bulk_report do
CommunicationChannel::BulkActions::ResetBounceCounts.new(bulk_action_args)
end
end
def bulk_reset_bounce_counts
if authorized_action(Account.site_admin, @current_user, :read_messages)
resetter = BulkBounceCountResetter.new(bouncing_channel_args)
resetter.send_later(:bulk_reset_bounce_counts)
render json: {scheduled_reset_approximate_count: resetter.count}
perform_bulk_action do
CommunicationChannel::BulkActions::ResetBounceCounts.new(bulk_action_args)
end
end
def unconfirmed_channel_report
generate_bulk_report do
CommunicationChannel::BulkActions::Confirm.new(bulk_action_args)
end
end
def bulk_confirm
perform_bulk_action do
CommunicationChannel::BulkActions::Confirm.new(bulk_action_args)
end
end
protected
def bouncing_channel_args
def bulk_action_args
account = params[:account_id] == 'self' ? @domain_root_account : Account.find(params[:account_id])
args = params.slice(:after, :before, :pattern).symbolize_keys
args = params.slice(:after, :before, :pattern, :with_invalid_paths, :path_type).symbolize_keys
args.merge!({account: account})
end
def generate_bulk_report
if authorized_action(Account.site_admin, @current_user, :read_messages)
action = yield
send_data(action.report, type: 'text/csv')
end
end
def perform_bulk_action
if authorized_action(Account.site_admin, @current_user, :read_messages)
action = yield
render json: action.perform!
end
end
def has_api_permissions?
@user == @current_user ||
@user.grants_right?(@current_user, session, :manage_user_details)

View File

@ -276,21 +276,17 @@ class CommunicationChannel < ActiveRecord::Base
}
def self.by_path_condition(path)
if %{mysql mysql2}.include?(connection_pool.spec.config[:adapter])
path
else
"LOWER(#{path})"
end
Arel::Nodes::NamedFunction.new('lower', [path])
end
scope :by_path, lambda { |path|
where("#{by_path_condition("communication_channels.path")}=#{by_path_condition("?")}", path)
}
scope :email, -> { where(:path_type => TYPE_EMAIL) }
scope :sms, -> { where(:path_type => TYPE_SMS) }
scope :by_path, ->(path) { where(by_path_condition(arel_table[:path]).eq(by_path_condition(path))) }
scope :path_like, ->(path) { where(by_path_condition(arel_table[:path]).matches(by_path_condition(path))) }
scope :active, -> { where(:workflow_state => 'active') }
scope :unretired, -> { where("communication_channels.workflow_state<>'retired'") }
scope :email, -> { where(path_type: TYPE_EMAIL) }
scope :sms, -> { where(path_type: TYPE_SMS) }
scope :active, -> { where(workflow_state: 'active') }
scope :unretired, -> { where.not(workflow_state: 'retired') }
scope :for_notification_frequency, lambda { |notification, frequency|
joins(:notification_policies).where(:notification_policies => { :notification_id => notification, :frequency => frequency })

View File

@ -0,0 +1,133 @@
#
# Copyright (C) 2015 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 CommunicationChannel
class BulkActions
# Maximum number of results to send back in a CSV report. The minimum of this and the individual reset action's
# bulk_limit will be used to limit the number of results returned.
REPORT_LIMIT = 10_000
attr_reader :account, :after, :before, :pattern, :path_type
def initialize(account:, after: nil, before: nil, pattern: nil, path_type: nil, with_invalid_paths: false)
@account, @pattern, @path_type, @with_invalid_paths = account, pattern, path_type, with_invalid_paths
@after = Time.zone.parse(after) if after
@before = Time.zone.parse(before) if before
end
def matching_channels(for_report: false)
ccs = CommunicationChannel.unretired
.where(user_id: User.of_account(account))
# Limit to self.class.bulk_limit, or REPORT_LIMIT if it's less and for_report is true
ccs = ccs.limit([(REPORT_LIMIT if for_report), self.class.bulk_limit].compact.min)
ccs = filter(ccs)
ccs = ccs.path_like(pattern.tr('*', '%')) if pattern
ccs = ccs.where(path_type: path_type) if path_type
ccs = ccs.where("path_type != 'email' or lower(path) LIKE '%_@%_.%_'") unless @with_invalid_paths
ccs
end
def count
Shackles.activate(:slave) do
matching_channels.count
end
end
def report
Shackles.activate(:slave) do
CSV.generate do |csv|
columns = self.class.report_columns
csv << [
I18n.t('User ID'),
I18n.t('Name'),
I18n.t('Communication channel ID'),
I18n.t('Type'),
I18n.t('Path')
] + columns.keys
matching_channels(for_report: true).preload(:user).each do |cc|
csv << [
cc.user.id,
cc.user.name,
cc.id,
cc.path_type,
cc.path_description
] + columns.values.map { |value_generator| value_generator.to_proc.call(cc) }
end
end
end
end
class ResetBounceCounts < BulkActions
def self.bulk_limit
1000
end
def self.report_columns
{
I18n.t('Date of most recent bounce') => :last_bounce_at,
I18n.t('Bounce reason') => :last_bounce_summary
}
end
def filter(ccs)
ccs = ccs.where('bounce_count > 0').order(:last_bounce_at)
ccs = ccs.where('last_bounce_at < ?', before) if before
ccs = ccs.where('last_bounce_at > ?', after) if after
ccs
end
def perform!
send_later(:reset_bounce_counts!)
{scheduled_reset_approximate_count: count}
end
private def reset_bounce_counts!
matching_channels.to_a.each(&:reset_bounce_count!)
end
end
class Confirm < BulkActions
def self.bulk_limit
10_000
end
def self.report_columns
{
I18n.t('Created at') => :created_at
}
end
def filter(ccs)
ccs = ccs.where(workflow_state: 'unconfirmed').order(:created_at)
ccs = ccs.where('created_at < ?', before) if before
ccs = ccs.where('created_at > ?', after) if after
ccs
end
def perform!
{confirmed_count: matching_channels.update_all(workflow_state: 'active')}
end
end
end
end

View File

@ -1297,6 +1297,8 @@ CanvasRails::Application.routes.draw do
post 'users/:user_id/communication_channels/:id', action: :reset_bounce_count, as: 'reset_bounce_count'
get 'accounts/:account_id/bouncing_communication_channels.csv', action: :bouncing_channel_report
post 'accounts/:account_id/bouncing_communication_channels/reset', action: :bulk_reset_bounce_counts
get 'accounts/:account_id/unconfirmed_communication_channels.csv', action: :unconfirmed_channel_report
post 'accounts/:account_id/unconfirmed_communication_channels/confirm', action: :bulk_confirm
delete 'users/:user_id/communication_channels/:id', action: :destroy
delete 'users/:user_id/communication_channels/:type/:address', action: :destroy, constraints: { address: %r{[^/?]+} }
end

View File

@ -4,8 +4,8 @@ class FixDuplicateCommunicationChannels < ActiveRecord::Migration
def self.up
CommunicationChannel.
group(CommunicationChannel.by_path_condition("path"), :path_type, :user_id).
select(["#{CommunicationChannel.by_path_condition("path")} AS path", :path_type, :user_id]).
group(CommunicationChannel.by_path_condition(CommunicationChannel.arel_table[:path]), :path_type, :user_id).
select([CommunicationChannel.by_path_condition(CommunicationChannel.arel_table[:path]).as('path'), :path_type, :user_id]).
having("COUNT(*) > 1").find_each do |baddie|
all = CommunicationChannel.where(user_id: baddie.user_id, path_type: baddie.path_type).
by_path(baddie.path).order("CASE workflow_state WHEN 'active' THEN 0 WHEN 'unconfirmed' THEN 1 ELSE 2 END", :created_at).to_a

View File

@ -1,82 +0,0 @@
#
# Copyright (C) 2015 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 BulkBounceCountResetter
# Maximum number of channels we'll process in API calls to bulk endpoints
# (bulk_reset_bounce_counts and bouncing_channel_report)
BULK_LIMIT = 1000
attr_reader :account, :after, :before, :pattern
def initialize(account:, after: nil, before: nil, pattern: nil)
@account, @pattern = account, pattern
@after = Time.zone.parse(after) if after
@before = Time.zone.parse(before) if before
end
def self.bulk_limit
BULK_LIMIT
end
def bouncing_channels_for_account
ccs = CommunicationChannel.unretired
.where(user_id: User.of_account(account))
.where('bounce_count > 0')
.order(:last_bounce_at)
.limit(self.class.bulk_limit)
ccs = ccs.where('last_bounce_at > ?', after) if after
ccs = ccs.where('last_bounce_at < ?', before) if before
ccs = ccs.where('path ILIKE ?', pattern.tr('*', '%')) if pattern
ccs
end
def count
bouncing_channels_for_account.count
end
def bulk_reset_bounce_counts
bouncing_channels_for_account.to_a.each(&:reset_bounce_count!).length
end
def bouncing_channel_report
Shackles.activate(:slave) do
CSV.generate do |csv|
csv << [
I18n.t('User ID'),
I18n.t('Name'),
I18n.t('Communication channel ID'),
I18n.t('Path'),
I18n.t('Date of most recent bounce'),
I18n.t('Bounce reason')
]
bouncing_channels_for_account.each do |cc|
csv << [
cc.user.id,
cc.user.name,
cc.id,
cc.path,
cc.last_bounce_at,
cc.last_bounce_summary
]
end
end
end
end
end

View File

@ -748,17 +748,7 @@ describe CommunicationChannelsController do
end
end
describe "GET 'bouncing_channel_report'" do
def channel_csv(cc)
[
cc.user.id.try(:to_s),
cc.user.name,
cc.id.try(:to_s),
cc.path,
cc.last_bounce_at.try(:to_s),
cc.last_bounce_summary.try(:to_s)
]
end
context 'bulk actions' do
def included_channels
CSV.parse(response.body).drop(1).map do |row|
@ -766,296 +756,400 @@ describe CommunicationChannelsController do
end
end
context 'as a site admin' do
before do
account_admin_user(account: Account.site_admin)
user_session(@user)
end
it 'fetches communication channels in this account and orders by date' do
now = Time.zone.now
u1 = user_with_pseudonym
u2 = user_with_pseudonym
c1 = u1.communication_channels.create!(path: 'one@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
cc.last_bounce_at = now
end
c2 = u1.communication_channels.create!(path: 'two@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 2
cc.last_bounce_at = now - 1.hour
end
c3 = u2.communication_channels.create!(path: 'three@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 3
cc.last_bounce_at = now + 1.hour
cc.last_bounce_details = {'bouncedRecipients' => [{'diagnosticCode' => 'stuff and things'}]}
end
get 'bouncing_channel_report', account_id: Account.default.id
expect(response).to have_http_status(:ok)
csv = CSV.parse(response.body)
expect(csv).to eq [
['User ID', 'Name', 'Communication channel ID', 'Path', 'Date of most recent bounce', 'Bounce reason'],
channel_csv(c2),
channel_csv(c1),
channel_csv(c3)
describe "GET 'bouncing_channel_report'" do
def channel_csv(cc)
[
cc.user.id.try(:to_s),
cc.user.name,
cc.id.try(:to_s),
cc.path_type,
cc.path_description,
cc.last_bounce_at.try(:to_s),
cc.last_bounce_summary.try(:to_s)
]
end
it 'ignores communication channels in other accounts' do
u1 = user_with_pseudonym
a = account_model
u2 = user_with_pseudonym(account: a)
c1 = u1.communication_channels.create!(path: 'one@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
end
u2.communication_channels.create!(path: 'two@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
context 'as a site admin' do
before do
account_admin_user(account: Account.site_admin)
user_session(@user)
end
get 'bouncing_channel_report', account_id: Account.default.id
it 'fetches communication channels in this account and orders by date' do
now = Time.zone.now
expect(included_channels).to eq([c1])
end
it "only reports active, bouncing communication channels" do
user_with_pseudonym
c1 = @user.communication_channels.create!(path: 'one@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
end
@user.communication_channels.create!(path: 'two@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
end
@user.communication_channels.create!(path: 'three@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'retired'
cc.bounce_count = 1
end
get 'bouncing_channel_report', account_id: Account.default.id
expect(included_channels).to eq([c1])
end
it 'uses the requested account' do
a = account_model
user_with_pseudonym(account: a)
c = @user.communication_channels.create!(path: 'one@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
end
get 'bouncing_channel_report', account_id: a.id
expect(included_channels).to eq([c])
end
it 'filters by date' do
user_with_pseudonym
@user.communication_channels.create!(path: 'one@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
cc.last_bounce_at = Time.zone.now - 1.day
end
c2 = @user.communication_channels.create!(path: 'two@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
cc.last_bounce_at = Time.zone.now
end
@user.communication_channels.create!(path: 'three@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
cc.last_bounce_at = Time.zone.now + 1.day
end
get 'bouncing_channel_report', account_id: Account.default.id,
before: Time.zone.now + 1.hour,
after: Time.zone.now - 1.hour
expect(included_channels).to eq([c2])
end
it 'filters by pattern, and case insensitively' do
user_with_pseudonym
# Uppercase "A" in the path to make sure it's matching case insensitively
c1 = @user.communication_channels.create!(path: 'bAr@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
end
@user.communication_channels.create!(path: 'foobar@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
end
get 'bouncing_channel_report', account_id: Account.default.id, pattern: 'bar*'
expect(included_channels).to eq([c1])
end
it 'limits to BulkBounceCountResetter.bulk_limit' do
BulkBounceCountResetter.stubs(:bulk_limit).returns(5)
now = Time.zone.now
user_with_pseudonym
ccs = (BulkBounceCountResetter.bulk_limit + 1).times.map do |n|
@user.communication_channels.create!(path: "c#{n}@example.com", path_type: 'email') do |cc|
u1 = user_with_pseudonym
u2 = user_with_pseudonym
c1 = u1.communication_channels.create!(path: 'one@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
cc.last_bounce_at = now + n.minutes
cc.last_bounce_at = now
end
c2 = u1.communication_channels.create!(path: 'two@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 2
cc.last_bounce_at = now - 1.hour
end
c3 = u2.communication_channels.create!(path: 'three@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 3
cc.last_bounce_at = now + 1.hour
cc.last_bounce_details = {'bouncedRecipients' => [{'diagnosticCode' => 'stuff and things'}]}
end
get 'bouncing_channel_report', account_id: Account.default.id
expect(response).to have_http_status(:ok)
csv = CSV.parse(response.body)
expect(csv).to eq [
['User ID', 'Name', 'Communication channel ID', 'Type', 'Path', 'Date of most recent bounce', 'Bounce reason'],
channel_csv(c2),
channel_csv(c1),
channel_csv(c3)
]
end
get 'bouncing_channel_report', account_id: Account.default.id
it 'ignores communication channels in other accounts' do
u1 = user_with_pseudonym
a = account_model
u2 = user_with_pseudonym(account: a)
expect(included_channels).to eq(ccs.first(BulkBounceCountResetter.bulk_limit))
end
end
c1 = u1.communication_channels.create!(path: 'one@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
end
u2.communication_channels.create!(path: 'two@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
end
context 'as a normal user' do
it "doesn't work" do
user_with_pseudonym
user_session(@user)
get 'bouncing_channel_report', account_id: Account.default.id
expect(response).to have_http_status(401)
end
end
end
get 'bouncing_channel_report', account_id: Account.default.id
describe "POST 'bulk_reset_bounce_counts'" do
context 'as a site admin' do
before do
account_admin_user(account: Account.site_admin)
user_session(@user)
end
it 'resets bounce counts' do
u1 = user_with_pseudonym
u2 = user_with_pseudonym
c1 = u1.communication_channels.create!(path: 'one@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
end
c2 = u1.communication_channels.create!(path: 'two@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 2
end
c3 = u2.communication_channels.create!(path: 'three@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 3
expect(included_channels).to eq([c1])
end
post 'bulk_reset_bounce_counts', account_id: Account.default.id
it "only reports active, bouncing communication channels" do
user_with_pseudonym
expect(response).to have_http_status(:ok)
[c1, c2, c3].each_with_index do |c,i|
expect(c.reload.bounce_count).to eq(i+1)
end
run_jobs
[c1, c2, c3].each do |c|
expect(c.reload.bounce_count).to eq(0)
end
end
c1 = @user.communication_channels.create!(path: 'one@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
end
@user.communication_channels.create!(path: 'two@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
end
@user.communication_channels.create!(path: 'three@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'retired'
cc.bounce_count = 1
end
it 'filters by date' do
user_with_pseudonym
get 'bouncing_channel_report', account_id: Account.default.id
c1 = @user.communication_channels.create!(path: 'one@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
cc.last_bounce_at = Time.zone.now - 1.day
end
c2 = @user.communication_channels.create!(path: 'two@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
cc.last_bounce_at = Time.zone.now
end
c3 = @user.communication_channels.create!(path: 'three@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
cc.last_bounce_at = Time.zone.now + 1.day
expect(included_channels).to eq([c1])
end
post 'bulk_reset_bounce_counts', account_id: Account.default.id,
it 'uses the requested account' do
a = account_model
user_with_pseudonym(account: a)
c = @user.communication_channels.create!(path: 'one@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
end
get 'bouncing_channel_report', account_id: a.id
expect(included_channels).to eq([c])
end
it 'filters by date' do
user_with_pseudonym
@user.communication_channels.create!(path: 'one@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
cc.last_bounce_at = Time.zone.now - 1.day
end
c2 = @user.communication_channels.create!(path: 'two@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
cc.last_bounce_at = Time.zone.now
end
@user.communication_channels.create!(path: 'three@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
cc.last_bounce_at = Time.zone.now + 1.day
end
get 'bouncing_channel_report', account_id: Account.default.id,
before: Time.zone.now + 1.hour,
after: Time.zone.now - 1.hour
run_jobs
expect(c1.reload.bounce_count).to eq(1)
expect(c2.reload.bounce_count).to eq(0)
expect(c3.reload.bounce_count).to eq(1)
end
it 'filters by pattern' do
user_with_pseudonym
c1 = @user.communication_channels.create!(path: 'bar@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
end
c2 = @user.communication_channels.create!(path: 'foobar@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
expect(included_channels).to eq([c2])
end
post 'bulk_reset_bounce_counts', account_id: Account.default.id, pattern: 'bar*'
it 'filters by pattern, and case insensitively' do
user_with_pseudonym
run_jobs
expect(c1.reload.bounce_count).to eq(0)
expect(c2.reload.bounce_count).to eq(1)
end
it 'respects the BULK_LIMIT' do
BulkBounceCountResetter.stubs(:bulk_limit).returns(5)
now = Time.zone.now
user_with_pseudonym
ccs = (BulkBounceCountResetter.bulk_limit + 1).times.map do |n|
@user.communication_channels.create!(path: "c#{n}@example.com", path_type: 'email') do |cc|
# Uppercase "A" in the path to make sure it's matching case insensitively
c1 = @user.communication_channels.create!(path: 'bAr@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
cc.last_bounce_at = now + n.minutes
end
@user.communication_channels.create!(path: 'foobar@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
end
get 'bouncing_channel_report', account_id: Account.default.id, pattern: 'bar*'
expect(included_channels).to eq([c1])
end
post 'bulk_reset_bounce_counts', account_id: Account.default.id
it 'limits to CommunicationChannel::BulkActions::ResetBounceCounts.bulk_limit' do
CommunicationChannel::BulkActions::ResetBounceCounts.stubs(:bulk_limit).returns(5)
now = Time.zone.now
run_jobs
ccs.each(&:reload)
expect(ccs[-1].bounce_count).to eq(1)
ccs.first(BulkBounceCountResetter.bulk_limit).each do |cc|
expect(cc.bounce_count).to eq(0)
user_with_pseudonym
ccs = (CommunicationChannel::BulkActions::ResetBounceCounts.bulk_limit + 1).times.map do |n|
@user.communication_channels.create!(path: "c#{n}@example.com", path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
cc.last_bounce_at = now + n.minutes
end
end
get 'bouncing_channel_report', account_id: Account.default.id
expect(included_channels).to eq(ccs.first(CommunicationChannel::BulkActions::ResetBounceCounts.bulk_limit))
end
end
context 'as a normal user' do
it "doesn't work" do
user_with_pseudonym
user_session(@user)
get 'bouncing_channel_report', account_id: Account.default.id
expect(response).to have_http_status(401)
end
end
end
context 'as a normal user' do
it "doesn't work" do
user_with_pseudonym
c = @user.communication_channels.create!(path: 'one@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
describe "POST 'bulk_reset_bounce_counts'" do
context 'as a site admin' do
before do
account_admin_user(account: Account.site_admin)
user_session(@user)
end
user_with_pseudonym
user_session(@user)
it 'resets bounce counts' do
u1 = user_with_pseudonym
u2 = user_with_pseudonym
c1 = u1.communication_channels.create!(path: 'one@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
end
c2 = u1.communication_channels.create!(path: 'two@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 2
end
c3 = u2.communication_channels.create!(path: 'three@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 3
end
post 'bulk_reset_bounce_counts', account_id: Account.default.id
post 'bulk_reset_bounce_counts', account_id: Account.default.id
expect(response).to have_http_status(401)
expect(c.reload.bounce_count).to eq(1)
expect(response).to have_http_status(:ok)
[c1, c2, c3].each_with_index do |c,i|
expect(c.reload.bounce_count).to eq(i+1)
end
run_jobs
[c1, c2, c3].each do |c|
expect(c.reload.bounce_count).to eq(0)
end
end
it 'filters by date' do
user_with_pseudonym
c1 = @user.communication_channels.create!(path: 'one@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
cc.last_bounce_at = Time.zone.now - 1.day
end
c2 = @user.communication_channels.create!(path: 'two@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
cc.last_bounce_at = Time.zone.now
end
c3 = @user.communication_channels.create!(path: 'three@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
cc.last_bounce_at = Time.zone.now + 1.day
end
post 'bulk_reset_bounce_counts', account_id: Account.default.id,
before: Time.zone.now + 1.hour,
after: Time.zone.now - 1.hour
run_jobs
expect(c1.reload.bounce_count).to eq(1)
expect(c2.reload.bounce_count).to eq(0)
expect(c3.reload.bounce_count).to eq(1)
end
it 'filters by pattern' do
user_with_pseudonym
c1 = @user.communication_channels.create!(path: 'bar@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
end
c2 = @user.communication_channels.create!(path: 'foobar@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
end
post 'bulk_reset_bounce_counts', account_id: Account.default.id, pattern: 'bar*'
run_jobs
expect(c1.reload.bounce_count).to eq(0)
expect(c2.reload.bounce_count).to eq(1)
end
it 'respects the BULK_LIMIT' do
CommunicationChannel::BulkActions::ResetBounceCounts.stubs(:bulk_limit).returns(5)
now = Time.zone.now
user_with_pseudonym
ccs = (CommunicationChannel::BulkActions::ResetBounceCounts.bulk_limit + 1).times.map do |n|
@user.communication_channels.create!(path: "c#{n}@example.com", path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
cc.last_bounce_at = now + n.minutes
end
end
post 'bulk_reset_bounce_counts', account_id: Account.default.id
run_jobs
ccs.each(&:reload)
expect(ccs[-1].bounce_count).to eq(1)
ccs.first(CommunicationChannel::BulkActions::ResetBounceCounts.bulk_limit).each do |cc|
expect(cc.bounce_count).to eq(0)
end
end
end
context 'as a normal user' do
it "doesn't work" do
user_with_pseudonym
c = @user.communication_channels.create!(path: 'one@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
cc.bounce_count = 1
end
user_with_pseudonym
user_session(@user)
post 'bulk_reset_bounce_counts', account_id: Account.default.id
expect(response).to have_http_status(401)
expect(c.reload.bounce_count).to eq(1)
end
end
end
context 'unconfirmed channels' do
context 'as a siteadmin' do
before do
account_admin_user(account: Account.site_admin)
user_session(@user)
user_with_pseudonym
@c1 = @user.communication_channels.create!(path: 'foo@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'unconfirmed'
end
@c2 = @user.communication_channels.create!(path: 'bar@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'unconfirmed'
end
@c3 = @user.communication_channels.create!(path: 'baz@example.com', path_type: 'email') do |cc|
cc.workflow_state = 'active'
end
@c4 = @user.communication_channels.create!(path: 'qux@.', path_type: 'email') do |cc|
cc.workflow_state = 'unconfirmed'
end
@c5 = @user.communication_channels.create!(path: '+18015550100', path_type: 'sms') do |cc|
cc.workflow_state = 'unconfirmed'
end
end
context "GET 'unconfirmed_channel_report'" do
it 'reports channels' do
get 'unconfirmed_channel_report', account_id: Account.default.id
expect(response).to have_http_status(:ok)
# can't expect to eq because we get stray channels for the users we created
expect(included_channels).to include(@c1, @c2, @c5)
expect(included_channels).to_not include(@c3, @c4)
end
it 'filters by path type' do
get 'unconfirmed_channel_report', account_id: Account.default.id, path_type: 'sms'
expect(response).to have_http_status(:ok)
expect(included_channels).to include(@c5)
expect(included_channels).to_not include(@c1, @c2, @c3, @c4)
end
end
context "POST 'bulk_confirm'" do
it 'confirms channels' do
post 'bulk_confirm', account_id: Account.default.id
expect(@c1.reload.workflow_state).to eq('active')
expect(@c2.reload.workflow_state).to eq('active')
end
it 'excludes channels with invalid paths' do
post 'bulk_confirm', account_id: Account.default.id
expect(@c4.reload.workflow_state).to eq('unconfirmed')
end
it 'includes channels with invalid paths if requested' do
post 'bulk_confirm', account_id: Account.default.id, with_invalid_paths: '1'
expect(@c1.reload.workflow_state).to eq('active')
expect(@c2.reload.workflow_state).to eq('active')
expect(@c4.reload.workflow_state).to eq('active')
end
end
end
context 'as a normal user' do
before do
user_with_pseudonym
user_session(@user)
end
context "GET 'unconfirmed_channel_report'" do
it "doesn't work" do
get 'unconfirmed_channel_report', account_id: Account.default.id
expect(response).to have_http_status(401)
end
end
context "POST 'bulk_confirm'" do
it "doesn't work" do
post 'bulk_confirm', account_id: Account.default.id
expect(response).to have_http_status(401)
end
end
end
end
end