Improve Zendesk import when DNS resolution fails etc.
This commit is contained in:
parent
fb612f34d1
commit
30beecd3e7
14 changed files with 142 additions and 79 deletions
|
@ -26,7 +26,7 @@ module Import
|
|||
|
||||
def attribute_config(object, name, attribute)
|
||||
{
|
||||
object: object,
|
||||
object: object.to_s,
|
||||
name: name,
|
||||
display: attribute.title,
|
||||
data_type: data_type(attribute),
|
||||
|
|
|
@ -40,7 +40,18 @@ class Sequencer
|
|||
def resource_iteration(&block)
|
||||
resource_collection.public_send(resource_iteration_method, &block)
|
||||
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
|
||||
end
|
||||
|
|
46
spec/lib/import/zendesk/object_attribute/base_examples.rb
Normal file
46
spec/lib/import/zendesk/object_attribute/base_examples.rb
Normal 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
|
|
@ -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
|
|
@ -1,6 +1,8 @@
|
|||
require 'rails_helper'
|
||||
require 'lib/import/zendesk/object_attribute/base_examples'
|
||||
|
||||
RSpec.describe Import::Zendesk::ObjectAttribute::Checkbox do
|
||||
it_behaves_like Import::Zendesk::ObjectAttribute::Base
|
||||
|
||||
it 'imports boolean object attribute from checkbox object field' do
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
require 'rails_helper'
|
||||
require 'lib/import/zendesk/object_attribute/base_examples'
|
||||
|
||||
# required due to some of rails autoloading issues
|
||||
require 'import/zendesk/object_attribute/date'
|
||||
|
||||
RSpec.describe Import::Zendesk::ObjectAttribute::Date do
|
||||
it_behaves_like Import::Zendesk::ObjectAttribute::Base
|
||||
|
||||
it 'imports date object attribute from date object field' do
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
require 'rails_helper'
|
||||
require 'lib/import/zendesk/object_attribute/base_examples'
|
||||
|
||||
RSpec.describe Import::Zendesk::ObjectAttribute::Decimal do
|
||||
it_behaves_like Import::Zendesk::ObjectAttribute::Base
|
||||
|
||||
it 'imports input object attribute from decimal object field' do
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
require 'rails_helper'
|
||||
require 'lib/import/zendesk/object_attribute/base_examples'
|
||||
|
||||
RSpec.describe Import::Zendesk::ObjectAttribute::Dropdown do
|
||||
it_behaves_like Import::Zendesk::ObjectAttribute::Base
|
||||
|
||||
it 'imports select object attribute from dropdown object field' do
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
require 'rails_helper'
|
||||
require 'lib/import/zendesk/object_attribute/base_examples'
|
||||
|
||||
# required due to some of rails autoloading issues
|
||||
require 'import/zendesk/object_attribute/integer'
|
||||
|
||||
RSpec.describe Import::Zendesk::ObjectAttribute::Integer do
|
||||
it_behaves_like Import::Zendesk::ObjectAttribute::Base
|
||||
|
||||
it 'imports integer object attribute from integer object field' do
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
require 'rails_helper'
|
||||
require 'lib/import/zendesk/object_attribute/base_examples'
|
||||
|
||||
# required due to some of rails autoloading issues
|
||||
require 'import/zendesk/object_attribute/regexp'
|
||||
|
||||
RSpec.describe Import::Zendesk::ObjectAttribute::Regexp do
|
||||
it_behaves_like Import::Zendesk::ObjectAttribute::Base
|
||||
|
||||
it 'imports input object attribute from regexp object field' do
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
require 'rails_helper'
|
||||
require 'lib/import/zendesk/object_attribute/base_examples'
|
||||
|
||||
RSpec.describe Import::Zendesk::ObjectAttribute::Tagger do
|
||||
it_behaves_like Import::Zendesk::ObjectAttribute::Base
|
||||
|
||||
it 'imports select object attribute from tagger object field' do
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
require 'rails_helper'
|
||||
require 'lib/import/zendesk/object_attribute/base_examples'
|
||||
|
||||
RSpec.describe Import::Zendesk::ObjectAttribute::Text do
|
||||
it_behaves_like Import::Zendesk::ObjectAttribute::Base
|
||||
|
||||
it 'imports input object attribute from text object field' do
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
require 'rails_helper'
|
||||
require 'lib/import/zendesk/object_attribute/base_examples'
|
||||
|
||||
RSpec.describe Import::Zendesk::ObjectAttribute::Textarea do
|
||||
it_behaves_like Import::Zendesk::ObjectAttribute::Base
|
||||
|
||||
it 'imports input object attribute from textarea object field' do
|
||||
|
||||
|
|
|
@ -1,61 +1,83 @@
|
|||
RSpec.shared_examples 'Sequencer::Unit::Import::Zendesk::SubSequence::Base' do
|
||||
before do
|
||||
allow(params[:client]).to receive(collection_name).and_return(client_collection)
|
||||
allow(client_collection).to receive(:all!).and_raise(api_error)
|
||||
end
|
||||
describe 'error handling' do
|
||||
before do
|
||||
allow(params[:client]).to receive(collection_name).and_return(client_collection)
|
||||
allow(client_collection).to receive(:all!).and_raise(api_error)
|
||||
end
|
||||
|
||||
let(:params) do
|
||||
{
|
||||
dry_run: false,
|
||||
import_job: instance_double(ImportJob),
|
||||
client: double('ZendeskAPI'),
|
||||
group_map: {}, # required by Tickets
|
||||
organization_map: {}, # required by Tickets
|
||||
ticket_field_map: {}, # required by Tickets
|
||||
user_map: {}, # required by Tickets
|
||||
}
|
||||
end
|
||||
let(:params) do
|
||||
{
|
||||
dry_run: false,
|
||||
import_job: instance_double(ImportJob),
|
||||
client: double('ZendeskAPI'),
|
||||
group_map: {}, # required by Tickets
|
||||
organization_map: {}, # required by Tickets
|
||||
ticket_field_map: {}, # required by Tickets
|
||||
user_map: {}, # required by Tickets
|
||||
}
|
||||
end
|
||||
|
||||
let(:collection_name) { described_class.name.demodulize.snakecase.to_sym }
|
||||
let(:client_collection) { double('ZendeskAPI::Collection') }
|
||||
let(:api_error) { ZendeskAPI::Error::NetworkError.new('Mock err msg', response_obj) }
|
||||
let(:response_obj) { double('Faraday::Response') }
|
||||
let(:collection_name) { described_class.name.demodulize.snakecase.to_sym }
|
||||
let(:client_collection) { double('ZendeskAPI::Collection') }
|
||||
let(:api_error) { ZendeskAPI::Error::NetworkError.new('Mock err msg', response_obj) }
|
||||
|
||||
# https://github.com/zammad/zammad/issues/2262
|
||||
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) }
|
||||
let(:response_obj) do
|
||||
# stubbed methods required for ZendeskAPI::Error::ClientError#to_s
|
||||
double('Faraday::Response', method: :get, url: 'https://example.com', status: 500)
|
||||
end
|
||||
|
||||
it 'rescues the resulting exception' do
|
||||
expect { process(params) }.not_to raise_error
|
||||
# https://github.com/zammad/zammad/issues/2262
|
||||
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
|
||||
|
||||
context 'when API returns other errors' do
|
||||
before { allow(response_obj).to receive(:status).and_return(500) }
|
||||
shared_examples 'Zendesk import data (available on all tiers)' do
|
||||
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
|
||||
expect { process(params) }.to raise_error(api_error)
|
||||
it 'does not rescue the resulting exception' do
|
||||
expect { process(params) }.to raise_error(api_error)
|
||||
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
|
||||
|
||||
shared_examples 'Zendesk import data (available on all tiers)' do
|
||||
context 'if API returns 403 forbidden during sync' do
|
||||
before { allow(response_obj).to receive(:status).and_return(403) }
|
||||
context 'when DNS resolution fails (getaddrinfo: nodename nor servname provided, or not known)' do
|
||||
it 'retries ten times, in 10s intervals' do
|
||||
expect(client_collection)
|
||||
.to receive(:all!).exactly(11).times
|
||||
|
||||
it 'does not rescue the resulting exception' do
|
||||
expect { process(params) }.to raise_error(api_error)
|
||||
end
|
||||
expect do
|
||||
process(params) do |unit|
|
||||
expect(unit).to receive(:sleep).with(10).exactly(10).times
|
||||
end
|
||||
end.to raise_error(api_error)
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue