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:
parent
a7e547cd7d
commit
3fcec37017
|
@ -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)
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue