Improve Zendesk import when DNS resolution fails etc.

This commit is contained in:
Ryan Lue 2018-12-14 02:59:19 +01:00
parent fb612f34d1
commit 30beecd3e7
14 changed files with 142 additions and 79 deletions

View file

@ -26,7 +26,7 @@ module Import
def attribute_config(object, name, attribute) def attribute_config(object, name, attribute)
{ {
object: object, object: object.to_s,
name: name, name: name,
display: attribute.title, display: attribute.title,
data_type: data_type(attribute), data_type: data_type(attribute),

View file

@ -40,7 +40,18 @@ class Sequencer
def resource_iteration(&block) def resource_iteration(&block)
resource_collection.public_send(resource_iteration_method, &block) resource_collection.public_send(resource_iteration_method, &block)
rescue ZendeskAPI::Error::NetworkError => e rescue ZendeskAPI::Error::NetworkError => e
return if e.response.status.to_s == '403' && resource_klass.in?(%w[UserField OrganizationField]) case e.response.status.to_s
when '403'
return if resource_klass.in?(%w[UserField OrganizationField])
when /^5\d\d$/
raise if (fail_count ||= 1) > 10
logger.error e
logger.info "Sleeping 10 seconds after ZendeskAPI::Error::NetworkError and retry (##{fail_count}/10)."
sleep 10
(fail_count += 1) && retry
end
raise raise
end end

View file

@ -0,0 +1,46 @@
require 'rails_helper'
RSpec.shared_examples Import::Zendesk::ObjectAttribute::Base do
let(:attribute) do
double(
title: 'Example attribute',
description: 'Example attribute description',
removable: false,
active: true,
position: 12,
visible_in_portal: true,
required_in_portal: true,
required: true,
type: 'input',
custom_field_options: [],
regexp_for_validation: '',
)
end
describe 'exception handling' do
let(:error_text) { Faker::Lorem.sentence }
it 'extends ObjectManager Attribute exception message' do
expect(ObjectManager::Attribute).to receive(:add).and_raise(RuntimeError, error_text)
expect do
described_class.new('Ticket', 'example_field', attribute)
end.to raise_error(RuntimeError, /'example_field': #{error_text}$/)
end
end
describe 'argument handling' do
it 'takes an ObjectLookup name as the first argument' do
expect(ObjectManager::Attribute)
.to receive(:add).with(hash_including(object: 'Ticket'))
described_class.new('Ticket', 'example_field', attribute)
end
it 'accepts a constant ObjectLookup name' do
expect(ObjectManager::Attribute)
.to receive(:add).with(hash_including(object: 'Ticket'))
described_class.new(Ticket, 'example_field', attribute)
end
end
end

View file

@ -1,34 +0,0 @@
require 'rails_helper'
RSpec.describe Import::Zendesk::ObjectAttribute::Base do
it 'extends ObjectManager Attribute exception text' do
attribute = double(
title: 'Example attribute',
description: 'Example attribute description',
removable: false,
active: true,
position: 12,
visible_in_portal: true,
required_in_portal: true,
required: true,
type: 'input',
)
error_text = 'some error'
expect(ObjectManager::Attribute).to receive(:add).and_raise(RuntimeError, error_text)
exception = nil
begin
described_class.new('Ticket', 'example_field', attribute)
rescue => e
exception = e
end
expect(exception).not_to be nil
expect(exception).to be_a(RuntimeError)
expect(exception.message).to include(error_text)
expect(exception.message).not_to eq(error_text)
end
end

View file

@ -1,6 +1,8 @@
require 'rails_helper' require 'rails_helper'
require 'lib/import/zendesk/object_attribute/base_examples'
RSpec.describe Import::Zendesk::ObjectAttribute::Checkbox do RSpec.describe Import::Zendesk::ObjectAttribute::Checkbox do
it_behaves_like Import::Zendesk::ObjectAttribute::Base
it 'imports boolean object attribute from checkbox object field' do it 'imports boolean object attribute from checkbox object field' do

View file

@ -1,9 +1,11 @@
require 'rails_helper' require 'rails_helper'
require 'lib/import/zendesk/object_attribute/base_examples'
# required due to some of rails autoloading issues # required due to some of rails autoloading issues
require 'import/zendesk/object_attribute/date' require 'import/zendesk/object_attribute/date'
RSpec.describe Import::Zendesk::ObjectAttribute::Date do RSpec.describe Import::Zendesk::ObjectAttribute::Date do
it_behaves_like Import::Zendesk::ObjectAttribute::Base
it 'imports date object attribute from date object field' do it 'imports date object attribute from date object field' do

View file

@ -1,6 +1,8 @@
require 'rails_helper' require 'rails_helper'
require 'lib/import/zendesk/object_attribute/base_examples'
RSpec.describe Import::Zendesk::ObjectAttribute::Decimal do RSpec.describe Import::Zendesk::ObjectAttribute::Decimal do
it_behaves_like Import::Zendesk::ObjectAttribute::Base
it 'imports input object attribute from decimal object field' do it 'imports input object attribute from decimal object field' do

View file

@ -1,6 +1,8 @@
require 'rails_helper' require 'rails_helper'
require 'lib/import/zendesk/object_attribute/base_examples'
RSpec.describe Import::Zendesk::ObjectAttribute::Dropdown do RSpec.describe Import::Zendesk::ObjectAttribute::Dropdown do
it_behaves_like Import::Zendesk::ObjectAttribute::Base
it 'imports select object attribute from dropdown object field' do it 'imports select object attribute from dropdown object field' do

View file

@ -1,9 +1,11 @@
require 'rails_helper' require 'rails_helper'
require 'lib/import/zendesk/object_attribute/base_examples'
# required due to some of rails autoloading issues # required due to some of rails autoloading issues
require 'import/zendesk/object_attribute/integer' require 'import/zendesk/object_attribute/integer'
RSpec.describe Import::Zendesk::ObjectAttribute::Integer do RSpec.describe Import::Zendesk::ObjectAttribute::Integer do
it_behaves_like Import::Zendesk::ObjectAttribute::Base
it 'imports integer object attribute from integer object field' do it 'imports integer object attribute from integer object field' do

View file

@ -1,9 +1,11 @@
require 'rails_helper' require 'rails_helper'
require 'lib/import/zendesk/object_attribute/base_examples'
# required due to some of rails autoloading issues # required due to some of rails autoloading issues
require 'import/zendesk/object_attribute/regexp' require 'import/zendesk/object_attribute/regexp'
RSpec.describe Import::Zendesk::ObjectAttribute::Regexp do RSpec.describe Import::Zendesk::ObjectAttribute::Regexp do
it_behaves_like Import::Zendesk::ObjectAttribute::Base
it 'imports input object attribute from regexp object field' do it 'imports input object attribute from regexp object field' do

View file

@ -1,6 +1,8 @@
require 'rails_helper' require 'rails_helper'
require 'lib/import/zendesk/object_attribute/base_examples'
RSpec.describe Import::Zendesk::ObjectAttribute::Tagger do RSpec.describe Import::Zendesk::ObjectAttribute::Tagger do
it_behaves_like Import::Zendesk::ObjectAttribute::Base
it 'imports select object attribute from tagger object field' do it 'imports select object attribute from tagger object field' do

View file

@ -1,6 +1,8 @@
require 'rails_helper' require 'rails_helper'
require 'lib/import/zendesk/object_attribute/base_examples'
RSpec.describe Import::Zendesk::ObjectAttribute::Text do RSpec.describe Import::Zendesk::ObjectAttribute::Text do
it_behaves_like Import::Zendesk::ObjectAttribute::Base
it 'imports input object attribute from text object field' do it 'imports input object attribute from text object field' do

View file

@ -1,6 +1,8 @@
require 'rails_helper' require 'rails_helper'
require 'lib/import/zendesk/object_attribute/base_examples'
RSpec.describe Import::Zendesk::ObjectAttribute::Textarea do RSpec.describe Import::Zendesk::ObjectAttribute::Textarea do
it_behaves_like Import::Zendesk::ObjectAttribute::Base
it 'imports input object attribute from textarea object field' do it 'imports input object attribute from textarea object field' do

View file

@ -1,61 +1,83 @@
RSpec.shared_examples 'Sequencer::Unit::Import::Zendesk::SubSequence::Base' do RSpec.shared_examples 'Sequencer::Unit::Import::Zendesk::SubSequence::Base' do
before do describe 'error handling' do
allow(params[:client]).to receive(collection_name).and_return(client_collection) before do
allow(client_collection).to receive(:all!).and_raise(api_error) allow(params[:client]).to receive(collection_name).and_return(client_collection)
end allow(client_collection).to receive(:all!).and_raise(api_error)
end
let(:params) do let(:params) do
{ {
dry_run: false, dry_run: false,
import_job: instance_double(ImportJob), import_job: instance_double(ImportJob),
client: double('ZendeskAPI'), client: double('ZendeskAPI'),
group_map: {}, # required by Tickets group_map: {}, # required by Tickets
organization_map: {}, # required by Tickets organization_map: {}, # required by Tickets
ticket_field_map: {}, # required by Tickets ticket_field_map: {}, # required by Tickets
user_map: {}, # required by Tickets user_map: {}, # required by Tickets
} }
end end
let(:collection_name) { described_class.name.demodulize.snakecase.to_sym } let(:collection_name) { described_class.name.demodulize.snakecase.to_sym }
let(:client_collection) { double('ZendeskAPI::Collection') } let(:client_collection) { double('ZendeskAPI::Collection') }
let(:api_error) { ZendeskAPI::Error::NetworkError.new('Mock err msg', response_obj) } let(:api_error) { ZendeskAPI::Error::NetworkError.new('Mock err msg', response_obj) }
let(:response_obj) { double('Faraday::Response') }
# https://github.com/zammad/zammad/issues/2262 let(:response_obj) do
context 'for lowest-tier Zendesk subscriptions ("Essential")' do # stubbed methods required for ZendeskAPI::Error::ClientError#to_s
shared_examples 'Zendesk import data (only available on Team tier and up)' do double('Faraday::Response', method: :get, url: 'https://example.com', status: 500)
context 'when API returns 403 forbidden during sync' do end
before { allow(response_obj).to receive(:status).and_return(403) }
it 'rescues the resulting exception' do # https://github.com/zammad/zammad/issues/2262
expect { process(params) }.not_to raise_error context 'for lowest-tier Zendesk subscriptions ("Essential")' do
shared_examples 'Zendesk import data (only available on Team tier and up)' do
context 'when API returns 403 forbidden during sync' do
before { allow(response_obj).to receive(:status).and_return(403) }
it 'rescues the resulting exception' do
expect { process(params) }.not_to raise_error
end
end
context 'when API returns other errors' do
# https://github.com/zammad/zammad/issues/2262
it 'does not rescue the resulting exception' do
expect do
process(params) do |unit|
allow(unit).to receive(:sleep) # stub out this method to speed up retry cycle
end
end
.to raise_error(api_error)
end
end end
end end
context 'when API returns other errors' do shared_examples 'Zendesk import data (available on all tiers)' do
before { allow(response_obj).to receive(:status).and_return(500) } context 'if API returns 403 forbidden during sync' do
before { allow(response_obj).to receive(:status).and_return(403) }
# https://github.com/zammad/zammad/issues/2262 it 'does not rescue the resulting exception' do
it 'does not rescue the resulting exception' do expect { process(params) }.to raise_error(api_error)
expect { process(params) }.to raise_error(api_error) end
end end
end end
if described_class.name.demodulize.in?(%w[UserFields OrganizationFields])
include_examples 'Zendesk import data (only available on Team tier and up)'
else
include_examples 'Zendesk import data (available on all tiers)'
end
end end
shared_examples 'Zendesk import data (available on all tiers)' do context 'when DNS resolution fails (getaddrinfo: nodename nor servname provided, or not known)' do
context 'if API returns 403 forbidden during sync' do it 'retries ten times, in 10s intervals' do
before { allow(response_obj).to receive(:status).and_return(403) } expect(client_collection)
.to receive(:all!).exactly(11).times
it 'does not rescue the resulting exception' do expect do
expect { process(params) }.to raise_error(api_error) process(params) do |unit|
end expect(unit).to receive(:sleep).with(10).exactly(10).times
end
end.to raise_error(api_error)
end end
end end
if described_class.name.demodulize.in?(%w[UserFields OrganizationFields])
include_examples 'Zendesk import data (only available on Team tier and up)'
else
include_examples 'Zendesk import data (available on all tiers)'
end
end end
end end