Skip to content

Commit

Permalink
feat(hubspot): Save portal id to hubspot integration
Browse files Browse the repository at this point in the history
  • Loading branch information
ivannovosad committed Oct 15, 2024
1 parent 7c71efc commit e3c19d6
Show file tree
Hide file tree
Showing 15 changed files with 295 additions and 5 deletions.
16 changes: 16 additions & 0 deletions app/jobs/integrations/hubspot/save_portal_id_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

module Integrations
module Hubspot
class SavePortalIdJob < ApplicationJob
queue_as 'integrations'

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

def perform(integration:)
result = Integrations::Hubspot::SavePortalIdService.call(integration:)
result.raise_if_error!
end
end
end
end
2 changes: 1 addition & 1 deletion app/models/integrations/hubspot_integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class HubspotIntegration < BaseIntegration

settings_accessors :default_targeted_object, :sync_subscriptions, :sync_invoices, :subscriptions_object_type_id,
:invoices_object_type_id, :companies_properties_version, :contacts_properties_version,
:subscriptions_properties_version, :invoices_properties_version
:subscriptions_properties_version, :invoices_properties_version, :portal_id
secrets_accessors :connection_id, :private_app_token

TARGETED_OBJECTS = %w[companies contacts].freeze
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

module Integrations
module Aggregator
class AccountInformationService < BaseService
def action_path
'v1/account-information'
end

def call
response = http_client.get(headers:)

result.account_information = OpenStruct.new(response)
result
end

private

def headers
{
'Connection-Id' => integration.connection_id,
'Authorization' => "Bearer #{secret_key}",
'Provider-Config-Key' => provider_key
}
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def action_path
def process_hash_result(body)
contact = body['succeededCompanies']&.first
contact_id = contact&.dig('id')
email = contact&.dig('lago_billing_email')
email = contact&.dig('email')

if contact_id
result.contact_id = contact_id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def deliver_success_webhook(customer:, webhook_code:)
def process_hash_result(body)
contact = body['succeededContacts']&.first
contact_id = contact&.dig('id')
email = contact&.dig('lago_billing_email')
email = contact&.dig('email')

if contact_id
result.contact_id = contact_id
Expand Down
1 change: 1 addition & 0 deletions app/services/integrations/hubspot/create_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def call
if integration.type == 'Integrations::HubspotIntegration'
Integrations::Aggregator::SendPrivateAppTokenJob.perform_later(integration:)
Integrations::Aggregator::SyncCustomObjectsAndPropertiesJob.perform_later(integration:)
Integrations::Hubspot::SavePortalIdJob.perform_later(integration:)
end

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

module Integrations
module Hubspot
class SavePortalIdService < BaseService
def initialize(integration:)
@integration = integration
super
end

def call
return result unless integration.type == 'Integrations::HubspotIntegration'
return result if integration.portal_id.present?

account_information_result = Integrations::Aggregator::AccountInformationService.call(integration:)

integration.update!(portal_id: account_information_result.account_information.id)

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

private

attr_reader :integration
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "1234567890",
"type": "STANDARD",
"timeZone": "US/Eastern",
"companyCurrency": "USD",
"additionalCurrencies": [],
"utcOffset": "-04:00",
"utcOffsetMilliseconds": -14400000,
"uiDomain": "app.hubspot.com",
"dataHostingLocation": "na1"
}
25 changes: 25 additions & 0 deletions spec/jobs/integrations/hubspot/save_portal_id_job_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Integrations::Hubspot::SavePortalIdJob, type: :job do
describe '#perform' do
subject(:job) { described_class }

let(:service) { instance_double(Integrations::Hubspot::SavePortalIdService) }
let(:integration) { create(:hubspot_integration) }
let(:result) { BaseService::Result.new }

before do
allow(Integrations::Hubspot::SavePortalIdService).to receive(:new).and_return(service)
allow(service).to receive(:call).and_return(result)
end

it 'saves portal id to the integration' do
described_class.perform_now(integration:)

expect(Integrations::Hubspot::SavePortalIdService).to have_received(:new)
expect(service).to have_received(:call)
end
end
end
7 changes: 7 additions & 0 deletions spec/models/integrations/hubspot_integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@
end
end

describe '#portal_id' do
it 'assigns and retrieve a setting' do
hubspot_integration.portal_id = '123456789'
expect(hubspot_integration.portal_id).to eq('123456789')
end
end

describe '#sync_invoices' do
it 'assigns and retrieve a setting' do
hubspot_integration.sync_invoices = true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Integrations::Aggregator::AccountInformationService do
subject(:account_information_service) { described_class.new(integration:) }

let(:integration) { create(:hubspot_integration) }

describe '.call' do
let(:lago_client) { instance_double(LagoHttpClient::Client) }
let(:endpoint) { 'https://api.nango.dev/v1/account-information' }

let(:headers) do
{
'Connection-Id' => integration.connection_id,
'Authorization' => "Bearer #{ENV["NANGO_SECRET_KEY"]}",
'Provider-Config-Key' => 'hubspot'
}
end

let(:aggregator_response) do
path = Rails.root.join('spec/fixtures/integration_aggregator/account_information_response.json')
JSON.parse(File.read(path))
end

before do
allow(LagoHttpClient::Client).to receive(:new).with(endpoint).and_return(lago_client)
allow(lago_client).to receive(:get).with(headers:).and_return(aggregator_response)
end

it 'successfully fetches account information' do
result = account_information_service.call
account_information = result.account_information

aggregate_failures do
expect(LagoHttpClient::Client).to have_received(:new).with(endpoint)
expect(lago_client).to have_received(:get)
expect(account_information.id).to eq('1234567890')
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
RSpec.describe Integrations::Aggregator::Companies::CreateService do
subject(:service_call) { described_class.call(integration:, customer:, subsidiary_id:) }

let(:service) { described_class.new(integration:, customer:, subsidiary_id:) }
let(:customer) { create(:customer, :with_same_billing_and_shipping_address, organization:) }
let(:subsidiary_id) { '1' }
let(:organization) { create(:organization) }
Expand Down Expand Up @@ -304,4 +305,84 @@
end
end
end

describe '#process_hash_result' do
subject(:process_hash_result) { service.send(:process_hash_result, body) }

let(:result) { service.instance_variable_get(:@result) }
let(:integration) { create(:hubspot_integration, organization:) }
let(:integration_type) { 'hubspot' }
let(:integration_type_key) { 'hubspot' }

before do
allow(service).to receive(:deliver_error_webhook)
end

context 'when contact is successfully created' do
let(:body) do
{
'succeededContacts' => [
{
'id' => '2e50c200-9a54-4a66-b241-1e75fb87373f',
'email' => 'billing@example.com'
}
]
}
end

it 'sets the contact_id and email in the result' do
process_hash_result

expect(result.contact_id).to eq('2e50c200-9a54-4a66-b241-1e75fb87373f')
expect(result.email).to eq('billing@example.com')
end
end

context 'when contact creation fails' do
let(:body) do
{
'failedContacts' => [
{
'validation_errors' => [
{'Message' => 'Email is invalid'},
{'Message' => 'Name is required'}
]
}
]
}
end

it 'delivers an error webhook' do
process_hash_result

expect(service).to have_received(:deliver_error_webhook).with(
customer:,
code: 'Validation error',
message: 'Email is invalid. Name is required'
)
end
end

context 'when there is a general error' do
let(:body) do
{
'error' => {
'payload' => {
'message' => 'An unexpected error occurred'
}
}
}
end

it 'delivers an error webhook' do
process_hash_result

expect(service).to have_received(:deliver_error_webhook).with(
customer:,
code: 'Validation error',
message: 'An unexpected error occurred'
)
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@
subject(:process_hash_result) { service.send(:process_hash_result, body) }

let(:result) { service.instance_variable_get(:@result) }
let(:integration) { create(:netsuite_integration, organization:) }
let(:integration) { create(:hubspot_integration, organization:) }
let(:integration_type) { 'hubspot' }
let(:integration_type_key) { 'hubspot' }

Expand All @@ -350,7 +350,7 @@
'succeededContacts' => [
{
'id' => '2e50c200-9a54-4a66-b241-1e75fb87373f',
'lago_billing_email' => 'billing@example.com'
'email' => 'billing@example.com'
}
]
}
Expand Down
1 change: 1 addition & 0 deletions spec/services/integrations/hubspot/create_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
integration = Integrations::HubspotIntegration.order(:created_at).last
expect(Integrations::Aggregator::SendPrivateAppTokenJob).to have_received(:perform_later).with(integration:)
expect(Integrations::Aggregator::SyncCustomObjectsAndPropertiesJob).to have_received(:perform_later).with(integration:)
expect(Integrations::Hubspot::SavePortalIdJob).to have_received(:perform_later).with(integration:)
end
end

Expand Down
48 changes: 48 additions & 0 deletions spec/services/integrations/hubspot/save_portal_id_service_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Integrations::Hubspot::SavePortalIdService do
describe '#call' do
let(:portal_id) { '123456' }
let(:integration) { create(:hubspot_integration) }
let(:service_call) { described_class.call(integration:) }
let(:result) { BaseService::Result.new }
let(:account_information) { OpenStruct.new(id: portal_id) }

before do
result.account_information = account_information
allow(Integrations::Aggregator::AccountInformationService).to receive(:call).and_return(result)
end

context 'when the service is successful' do
it 'saves the portal ID to the integration' do
expect { service_call }.to change { integration.reload.portal_id }.to(portal_id)
end

it 'returns a success result' do
result = service_call
expect(result).to be_success
end
end

context 'when the service fails' do
before do
allow(integration).to receive(:update!).and_raise(ActiveRecord::RecordInvalid.new(integration))
end

it 'does not change the portal ID' do
expect { service_call }.not_to change { integration.reload.portal_id }
end

it 'returns an error message' do
result = service_call

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

0 comments on commit e3c19d6

Please sign in to comment.