Skip to content

Commit

Permalink
feat(xero): Add integration services
Browse files Browse the repository at this point in the history
  • Loading branch information
ivannovosad committed Jun 6, 2024
1 parent 8503272 commit 3385169
Show file tree
Hide file tree
Showing 10 changed files with 321 additions and 15 deletions.
8 changes: 5 additions & 3 deletions app/jobs/integrations/aggregator/perform_sync_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ class PerformSyncJob < ApplicationJob

retry_on LagoHttpClient::HttpError, wait: :exponentially_longer, attempts: 3

def perform(integration:)
def perform(integration:, sync_tax_items: false)
sync_result = Integrations::Aggregator::SyncService.call(integration:)
sync_result.raise_if_error!

items_result = Integrations::Aggregator::ItemsService.call(integration:)
items_result.raise_if_error!

tax_items_result = Integrations::Aggregator::TaxItemsService.call(integration:)
tax_items_result.raise_if_error!
if sync_tax_items
tax_items_result = Integrations::Aggregator::TaxItemsService.call(integration:)
tax_items_result.raise_if_error!
end
end
end
end
Expand Down
5 changes: 4 additions & 1 deletion app/services/integrations/netsuite/create_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ def call(**args)

if integration.type == 'Integrations::NetsuiteIntegration'
Integrations::Aggregator::SendRestletEndpointJob.perform_later(integration:)
Integrations::Aggregator::PerformSyncJob.set(wait: 2.seconds).perform_later(integration:)
Integrations::Aggregator::PerformSyncJob.set(wait: 2.seconds).perform_later(
integration:,
sync_tax_items: true
)
end

result.integration = integration
Expand Down
5 changes: 4 additions & 1 deletion app/services/integrations/netsuite/update_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ def call

if integration.type == 'Integrations::NetsuiteIntegration' && integration.script_endpoint_url != old_script_url
Integrations::Aggregator::SendRestletEndpointJob.perform_later(integration:)
Integrations::Aggregator::PerformSyncJob.set(wait: 2.seconds).perform_later(integration:)
Integrations::Aggregator::PerformSyncJob.set(wait: 2.seconds).perform_later(
integration:,
sync_tax_items: true
)
end

result.integration = integration
Expand Down
33 changes: 33 additions & 0 deletions app/services/integrations/xero/create_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

module Integrations
module Xero
class CreateService < BaseService
def call(**args)
organization = Organization.find_by(id: args[:organization_id])

unless organization.premium_integrations.include?('xero')
return result.not_allowed_failure!(code: 'premium_integration_missing')
end

integration = Integrations::XeroIntegration.new(
organization:,
name: args[:name],
code: args[:code],
connection_id: args[:connection_id]
)

integration.save!

if integration.type == 'Integrations::XeroIntegration'
Integrations::Aggregator::PerformSyncJob.set(wait: 2.seconds).perform_later(integration:)
end

result.integration = integration
result
rescue ActiveRecord::RecordInvalid => e
result.record_validation_failure!(record: e.record)
end
end
end
end
36 changes: 36 additions & 0 deletions app/services/integrations/xero/update_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

module Integrations
module Xero
class UpdateService < BaseService
def initialize(integration:, params:)
@integration = integration
@params = params

super
end

def call
return result.not_found_failure!(resource: 'integration') unless integration

unless integration.organization.premium_integrations.include?('xero')
return result.not_allowed_failure!(code: 'premium_integration_missing')
end

integration.name = params[:name] if params.key?(:name)
integration.code = params[:code] if params.key?(:code)

integration.save!

result.integration = integration
result
rescue ActiveRecord::RecordInvalid => e
result.record_validation_failure!(record: e.record)
end

private

attr_reader :integration, :params
end
end
end
4 changes: 4 additions & 0 deletions spec/factories/integrations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,9 @@
type { 'Integrations::XeroIntegration' }
code { 'xero' }
name { 'Xero Integration' }

secrets do
{connection_id: SecureRandom.uuid}.to_json
end
end
end
57 changes: 47 additions & 10 deletions spec/jobs/integrations/aggregator/perform_sync_job_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
require 'rails_helper'

RSpec.describe Integrations::Aggregator::PerformSyncJob, type: :job do
subject(:perform_sync_job) { described_class }
subject(:perform_sync_job) { described_class.perform_now(integration:, sync_tax_items:) }

let(:sync_service) { instance_double(Integrations::Aggregator::SyncService) }
let(:items_service) { instance_double(Integrations::Aggregator::ItemsService) }
Expand All @@ -20,20 +20,57 @@

allow(Integrations::Aggregator::TaxItemsService).to receive(:new).and_return(tax_items_service)
allow(tax_items_service).to receive(:call).and_return(result)

perform_sync_job
end

it 'calls the aggregator sync service' do
described_class.perform_now(integration:)
context 'when sync_tax_items is true' do
let(:sync_tax_items) { true }

it 'calls the aggregator sync service' do
aggregate_failures do
expect(Integrations::Aggregator::SyncService).to have_received(:new)
expect(sync_service).to have_received(:call)
end
end

aggregate_failures do
expect(Integrations::Aggregator::SyncService).to have_received(:new)
expect(sync_service).to have_received(:call)
it 'calls the aggregator items service' do
aggregate_failures do
expect(Integrations::Aggregator::ItemsService).to have_received(:new)
expect(items_service).to have_received(:call)
end
end

expect(Integrations::Aggregator::ItemsService).to have_received(:new)
expect(items_service).to have_received(:call)
it 'calls the aggregator tax items service' do
aggregate_failures do
expect(Integrations::Aggregator::TaxItemsService).to have_received(:new)
expect(tax_items_service).to have_received(:call)
end
end
end

context 'when sync_tax_items is false' do
let(:sync_tax_items) { false }

it 'calls the aggregator sync service' do
aggregate_failures do
expect(Integrations::Aggregator::SyncService).to have_received(:new)
expect(sync_service).to have_received(:call)
end
end

it 'calls the aggregator items service' do
aggregate_failures do
expect(Integrations::Aggregator::ItemsService).to have_received(:new)
expect(items_service).to have_received(:call)
end
end

expect(Integrations::Aggregator::TaxItemsService).to have_received(:new)
expect(tax_items_service).to have_received(:call)
it 'does not call the aggregator tax items service' do
aggregate_failures do
expect(Integrations::Aggregator::TaxItemsService).not_to have_received(:new)
expect(tax_items_service).not_to have_received(:call)
end
end
end
end
10 changes: 10 additions & 0 deletions spec/services/integrations/aggregator/items_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,14 @@
end
end
end

describe '#action_path' do
subject(:action_path_call) { items_service.action_path }

let(:action_path) { 'v1/netsuite/items' }

it 'returns the path' do
expect(subject).to eq(action_path)
end
end
end
93 changes: 93 additions & 0 deletions spec/services/integrations/xero/create_service_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Integrations::Xero::CreateService, type: :service do
let(:service) { described_class.new(membership.user) }
let(:membership) { create(:membership) }
let(:organization) { membership.organization }

describe '#call' do
subject(:service_call) { service.call(**create_args) }

let(:name) { 'Xero 1' }

let(:create_args) do
{
name:,
code: 'xero1',
organization_id: organization.id,
connection_id: 'conn1'
}
end

context 'without premium license' do
it 'does not create an integration' do
expect { service_call }.not_to change(Integrations::XeroIntegration, :count)
end

it 'returns an error' do
result = service_call

aggregate_failures do
expect(result).not_to be_success
expect(result.error).to be_a(BaseService::MethodNotAllowedFailure)
end
end
end

context 'with premium license' do
around { |test| lago_premium!(&test) }

context 'when xero premium integration is not present' do
it 'returns an error' do
result = service_call

aggregate_failures do
expect(result).not_to be_success
expect(result.error).to be_a(BaseService::MethodNotAllowedFailure)
end
end
end

context 'when xero premium integration is present' do
before do
organization.update!(premium_integrations: ['xero'])
end

context 'without validation errors' do
it 'creates an integration' do
expect { service_call }.to change(Integrations::XeroIntegration, :count).by(1)

integration = Integrations::XeroIntegration.order(:created_at).last
expect(integration.name).to eq(name)
end

it 'returns an integration in result object' do
result = service_call

expect(result.integration).to be_a(Integrations::XeroIntegration)
end

it 'calls Integrations::Aggregator::PerformSyncJob' do
expect { service_call }.to have_enqueued_job(Integrations::Aggregator::PerformSyncJob)
end
end

context 'with validation error' do
let(:name) { nil }

it 'returns an error' do
result = service_call

aggregate_failures do
expect(result).not_to be_success
expect(result.error).to be_a(BaseService::ValidationFailure)
expect(result.error.messages[:name]).to eq(['value_is_mandatory'])
end
end
end
end
end
end
end
85 changes: 85 additions & 0 deletions spec/services/integrations/xero/update_service_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Integrations::Xero::UpdateService, type: :service do
let(:integration) { create(:xero_integration, organization:) }
let(:organization) { membership.organization }
let(:membership) { create(:membership) }

describe '#call' do
subject(:service_call) { described_class.call(integration:, params: update_args) }

before { integration }

let(:name) { 'Xero 1' }

let(:update_args) do
{
name:,
code: 'xero1'
}
end

context 'without premium license' do
it 'returns an error' do
result = service_call

aggregate_failures do
expect(result).not_to be_success
expect(result.error).to be_a(BaseService::MethodNotAllowedFailure)
end
end
end

context 'with premium license' do
around { |test| lago_premium!(&test) }

context 'when xero premium integration is not present' do
it 'returns an error' do
result = service_call

aggregate_failures do
expect(result).not_to be_success
expect(result.error).to be_a(BaseService::MethodNotAllowedFailure)
end
end
end

context 'when xero premium integration is present' do
before do
organization.update!(premium_integrations: ['xero'])
end

context 'without validation errors' do
it 'updates an integration' do
service_call

integration = Integrations::XeroIntegration.order(updated_at: :desc).first
expect(integration.name).to eq(name)
end

it 'returns an integration in result object' do
result = service_call

expect(result.integration).to be_a(Integrations::XeroIntegration)
end
end

context 'with validation error' do
let(:name) { nil }

it 'returns an error' do
result = service_call

aggregate_failures do
expect(result).not_to be_success
expect(result.error).to be_a(BaseService::ValidationFailure)
expect(result.error.messages[:name]).to eq(['value_is_mandatory'])
end
end
end
end
end
end
end

0 comments on commit 3385169

Please sign in to comment.