Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add hubspot connection services #2597

Merged
merged 6 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions app/jobs/integrations/aggregator/send_private_app_token_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

module Integrations
module Aggregator
class SendPrivateAppTokenJob < ApplicationJob
queue_as 'integrations'

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

def perform(integration:)
result = Integrations::Aggregator::SendPrivateAppTokenService.call(integration:)
result.raise_if_error!
end
end
end
end
4 changes: 4 additions & 0 deletions app/services/integrations/aggregator/base_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ def provider
'xero'
when 'Integrations::AnrokIntegration'
'anrok'
when 'Integrations::HubspotIntegration'
'hubspot'
end
end

Expand All @@ -43,6 +45,8 @@ def provider_key
'xero'
when 'Integrations::AnrokIntegration'
'anrok'
when 'Integrations::HubspotIntegration'
'hubspot'
end
end

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

module Integrations
module Aggregator
class SendPrivateAppTokenService < BaseService
brunomiguelpinto marked this conversation as resolved.
Show resolved Hide resolved
def action_path
"connection/#{integration.connection_id}/metadata"
end

def call
return unless integration.type == 'Integrations::HubspotIntegration'
return unless integration.private_app_token

payload = {
privateAppToken: integration.private_app_token
}

response = http_client.post_with_response(payload, headers)
result.response = response

result
end

private

def headers
{
'Provider-Config-Key' => 'hubspot',
'Authorization' => "Bearer #{secret_key}"
}
end
end
end
end
44 changes: 44 additions & 0 deletions app/services/integrations/hubspot/create_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

module Integrations
module Hubspot
class CreateService < BaseService
attr_reader :params
def initialize(params:)
@params = params

super
end

def call
organization = Organization.find_by(id: params[:organization_id])

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

integration = Integrations::HubspotIntegration.new(
organization:,
name: params[:name],
code: params[:code],
connection_id: params[:connection_id],
private_app_token: params[:private_app_token],
default_targeted_object: params[:default_targeted_object],
sync_invoices: ActiveModel::Type::Boolean.new.cast(params[:sync_invoices]),
sync_subscriptions: ActiveModel::Type::Boolean.new.cast(params[:sync_subscriptions])
)

integration.save!

if integration.type == 'Integrations::HubspotIntegration'
Integrations::Aggregator::SendPrivateAppTokenJob.perform_later(integration:)
end

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

module Integrations
module Hubspot
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?('hubspot')
return result.not_allowed_failure!(code: 'premium_integration_missing')
end

old_private_app_token = integration.private_app_token

integration.name = params[:name] if params.key?(:name)
integration.code = params[:code] if params.key?(:code)
integration.private_app_token = params[:private_app_token] if params.key?(:private_app_token)
integration.default_targeted_object = params[:default_targeted_object] if params.key?(:default_targeted_object)
integration.sync_invoices = params[:sync_invoices] if params.key?(:sync_invoices)
integration.sync_subscriptions = params[:sync_subscriptions] if params.key?(:sync_subscriptions)

integration.save!

if integration.type == 'Integrations::HubspotIntegration' && integration.private_app_token != old_private_app_token
Integrations::Aggregator::SendPrivateAppTokenJob.perform_later(integration:)
end

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

private

attr_reader :integration, :params
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Integrations::Aggregator::SendPrivateAppTokenJob, type: :job do
describe '#perform' do
subject(:send_token_job) { described_class }

let(:send_token_service) { instance_double(Integrations::Aggregator::SendPrivateAppTokenService) }
let(:integration) { create(:hubspot_integration) }
let(:result) { BaseService::Result.new }

before do
allow(Integrations::Aggregator::SendPrivateAppTokenService).to receive(:new).and_return(send_token_service)
allow(send_token_service).to receive(:call).and_return(result)
end

it 'sends the private app token the hubspot' do
described_class.perform_now(integration:)

expect(Integrations::Aggregator::SendPrivateAppTokenService).to have_received(:new)
expect(send_token_service).to have_received(:call)
end
end
end
109 changes: 109 additions & 0 deletions spec/services/integrations/hubspot/create_service_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Integrations::Hubspot::CreateService, type: :service do
let(:membership) { create(:membership) }
let(:organization) { membership.organization }

describe '#call' do
subject(:service_call) { described_class.call(params: create_args) }

let(:name) { 'Hubspot 1' }
let(:script_endpoint_url) { Faker::Internet.url }

let(:create_args) do
{
name:,
code: 'hubspot1',
organization_id: organization.id,
connection_id: 'conn1',
private_app_token: 'token',
client_secret: 'secret',
default_targeted_object: "test",
sync_invoices: false,
sync_subscriptions: false
}
end

context 'without premium license' do
it 'does not create an integration' do
expect { service_call }.not_to change(Integrations::HubspotIntegration, :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 'with hubspot premium integration 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 'with hubspot premium integration present' do
before do
organization.update!(premium_integrations: ['hubspot'])
allow(Integrations::Aggregator::SendPrivateAppTokenJob).to receive(:perform_later)
end

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

integration = Integrations::HubspotIntegration.order(:created_at).last
expect(integration.name).to eq(name)
expect(integration.code).to eq(create_args[:code])
expect(integration.connection_id).to eq(create_args[:connection_id])
expect(integration.private_app_token).to eq(create_args[:private_app_token])
expect(integration.default_targeted_object).to eq(create_args[:default_targeted_object])
expect(integration.sync_invoices).to eq(create_args[:sync_invoices])
expect(integration.sync_subscriptions).to eq(create_args[:sync_subscriptions])
expect(integration.organization_id).to eq(organization.id)
end

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

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

it 'calls Integrations::Aggregator::SendPrivateAppTokenJob' do
service_call

integration = Integrations::HubspotIntegration.order(:created_at).last
expect(Integrations::Aggregator::SendPrivateAppTokenJob).to have_received(:perform_later).with(integration:)
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
93 changes: 93 additions & 0 deletions spec/services/integrations/hubspot/update_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::Hubspot::UpdateService, type: :service do
let(:integration) { create(:hubspot_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) { 'Hubspot 1' }
let(:update_args) do
{
name:,
code: 'hubspot1',
private_app_token: 'new_token'
}
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 'with hubspot premium integration 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 'with hubspot premium integration present' do
before do
organization.update!(premium_integrations: ['hubspot'])
allow(Integrations::Aggregator::SendPrivateAppTokenJob).to receive(:perform_later)
end

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

integration = Integrations::HubspotIntegration.order(:updated_at).last
expect(integration.name).to eq(name)
expect(integration.private_app_token).to eq(update_args[:private_app_token])
end

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

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

it 'calls Integrations::Aggregator::SendPrivateAppTokenJob' do
service_call

expect(Integrations::Aggregator::SendPrivateAppTokenJob).to have_received(:perform_later).with(integration:)
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
Loading