diff --git a/app/jobs/integrations/aggregator/sync_custom_objects_and_properties_job.rb b/app/jobs/integrations/aggregator/sync_custom_objects_and_properties_job.rb new file mode 100644 index 00000000000..56a665febb8 --- /dev/null +++ b/app/jobs/integrations/aggregator/sync_custom_objects_and_properties_job.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Integrations + module Aggregator + class SyncCustomObjectsAndPropertiesJob < ApplicationJob + queue_as 'integrations' + + def perform(integration:) + Integrations::Hubspot::Invoices::DeployObjectJob.perform_later(integration:) + Integrations::Hubspot::Subscriptions::DeployObjectJob.perform_later(integration:) + Integrations::Hubspot::Companies::DeployPropertiesJob.perform_later(integration:) + Integrations::Hubspot::Contacts::DeployPropertiesJob.perform_later(integration:) + end + end + end +end diff --git a/app/jobs/integrations/hubspot/companies/deploy_properties_job.rb b/app/jobs/integrations/hubspot/companies/deploy_properties_job.rb new file mode 100644 index 00000000000..80f7e3cd2b4 --- /dev/null +++ b/app/jobs/integrations/hubspot/companies/deploy_properties_job.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Integrations + module Hubspot + module Companies + class DeployPropertiesJob < ApplicationJob + queue_as 'integrations' + + retry_on LagoHttpClient::HttpError, wait: :polynomially_longer, attempts: 3 + retry_on Integrations::Aggregator::RequestLimitError, wait: :polynomially_longer, attempts: 10 + + def perform(integration:) + result = Integrations::Hubspot::Companies::DeployPropertiesService.call(integration:) + result.raise_if_error! + end + end + end + end +end diff --git a/app/jobs/integrations/hubspot/contacts/deploy_properties_job.rb b/app/jobs/integrations/hubspot/contacts/deploy_properties_job.rb new file mode 100644 index 00000000000..4cd7a78f2a3 --- /dev/null +++ b/app/jobs/integrations/hubspot/contacts/deploy_properties_job.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Integrations + module Hubspot + module Contacts + class DeployPropertiesJob < ApplicationJob + queue_as 'integrations' + + retry_on LagoHttpClient::HttpError, wait: :polynomially_longer, attempts: 3 + retry_on Integrations::Aggregator::RequestLimitError, wait: :polynomially_longer, attempts: 10 + + def perform(integration:) + result = Integrations::Hubspot::Contacts::DeployPropertiesService.call(integration:) + result.raise_if_error! + end + end + end + end +end diff --git a/app/jobs/integrations/hubspot/invoices/deploy_object_job.rb b/app/jobs/integrations/hubspot/invoices/deploy_object_job.rb new file mode 100644 index 00000000000..8575b241868 --- /dev/null +++ b/app/jobs/integrations/hubspot/invoices/deploy_object_job.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Integrations + module Hubspot + module Invoices + class DeployObjectJob < ApplicationJob + queue_as 'integrations' + + retry_on LagoHttpClient::HttpError, wait: :polynomially_longer, attempts: 3 + retry_on Integrations::Aggregator::RequestLimitError, wait: :polynomially_longer, attempts: 10 + + def perform(integration:) + result = Integrations::Hubspot::Invoices::DeployObjectService.call(integration:) + result.raise_if_error! + end + end + end + end +end diff --git a/app/jobs/integrations/hubspot/subscriptions/deploy_object_job.rb b/app/jobs/integrations/hubspot/subscriptions/deploy_object_job.rb new file mode 100644 index 00000000000..9e2d76ce9af --- /dev/null +++ b/app/jobs/integrations/hubspot/subscriptions/deploy_object_job.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Integrations + module Hubspot + module Subscriptions + class DeployObjectJob < ApplicationJob + queue_as 'integrations' + + retry_on LagoHttpClient::HttpError, wait: :polynomially_longer, attempts: 3 + retry_on Integrations::Aggregator::RequestLimitError, wait: :polynomially_longer, attempts: 10 + + def perform(integration:) + result = Integrations::Hubspot::Subscriptions::DeployObjectService.call(integration:) + result.raise_if_error! + end + end + end + end +end diff --git a/app/jobs/send_webhook_job.rb b/app/jobs/send_webhook_job.rb index 0d3b226318f..18a2ab6235f 100644 --- a/app/jobs/send_webhook_job.rb +++ b/app/jobs/send_webhook_job.rb @@ -33,6 +33,7 @@ class SendWebhookJob < ApplicationJob 'credit_note.created' => Webhooks::CreditNotes::CreatedService, 'credit_note.generated' => Webhooks::CreditNotes::GeneratedService, 'credit_note.provider_refund_failure' => Webhooks::CreditNotes::PaymentProviderRefundFailureService, + 'integration.provider_error' => Webhooks::Integrations::ProviderErrorService, 'payment_provider.error' => Webhooks::PaymentProviders::ErrorService, 'payment_request.created' => Webhooks::PaymentRequests::CreatedService, "payment_request.payment_failure" => Webhooks::PaymentProviders::PaymentRequestPaymentFailureService, diff --git a/app/models/integrations/hubspot_integration.rb b/app/models/integrations/hubspot_integration.rb index 062c94786ed..fe5058915ae 100644 --- a/app/models/integrations/hubspot_integration.rb +++ b/app/models/integrations/hubspot_integration.rb @@ -4,10 +4,20 @@ module Integrations class HubspotIntegration < BaseIntegration validates :connection_id, :private_app_token, :default_targeted_object, presence: true - settings_accessors :default_targeted_object, :sync_subscriptions, :sync_invoices + 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 secrets_accessors :connection_id, :private_app_token - TARGETED_OBJECTS = %w[Companies Contacts] + TARGETED_OBJECTS = %w[companies contacts].freeze + + def companies_object_type_id + '0-2' + end + + def contacts_object_type_id + '0-1' + end end end diff --git a/app/serializers/v1/integrations/provider_error_serializer.rb b/app/serializers/v1/integrations/provider_error_serializer.rb new file mode 100644 index 00000000000..51c428906cc --- /dev/null +++ b/app/serializers/v1/integrations/provider_error_serializer.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module V1 + module Integrations + class ProviderErrorSerializer < ModelSerializer + def serialize + { + lago_integration_id: model.id, + provider: options[:provider], + provider_code: options[:provider_code], + provider_error: options[:provider_error] + } + end + end + end +end diff --git a/app/services/integrations/aggregator/base_service.rb b/app/services/integrations/aggregator/base_service.rb index 454ed225207..ea96d34c300 100644 --- a/app/services/integrations/aggregator/base_service.rb +++ b/app/services/integrations/aggregator/base_service.rb @@ -78,6 +78,19 @@ def deliver_error_webhook(customer:, code:, message:) ) end + def deliver_integration_error_webhook(integration:, code:, message:) + SendWebhookJob.perform_later( + 'integration.provider_error', + integration, + provider:, + provider_code: integration.code, + provider_error: { + message:, + error_code: code + } + ) + end + def deliver_tax_error_webhook(customer:, code:, message:) SendWebhookJob.perform_later( 'customer.tax_provider_error', diff --git a/app/services/integrations/hubspot/companies/deploy_properties_service.rb b/app/services/integrations/hubspot/companies/deploy_properties_service.rb new file mode 100644 index 00000000000..126ce4c78df --- /dev/null +++ b/app/services/integrations/hubspot/companies/deploy_properties_service.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +module Integrations + module Hubspot + module Companies + class DeployPropertiesService < Integrations::Aggregator::BaseService + VERSION = 1 + + def action_path + "v1/hubspot/properties" + end + + def call + return unless integration.type == 'Integrations::HubspotIntegration' + return result if integration.companies_properties_version == VERSION + response = nil + ActiveRecord::Base.transaction do + response = http_client.post_with_response(payload, headers) + integration.settings = integration.reload.settings + integration.companies_properties_version = VERSION + integration.save! + end + result.response = response + result + rescue LagoHttpClient::HttpError => e + message = message(e) + deliver_integration_error_webhook(integration:, code: 'integration_error', message:) + end + + private + + def headers + { + 'Provider-Config-Key' => 'hubspot', + 'Authorization' => "Bearer #{secret_key}", + 'Connection-Id' => integration.connection_id + } + end + + def payload + { + objectType: "companies", + inputs: [ + { + groupName: "companyinformation", + name: "lago_customer_id", + label: "Lago Customer Id", + type: "string", + fieldType: "text", + displayOrder: -1, + hasUniqueValue: true, + searchableInGlobalSearch: true, + formField: true + }, + { + groupName: "companyinformation", + name: "lago_customer_external_id", + label: "Lago Customer External Id", + type: "string", + fieldType: "text", + displayOrder: -1, + searchableInGlobalSearch: true, + formField: true + }, + { + groupName: "companyinformation", + name: "lago_billing_email", + label: "Lago Billing Email", + type: "string", + fieldType: "text", + searchableInGlobalSearch: true, + formField: true + }, + { + groupName: "companyinformation", + name: "lago_tax_identification_number", + label: "Lago Tax Identification Number", + type: "string", + fieldType: "text", + searchableInGlobalSearch: true, + formField: true + } + ] + }.freeze + end + end + end + end +end diff --git a/app/services/integrations/hubspot/contacts/deploy_properties_service.rb b/app/services/integrations/hubspot/contacts/deploy_properties_service.rb new file mode 100644 index 00000000000..f8eaac96d86 --- /dev/null +++ b/app/services/integrations/hubspot/contacts/deploy_properties_service.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +module Integrations + module Hubspot + module Contacts + class DeployPropertiesService < Integrations::Aggregator::BaseService + VERSION = 1 + + def action_path + "v1/hubspot/properties" + end + + def call + return unless integration.type == 'Integrations::HubspotIntegration' + return result if integration.contacts_properties_version == VERSION + response = nil + ActiveRecord::Base.transaction do + response = http_client.post_with_response(payload, headers) + integration.settings = integration.reload.settings + integration.contacts_properties_version = VERSION + integration.save! + end + result.response = response + result + rescue LagoHttpClient::HttpError => e + message = message(e) + deliver_integration_error_webhook(integration:, code: 'integration_error', message:) + end + + private + + def headers + { + 'Provider-Config-Key' => 'hubspot', + 'Authorization' => "Bearer #{secret_key}", + 'Connection-Id' => integration.connection_id + } + end + + def payload + { + objectType: "contacts", + inputs: [ + { + groupName: "contactinformation", + name: "lago_customer_id", + label: "Lago Customer Id", + type: "string", + fieldType: "text", + displayOrder: -1, + hasUniqueValue: true, + searchableInGlobalSearch: true, + formField: true + }, + { + groupName: "contactinformation", + name: "lago_customer_external_id", + label: "Lago Customer External Id", + type: "string", + fieldType: "text", + displayOrder: -1, + hasUniqueValue: true, + searchableInGlobalSearch: true, + formField: true + }, + { + groupName: "contactinformation", + name: "lago_billing_email", + label: "Lago Billing Email", + type: "string", + fieldType: "text", + searchableInGlobalSearch: true, + formField: true + } + ] + }.freeze + end + end + end + end +end diff --git a/app/services/integrations/hubspot/create_service.rb b/app/services/integrations/hubspot/create_service.rb index 570e379862b..1ed0b8326f1 100644 --- a/app/services/integrations/hubspot/create_service.rb +++ b/app/services/integrations/hubspot/create_service.rb @@ -32,6 +32,7 @@ def call if integration.type == 'Integrations::HubspotIntegration' Integrations::Aggregator::SendPrivateAppTokenJob.perform_later(integration:) + Integrations::Aggregator::SyncCustomObjectsAndPropertiesJob.perform_later(integration:) end result.integration = integration diff --git a/app/services/integrations/hubspot/invoices/deploy_object_service.rb b/app/services/integrations/hubspot/invoices/deploy_object_service.rb new file mode 100644 index 00000000000..5da9dfca39c --- /dev/null +++ b/app/services/integrations/hubspot/invoices/deploy_object_service.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true + +module Integrations + module Hubspot + module Invoices + class DeployObjectService < Integrations::Aggregator::BaseService + VERSION = 1 + + def action_path + "v1/hubspot/object" + end + + def call + return unless integration.type == 'Integrations::HubspotIntegration' + return result if integration.invoices_properties_version == VERSION + response = nil + ActiveRecord::Base.transaction do + response = http_client.post_with_response(payload, headers) + integration.settings = integration.reload.settings + integration.invoices_properties_version = VERSION + integration.save! + end + result.response = response + result + rescue LagoHttpClient::HttpError => e + message = message(e) + deliver_integration_error_webhook(integration:, code: 'integration_error', message:) + end + + private + + def headers + { + 'Provider-Config-Key' => 'hubspot', + 'Authorization' => "Bearer #{secret_key}", + 'Connection-Id' => integration.connection_id + } + end + + def payload + { + name: "LagoInvoices", + description: "Invoices issued by Lago billing engine", + requiredProperties: [ + "lago_invoice_id" + ], + labels: { + singular: "LagoInvoice", + plural: "LagoInvoices" + }, + primaryDisplayProperty: "lago_invoice_number", + secondaryDisplayProperties: %w[lago_invoice_status lago_invoice_id], + searchableProperties: %w[lago_invoice_number lago_invoice_id], + properties: [ + { + name: "lago_invoice_id", + label: "Lago Invoice Id", + type: "string", + fieldType: "text", + hasUniqueValue: true, + searchableInGlobalSearch: true + }, + { + name: "lago_invoice_number", + label: "Lago Invoice Number", + type: "string", + fieldType: "text", + searchableInGlobalSearch: true + }, + { + name: "lago_invoice_issuing_date", + label: "Lago Invoice Issuing Date", + type: "date", + fieldType: "date" + }, + { + name: "lago_invoice_payment_due_date", + label: "Lago Invoice Payment Due Date", + type: "date", + fieldType: "date" + }, + { + name: "lago_invoice_payment_overdue", + label: "Lago Invoice Payment Overdue", + groupName: "LagoInvoices", + type: "bool", + fieldType: "booleancheckbox", + options: [ + { + label: "True", + value: "true" + }, + { + label: "False", + value: "false" + } + ] + }, + { + name: "lago_invoice_type", + label: "Lago Invoice Type", + type: "string", + fieldType: "text" + }, + { + name: "lago_invoice_status", + label: "Lago Invoice Status", + type: "string", + fieldType: "text" + }, + { + name: "lago_invoice_payment_status", + label: "Lago Invoice Payment Status", + type: "string", + fieldType: "text" + }, + { + name: "lago_invoice_currency", + label: "Lago Invoice Currency", + type: "string", + fieldType: "text" + }, + { + name: "lago_invoice_total_amount", + label: "Lago Invoice Total Amount", + type: "number", + fieldType: "number" + }, + { + name: "lago_invoice_subtotal_excluding_taxes", + label: "Lago Invoice Subtotal Excluding Taxes", + type: "number", + fieldType: "number" + }, + { + name: "lago_invoice_file_url", + label: "Lago Invoice File URL", + type: "string", + fieldType: "file" + } + ], + associatedObjects: %w[COMPANY CONTACT] + } + end + end + end + end +end diff --git a/app/services/integrations/hubspot/invoices/deploy_properties_service.rb b/app/services/integrations/hubspot/invoices/deploy_properties_service.rb new file mode 100644 index 00000000000..d4635fe80d4 --- /dev/null +++ b/app/services/integrations/hubspot/invoices/deploy_properties_service.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module Integrations + module Hubspot + module Invoices + class DeployPropertiesService < Integrations::Aggregator::BaseService + VERSION = 1 + + def action_path + "v1/hubspot/properties" + end + + def call + return unless integration.type == 'Integrations::HubspotIntegration' + return result if integration.invoices_properties_version == VERSION + response = nil + ActiveRecord::Base.transaction do + response = http_client.post_with_response(payload, headers) + integration.settings = integration.reload.settings + integration.invoices_properties_version = VERSION + integration.save! + end + result.response = response + result + rescue LagoHttpClient::HttpError => e + message = message(e) + deliver_integration_error_webhook(integration:, code: 'integration_error', message:) + end + + private + + def headers + { + 'Provider-Config-Key' => 'hubspot', + 'Authorization' => "Bearer #{secret_key}", + 'Connection-Id' => integration.connection_id + } + end + + def payload + { + objectType: "LagoInvoices", + inputs: [ + { + groupName: "lagoinvoices_information", + name: "example", + label: "example label", + type: "string", + fieldType: "text" + } + ] + } + end + end + end + end +end diff --git a/app/services/integrations/hubspot/subscriptions/deploy_object_service.rb b/app/services/integrations/hubspot/subscriptions/deploy_object_service.rb new file mode 100644 index 00000000000..81cf0c8d30d --- /dev/null +++ b/app/services/integrations/hubspot/subscriptions/deploy_object_service.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +module Integrations + module Hubspot + module Subscriptions + class DeployObjectService < Integrations::Aggregator::BaseService + VERSION = 1 + + def action_path + "v1/hubspot/object" + end + + def call + return unless integration.type == 'Integrations::HubspotIntegration' + return result if integration.subscriptions_properties_version == VERSION + response = nil + ActiveRecord::Base.transaction do + response = http_client.post_with_response(payload, headers) + integration.settings = integration.reload.settings + integration.subscriptions_properties_version = VERSION + integration.save! + end + result.response = response + result + rescue LagoHttpClient::HttpError => e + message = message(e) + deliver_integration_error_webhook(integration:, code: 'integration_error', message:) + end + + private + + def headers + { + 'Provider-Config-Key' => 'hubspot', + 'Authorization' => "Bearer #{secret_key}", + 'Connection-Id' => integration.connection_id + } + end + + def payload + { + secondaryDisplayProperties: [ + "lago_external_subscription_id" + ], + requiredProperties: [ + "lago_subscription_id" + ], + searchableProperties: %w[lago_subscription_id lago_external_subscription_id], + name: "LagoSubscriptions", + associatedObjects: %w[COMPANY CONTACT], + properties: [ + { + name: "lago_subscription_id", + label: "Lago Subscription Id", + type: "string", + fieldType: "text", + hasUniqueValue: true, + searchableInGlobalSearch: true + }, + { + name: "lago_external_subscription_id", + label: "Lago External Subscription Id", + type: "string", + fieldType: "text", + searchableInGlobalSearch: true + }, + { + name: "lago_subscription_name", + label: "Lago Subscription Name", + type: "string", + fieldType: "text" + }, + { + name: "lago_subscription_plan_code", + label: "Lago Subscription Plan Code", + type: "string", + fieldType: "text" + }, + { + name: "lago_subscription_status", + label: "Lago Subscription Status", + type: "string", + fieldType: "text" + }, + { + name: "lago_subscription_created_at", + label: "Lago Subscription Created At", + type: "date", + fieldType: "date" + }, + { + name: "lago_subscription_started_at", + label: "Lago Subscription Started At", + type: "date", + fieldType: "date" + }, + { + name: "lago_subscription_ending_at", + label: "Lago Subscription Ending At", + type: "date", + fieldType: "date" + }, + { + name: "lago_subscription_at", + label: "Lago Subscription At", + type: "date", + fieldType: "date" + }, + { + name: "lago_subscription_terminated_at", + label: "Lago Subscription Terminated At", + type: "date", + fieldType: "date" + }, + { + name: "lago_subscription_trial_ended_at", + label: "Lago Subscription Trial Ended At", + type: "date", + fieldType: "date" + }, + { + name: "lago_billing_time", + label: "Lago Billing Time", + type: "enumeration", + fieldType: "radio", + displayOrder: -1, + hasUniqueValue: false, + searchableInGlobalSearch: true, + formField: true, + options: [ + { + label: "Calendar", + value: "calendar", + displayOrder: 1 + }, + { + label: "Anniversary", + value: "anniversary", + displayOrder: 2 + } + ] + } + ], + labels: { + singular: "LagoSubscription", + plural: "LagoSubscriptions" + }, + primaryDisplayProperty: "lago_subscription_id", + description: "string" + } + end + end + end + end +end diff --git a/app/services/integrations/hubspot/subscriptions/deploy_properties_service.rb b/app/services/integrations/hubspot/subscriptions/deploy_properties_service.rb new file mode 100644 index 00000000000..7fa87272e91 --- /dev/null +++ b/app/services/integrations/hubspot/subscriptions/deploy_properties_service.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module Integrations + module Hubspot + module Subscriptions + class DeployPropertiesService < Integrations::Aggregator::BaseService + VERSION = 1 + + def action_path + "v1/hubspot/properties" + end + + def call + return unless integration.type == 'Integrations::HubspotIntegration' + return result if integration.subscriptions_properties_version == VERSION + response = nil + ActiveRecord::Base.transaction do + response = http_client.post_with_response(payload, headers) + integration.settings = integration.reload.settings + integration.subscriptions_properties_version = VERSION + integration.save! + end + result.response = response + result + rescue LagoHttpClient::HttpError => e + message = message(e) + deliver_integration_error_webhook(integration:, code: 'integration_error', message:) + end + + private + + def headers + { + 'Provider-Config-Key' => 'hubspot', + 'Authorization' => "Bearer #{secret_key}", + 'Connection-Id' => integration.connection_id + } + end + + def payload + { + objectType: "LagoSubscriptions", + inputs: [ + { + groupName: "lagosubscriptions_information", + name: "example", + label: "example label", + type: "string", + fieldType: "text" + } + ] + } + end + end + end + end +end diff --git a/app/services/webhooks/integrations/provider_error_service.rb b/app/services/webhooks/integrations/provider_error_service.rb new file mode 100644 index 00000000000..29ad6c4fb82 --- /dev/null +++ b/app/services/webhooks/integrations/provider_error_service.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Webhooks + module Integrations + class ProviderErrorService < Webhooks::BaseService + private + + def current_organization + @current_organization ||= object.organization + end + + def object_serializer + ::V1::Integrations::ProviderErrorSerializer.new( + object, + root_name: object_type, + provider_error: options[:provider_error], + provider: options[:provider], + provider_code: options[:provider_code] + ) + end + + def webhook_type + 'integration.provider_error' + end + + def object_type + 'provider_error' + end + end + end +end diff --git a/schema.graphql b/schema.graphql index 3a0b8789c20..864b5f57ca8 100644 --- a/schema.graphql +++ b/schema.graphql @@ -6764,8 +6764,8 @@ type SyncIntegrationInvoicePayload { } enum TargetedObjectsEnum { - Companies - Contacts + companies + contacts } type Tax { diff --git a/schema.json b/schema.json index 4329372e164..761734e542d 100644 --- a/schema.json +++ b/schema.json @@ -35020,13 +35020,13 @@ "inputFields": null, "enumValues": [ { - "name": "Companies", + "name": "companies", "description": null, "isDeprecated": false, "deprecationReason": null }, { - "name": "Contacts", + "name": "contacts", "description": null, "isDeprecated": false, "deprecationReason": null diff --git a/spec/factories/integrations.rb b/spec/factories/integrations.rb index cbf155d7548..6f01830cbbc 100644 --- a/spec/factories/integrations.rb +++ b/spec/factories/integrations.rb @@ -60,7 +60,17 @@ name { 'Hubspot Integration' } settings do - {default_targeted_object: 'Companies', sync_subscriptions: true, sync_invoices: true} + { + default_targeted_object: 'companies', + sync_subscriptions: true, + sync_invoices: true, + subscriptions_object_type_id: Faker::Number.number(digits: 2), + invoices_object_type_id: Faker::Number.number(digits: 2), + companies_properties_version: 1, + contacts_properties_version: 1, + subscriptions_properties_version: 1, + invoices_properties_version: 1 + } end secrets do diff --git a/spec/graphql/mutations/integrations/hubspot/create_spec.rb b/spec/graphql/mutations/integrations/hubspot/create_spec.rb index 2f9718209cf..aed1d2906fd 100644 --- a/spec/graphql/mutations/integrations/hubspot/create_spec.rb +++ b/spec/graphql/mutations/integrations/hubspot/create_spec.rb @@ -45,7 +45,7 @@ code:, name:, connectionId: 'this-is-random-uuid', - defaultTargetedObject: 'Companies', + defaultTargetedObject: 'companies', privateAppToken: 'some-private-app-token' } } diff --git a/spec/graphql/types/integrations/hubspot/targeted_objects_enum_spec.rb b/spec/graphql/types/integrations/hubspot/targeted_objects_enum_spec.rb index e68c87dfe87..f741f1a8c9f 100644 --- a/spec/graphql/types/integrations/hubspot/targeted_objects_enum_spec.rb +++ b/spec/graphql/types/integrations/hubspot/targeted_objects_enum_spec.rb @@ -4,6 +4,6 @@ RSpec.describe Types::Integrations::Hubspot::TargetedObjectsEnum do it 'enumerizes the correct values' do - expect(described_class.values.keys).to match_array(%w[Companies Contacts]) + expect(described_class.values.keys).to match_array(%w[companies contacts]) end end diff --git a/spec/jobs/integrations/aggregator/sync_custom_objects_and_properties_job_spec.rb b/spec/jobs/integrations/aggregator/sync_custom_objects_and_properties_job_spec.rb new file mode 100644 index 00000000000..2ec9fd9eae2 --- /dev/null +++ b/spec/jobs/integrations/aggregator/sync_custom_objects_and_properties_job_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Integrations::Aggregator::SyncCustomObjectsAndPropertiesJob, type: :job do + describe '#perform' do + subject(:sync_job) { described_class } + + let(:integration) { create(:hubspot_integration) } + + before do + allow(Integrations::Hubspot::Subscriptions::DeployObjectJob).to receive(:perform_later) + allow(Integrations::Hubspot::Invoices::DeployObjectJob).to receive(:perform_later) + allow(Integrations::Hubspot::Companies::DeployPropertiesJob).to receive(:perform_later) + allow(Integrations::Hubspot::Contacts::DeployPropertiesJob).to receive(:perform_later) + end + + it 'schedules all jobs needed with the current integration' do + sync_job.perform_now(integration: integration) + + expect(Integrations::Hubspot::Subscriptions::DeployObjectJob).to have_received(:perform_later).with(integration:) + expect(Integrations::Hubspot::Invoices::DeployObjectJob).to have_received(:perform_later).with(integration:) + expect(Integrations::Hubspot::Companies::DeployPropertiesJob).to have_received(:perform_later).with(integration:) + expect(Integrations::Hubspot::Contacts::DeployPropertiesJob).to have_received(:perform_later).with(integration:) + end + end +end diff --git a/spec/jobs/integrations/hubspot/companies/deploy_properties_job_spec.rb b/spec/jobs/integrations/hubspot/companies/deploy_properties_job_spec.rb new file mode 100644 index 00000000000..9d78047a8a1 --- /dev/null +++ b/spec/jobs/integrations/hubspot/companies/deploy_properties_job_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Integrations::Hubspot::Companies::DeployPropertiesJob, type: :job do + describe '#perform' do + subject(:deploy_properties_job) { described_class } + + let(:deploy_company_service) { instance_double(Integrations::Hubspot::Companies::DeployPropertiesService) } + let(:integration) { create(:hubspot_integration) } + let(:result) { BaseService::Result.new } + + before do + allow(Integrations::Hubspot::Companies::DeployPropertiesService).to receive(:new).and_return(deploy_company_service) + allow(deploy_company_service).to receive(:call).and_return(result) + end + + it 'calls the DeployPropertiesService to sync companies custom properties' do + deploy_properties_job.perform_now(integration:) + expect(Integrations::Hubspot::Companies::DeployPropertiesService).to have_received(:new) + expect(deploy_company_service).to have_received(:call) + end + end +end diff --git a/spec/jobs/integrations/hubspot/contacts/deploy_properties_job_spec.rb b/spec/jobs/integrations/hubspot/contacts/deploy_properties_job_spec.rb new file mode 100644 index 00000000000..49ccddffdf8 --- /dev/null +++ b/spec/jobs/integrations/hubspot/contacts/deploy_properties_job_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Integrations::Hubspot::Contacts::DeployPropertiesJob, type: :job do + describe '#perform' do + subject(:deploy_properties_job) { described_class } + + let(:deploy_contact_service) { instance_double(Integrations::Hubspot::Contacts::DeployPropertiesService) } + let(:integration) { create(:hubspot_integration) } + let(:result) { BaseService::Result.new } + + before do + allow(Integrations::Hubspot::Contacts::DeployPropertiesService).to receive(:new).and_return(deploy_contact_service) + allow(deploy_contact_service).to receive(:call).and_return(result) + end + + it 'calls the DeployPropertiesService to sync contacts custom properties' do + deploy_properties_job.perform_now(integration:) + + expect(Integrations::Hubspot::Contacts::DeployPropertiesService).to have_received(:new) + expect(deploy_contact_service).to have_received(:call) + end + end +end diff --git a/spec/jobs/integrations/hubspot/invoices/deploy_object_job_spec.rb b/spec/jobs/integrations/hubspot/invoices/deploy_object_job_spec.rb new file mode 100644 index 00000000000..7e738f3b3b0 --- /dev/null +++ b/spec/jobs/integrations/hubspot/invoices/deploy_object_job_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Integrations::Hubspot::Invoices::DeployObjectJob, type: :job do + describe '#perform' do + subject(:deploy_object_job) { described_class } + + let(:deploy_object_service) { instance_double(Integrations::Hubspot::Invoices::DeployObjectService) } + let(:integration) { create(:hubspot_integration) } + let(:result) { BaseService::Result.new } + + before do + allow(Integrations::Hubspot::Invoices::DeployObjectService).to receive(:new).and_return(deploy_object_service) + allow(deploy_object_service).to receive(:call).and_return(result) + end + + it 'calls the DeployObjectService to deploy invoice custom object' do + deploy_object_job.perform_now(integration:) + + expect(Integrations::Hubspot::Invoices::DeployObjectService).to have_received(:new) + expect(deploy_object_service).to have_received(:call) + end + end +end diff --git a/spec/jobs/integrations/hubspot/subscriptions/deploy_object_job_spec.rb b/spec/jobs/integrations/hubspot/subscriptions/deploy_object_job_spec.rb new file mode 100644 index 00000000000..e5ab95d0353 --- /dev/null +++ b/spec/jobs/integrations/hubspot/subscriptions/deploy_object_job_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Integrations::Hubspot::Subscriptions::DeployObjectJob, type: :job do + describe '#perform' do + subject(:deploy_object_job) { described_class } + + let(:deploy_object_service) { instance_double(Integrations::Hubspot::Subscriptions::DeployObjectService) } + let(:integration) { create(:hubspot_integration) } + let(:result) { BaseService::Result.new } + + before do + allow(Integrations::Hubspot::Subscriptions::DeployObjectService).to receive(:new).and_return(deploy_object_service) + allow(deploy_object_service).to receive(:call).and_return(result) + end + + it 'calls the DeployObjectService to deploy subscription custom object' do + deploy_object_job.perform_now(integration:) + + expect(Integrations::Hubspot::Subscriptions::DeployObjectService).to have_received(:new) + expect(deploy_object_service).to have_received(:call) + end + end +end diff --git a/spec/models/integrations/hubspot_integration_spec.rb b/spec/models/integrations/hubspot_integration_spec.rb index f82d5693dbd..5a7ff471fd9 100644 --- a/spec/models/integrations/hubspot_integration_spec.rb +++ b/spec/models/integrations/hubspot_integration_spec.rb @@ -32,8 +32,8 @@ describe '#default_targeted_object' do it 'assigns and retrieve a setting' do - hubspot_integration.default_targeted_object = 'Companies' - expect(hubspot_integration.default_targeted_object).to eq('Companies') + hubspot_integration.default_targeted_object = 'companies' + expect(hubspot_integration.default_targeted_object).to eq('companies') end end @@ -50,4 +50,58 @@ expect(hubspot_integration.sync_subscriptions).to eq(true) end end + + describe '#subscriptions_object_type_id' do + it 'assigns and retrieve a setting' do + hubspot_integration.subscriptions_object_type_id = '123' + expect(hubspot_integration.subscriptions_object_type_id).to eq('123') + end + end + + describe '#invoices_object_type_id' do + it 'assigns and retrieve a setting' do + hubspot_integration.invoices_object_type_id = '123' + expect(hubspot_integration.invoices_object_type_id).to eq('123') + end + end + + describe '#companies_properties_version' do + it 'assigns and retrieve a setting' do + hubspot_integration.companies_properties_version = 5 + expect(hubspot_integration.companies_properties_version).to eq(5) + end + end + + describe '#contacts_properties_version' do + it 'assigns and retrieve a setting' do + hubspot_integration.contacts_properties_version = 6 + expect(hubspot_integration.contacts_properties_version).to eq(6) + end + end + + describe '#subscriptions_properties_version' do + it 'assigns and retrieve a setting' do + hubspot_integration.subscriptions_properties_version = 7 + expect(hubspot_integration.subscriptions_properties_version).to eq(7) + end + end + + describe '#invoices_properties_version' do + it 'assigns and retrieve a setting' do + hubspot_integration.invoices_properties_version = 8 + expect(hubspot_integration.invoices_properties_version).to eq(8) + end + end + + describe '#companies_object_type_id' do + it 'returns the correct object type id for companies' do + expect(hubspot_integration.companies_object_type_id).to eq('0-2') + end + end + + describe '#contacts_object_type_id' do + it 'returns the correct object type id for contacts' do + expect(hubspot_integration.contacts_object_type_id).to eq('0-1') + end + end end diff --git a/spec/serializers/v1/integrations/provider_error_serializer_spec.rb b/spec/serializers/v1/integrations/provider_error_serializer_spec.rb new file mode 100644 index 00000000000..31ed14654a2 --- /dev/null +++ b/spec/serializers/v1/integrations/provider_error_serializer_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ::V1::Integrations::ProviderErrorSerializer do + subject(:serializer) { described_class.new(integration, options) } + + let(:integration) { create(:netsuite_integration) } + + let(:options) do + { + 'provider_error' => { + 'error_message' => 'message', + 'error_code' => 'code' + }, + 'provider' => 'netsuite', + 'provider_code' => integration.code + }.with_indifferent_access + end + + it 'serializes the object' do + result = JSON.parse(serializer.to_json) + + aggregate_failures do + expect(result['data']['lago_integration_id']).to eq(integration.id) + expect(result['data']['provider']).to eq(options[:provider]) + expect(result['data']['provider_code']).to eq(integration.code) + expect(result['data']['provider_error']).to eq(options[:provider_error]) + end + end +end diff --git a/spec/services/integrations/hubspot/companies/deploy_properties_service_spec.rb b/spec/services/integrations/hubspot/companies/deploy_properties_service_spec.rb new file mode 100644 index 00000000000..6a629a7a60a --- /dev/null +++ b/spec/services/integrations/hubspot/companies/deploy_properties_service_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Integrations::Hubspot::Companies::DeployPropertiesService do + subject(:deploy_properties_service) { described_class.new(integration:) } + + let(:integration) { create(:hubspot_integration) } + + describe '.call' do + let(:http_client) { instance_double(LagoHttpClient::Client) } + let(:endpoint) { "https://api.nango.dev/v1/hubspot/properties" } + let(:response) { instance_double('Response', success?: true) } + + before do + allow(LagoHttpClient::Client).to receive(:new) + .with(endpoint) + .and_return(http_client) + allow(http_client).to receive(:post_with_response).and_return(response) + + integration.companies_properties_version = nil + integration.save! + end + + it 'successfully deploys companies properties and updates the companies_properties_version' do + deploy_properties_service.call + + aggregate_failures do + expect(LagoHttpClient::Client).to have_received(:new).with(endpoint) + expect(http_client).to have_received(:post_with_response) do |payload, headers| + expect(payload[:objectType]).to eq('companies') + expect(headers['Authorization']).to include('Bearer') + end + expect(integration.reload.companies_properties_version).to eq(described_class::VERSION) + end + end + + context 'when companies_properties_version is already up-to-date' do + before do + integration.companies_properties_version = described_class::VERSION + integration.save! + end + + it 'does not make an API call and keeps the version unchanged' do + deploy_properties_service.call + + aggregate_failures do + expect(LagoHttpClient::Client).not_to have_received(:new) + expect(http_client).not_to have_received(:post_with_response) + expect(integration.reload.companies_properties_version).to eq(described_class::VERSION) + end + end + end + + context 'when an HTTP error occurs' do + let(:error) { LagoHttpClient::HttpError.new('error message', '{"error": {"message": "unknown failure"}}', nil) } + + before do + allow(http_client).to receive(:post_with_response).and_raise(error) + end + + it 'delivers an integration error webhook' do + expect { deploy_properties_service.call }.to enqueue_job(SendWebhookJob) + .with( + 'integration.provider_error', + integration, + provider: 'hubspot', + provider_code: integration.code, + provider_error: { + message: 'unknown failure', + error_code: 'integration_error' + } + ) + end + end + end +end diff --git a/spec/services/integrations/hubspot/contacts/deploy_properties_service_spec.rb b/spec/services/integrations/hubspot/contacts/deploy_properties_service_spec.rb new file mode 100644 index 00000000000..1a3a19f9efe --- /dev/null +++ b/spec/services/integrations/hubspot/contacts/deploy_properties_service_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Integrations::Hubspot::Contacts::DeployPropertiesService do + subject(:deploy_properties_service) { described_class.new(integration:) } + + let(:integration) { create(:hubspot_integration) } + + describe '.call' do + let(:http_client) { instance_double(LagoHttpClient::Client) } + let(:endpoint) { "https://api.nango.dev/v1/hubspot/properties" } + let(:response) { instance_double('Response', success?: true) } + + before do + allow(LagoHttpClient::Client).to receive(:new) + .with(endpoint) + .and_return(http_client) + allow(http_client).to receive(:post_with_response).and_return(response) + + integration.contacts_properties_version = nil + integration.save! + end + + it 'successfully deploys contacts properties and updates the contacts_properties_version' do + deploy_properties_service.call + + aggregate_failures do + expect(LagoHttpClient::Client).to have_received(:new).with(endpoint) + expect(http_client).to have_received(:post_with_response) do |payload, headers| + expect(payload[:objectType]).to eq('contacts') + expect(headers['Authorization']).to include('Bearer') + end + expect(integration.reload.contacts_properties_version).to eq(described_class::VERSION) + end + end + + context 'when contacts_properties_version is already up-to-date' do + before do + integration.contacts_properties_version = described_class::VERSION + integration.save! + end + + it 'does not make an API call and keeps the version unchanged' do + deploy_properties_service.call + + aggregate_failures do + expect(LagoHttpClient::Client).not_to have_received(:new) + expect(http_client).not_to have_received(:post_with_response) + expect(integration.reload.contacts_properties_version).to eq(described_class::VERSION) + end + end + end + + context 'when an HTTP error occurs' do + let(:error) { LagoHttpClient::HttpError.new('error message', '{"error": {"message": "unknown failure"}}', nil) } + + before do + allow(http_client).to receive(:post_with_response).and_raise(error) + end + + it 'delivers an integration error webhook' do + expect { deploy_properties_service.call }.to enqueue_job(SendWebhookJob) + .with( + 'integration.provider_error', + integration, + provider: 'hubspot', + provider_code: integration.code, + provider_error: { + message: 'unknown failure', + error_code: 'integration_error' + } + ) + end + end + end +end diff --git a/spec/services/integrations/hubspot/create_service_spec.rb b/spec/services/integrations/hubspot/create_service_spec.rb index 999b5867e8a..9c028ac635a 100644 --- a/spec/services/integrations/hubspot/create_service_spec.rb +++ b/spec/services/integrations/hubspot/create_service_spec.rb @@ -59,6 +59,7 @@ before do organization.update!(premium_integrations: ['hubspot']) allow(Integrations::Aggregator::SendPrivateAppTokenJob).to receive(:perform_later) + allow(Integrations::Aggregator::SyncCustomObjectsAndPropertiesJob).to receive(:perform_later) end context 'without validation errors' do @@ -82,11 +83,12 @@ expect(result.integration).to be_a(Integrations::HubspotIntegration) end - it 'calls Integrations::Aggregator::SendPrivateAppTokenJob' do + it 'enqueues the jobs to send token and sync objects to Hubspot' do service_call 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:) end end diff --git a/spec/services/integrations/hubspot/invoices/deploy_object_service_spec.rb b/spec/services/integrations/hubspot/invoices/deploy_object_service_spec.rb new file mode 100644 index 00000000000..b9ef309ec38 --- /dev/null +++ b/spec/services/integrations/hubspot/invoices/deploy_object_service_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Integrations::Hubspot::Invoices::DeployObjectService do + subject(:deploy_object_service) { described_class.new(integration:) } + + let(:integration) { create(:hubspot_integration) } + + describe '.call' do + let(:http_client) { instance_double(LagoHttpClient::Client) } + let(:endpoint) { "https://api.nango.dev/v1/hubspot/object" } + let(:response) { instance_double('Response', success?: true) } + + before do + allow(LagoHttpClient::Client).to receive(:new) + .with(endpoint) + .and_return(http_client) + allow(http_client).to receive(:post_with_response).and_return(response) + + integration.invoices_properties_version = nil + integration.save! + end + + it 'successfully deploys invoice custom object and updates the invoices_properties_version' do + deploy_object_service.call + + aggregate_failures do + expect(LagoHttpClient::Client).to have_received(:new).with(endpoint) + expect(http_client).to have_received(:post_with_response) do |payload, headers| + expect(payload[:name]).to eq('LagoInvoices') + expect(headers['Authorization']).to include('Bearer') + end + expect(integration.reload.invoices_properties_version).to eq(described_class::VERSION) + end + end + + context 'when invoices_properties_version is already up-to-date' do + before do + integration.invoices_properties_version = described_class::VERSION + integration.save! + end + + it 'does not make an API call and keeps the version unchanged' do + deploy_object_service.call + + aggregate_failures do + expect(LagoHttpClient::Client).not_to have_received(:new) + expect(http_client).not_to have_received(:post_with_response) + expect(integration.reload.invoices_properties_version).to eq(described_class::VERSION) + end + end + end + + context 'when an HTTP error occurs' do + let(:error) { LagoHttpClient::HttpError.new('error message', '{"error": {"message": "unknown failure"}}', nil) } + + before do + allow(http_client).to receive(:post_with_response).and_raise(error) + end + + it 'delivers an integration error webhook' do + expect { deploy_object_service.call }.to enqueue_job(SendWebhookJob) + .with( + 'integration.provider_error', + integration, + provider: 'hubspot', + provider_code: integration.code, + provider_error: { + message: 'unknown failure', + error_code: 'integration_error' + } + ) + end + end + end +end diff --git a/spec/services/integrations/hubspot/invoices/deploy_properties_service_spec.rb b/spec/services/integrations/hubspot/invoices/deploy_properties_service_spec.rb new file mode 100644 index 00000000000..33888e69ad7 --- /dev/null +++ b/spec/services/integrations/hubspot/invoices/deploy_properties_service_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Integrations::Hubspot::Invoices::DeployPropertiesService do + subject(:deploy_properties_service) { described_class.new(integration:) } + + let(:integration) { create(:hubspot_integration) } + + describe '.call' do + let(:http_client) { instance_double(LagoHttpClient::Client) } + let(:endpoint) { "https://api.nango.dev/v1/hubspot/properties" } + let(:response) { instance_double('Response', success?: true) } + + before do + allow(LagoHttpClient::Client).to receive(:new) + .with(endpoint) + .and_return(http_client) + allow(http_client).to receive(:post_with_response).and_return(response) + + integration.invoices_properties_version = nil + integration.save! + end + + it 'successfully deploys invoices properties and updates the invoices_properties_version' do + deploy_properties_service.call + + aggregate_failures do + expect(LagoHttpClient::Client).to have_received(:new).with(endpoint) + expect(http_client).to have_received(:post_with_response) do |payload, headers| + expect(payload[:objectType]).to eq('LagoInvoices') + expect(headers['Authorization']).to include('Bearer') + end + expect(integration.reload.invoices_properties_version).to eq(described_class::VERSION) + end + end + + context 'when invoices_properties_version is already up-to-date' do + before do + integration.invoices_properties_version = described_class::VERSION + integration.save! + end + + it 'does not make an API call and keeps the version unchanged' do + deploy_properties_service.call + + aggregate_failures do + expect(LagoHttpClient::Client).not_to have_received(:new) + expect(http_client).not_to have_received(:post_with_response) + expect(integration.reload.invoices_properties_version).to eq(described_class::VERSION) + end + end + end + + context 'when an HTTP error occurs' do + let(:error) { LagoHttpClient::HttpError.new('error message', '{"error": {"message": "unknown failure"}}', nil) } + + before do + allow(http_client).to receive(:post_with_response).and_raise(error) + end + + it 'delivers an integration error webhook' do + expect { deploy_properties_service.call }.to enqueue_job(SendWebhookJob) + .with( + 'integration.provider_error', + integration, + provider: 'hubspot', + provider_code: integration.code, + provider_error: { + message: 'unknown failure', + error_code: 'integration_error' + } + ) + end + end + end +end diff --git a/spec/services/integrations/hubspot/subscriptions/deploy_object_service_spec.rb b/spec/services/integrations/hubspot/subscriptions/deploy_object_service_spec.rb new file mode 100644 index 00000000000..b2943ae8339 --- /dev/null +++ b/spec/services/integrations/hubspot/subscriptions/deploy_object_service_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Integrations::Hubspot::Subscriptions::DeployObjectService do + subject(:deploy_object_service) { described_class.new(integration:) } + + let(:integration) { create(:hubspot_integration) } + + describe '.call' do + let(:http_client) { instance_double(LagoHttpClient::Client) } + let(:endpoint) { "https://api.nango.dev/v1/hubspot/object" } + let(:response) { instance_double('Response', success?: true) } + + before do + allow(LagoHttpClient::Client).to receive(:new) + .with(endpoint) + .and_return(http_client) + allow(http_client).to receive(:post_with_response).and_return(response) + + integration.subscriptions_properties_version = nil + integration.save! + end + + it 'successfully deploys subscription custom object and updates the subscriptions_properties_version' do + deploy_object_service.call + + aggregate_failures do + expect(LagoHttpClient::Client).to have_received(:new).with(endpoint) + expect(http_client).to have_received(:post_with_response) do |payload, headers| + expect(payload[:name]).to eq('LagoSubscriptions') + expect(headers['Authorization']).to include('Bearer') + end + expect(integration.reload.subscriptions_properties_version).to eq(described_class::VERSION) + end + end + + context 'when subscriptions_properties_version is already up-to-date' do + before do + integration.subscriptions_properties_version = described_class::VERSION + integration.save! + end + + it 'does not make an API call and keeps the version unchanged' do + deploy_object_service.call + + aggregate_failures do + expect(LagoHttpClient::Client).not_to have_received(:new) + expect(http_client).not_to have_received(:post_with_response) + expect(integration.reload.subscriptions_properties_version).to eq(described_class::VERSION) + end + end + end + + context 'when an HTTP error occurs' do + let(:error) { LagoHttpClient::HttpError.new('error message', '{"error": {"message": "unknown failure"}}', nil) } + + before do + allow(http_client).to receive(:post_with_response).and_raise(error) + end + + it 'delivers an integration error webhook' do + expect { deploy_object_service.call }.to enqueue_job(SendWebhookJob) + .with( + 'integration.provider_error', + integration, + provider: 'hubspot', + provider_code: integration.code, + provider_error: { + message: 'unknown failure', + error_code: 'integration_error' + } + ) + end + end + end +end diff --git a/spec/services/integrations/hubspot/subscriptions/deploy_properties_service_spec.rb b/spec/services/integrations/hubspot/subscriptions/deploy_properties_service_spec.rb new file mode 100644 index 00000000000..0638b6b74dc --- /dev/null +++ b/spec/services/integrations/hubspot/subscriptions/deploy_properties_service_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Integrations::Hubspot::Subscriptions::DeployPropertiesService do + subject(:deploy_properties_service) { described_class.new(integration:) } + + let(:integration) { create(:hubspot_integration) } + + describe '.call' do + let(:http_client) { instance_double(LagoHttpClient::Client) } + let(:endpoint) { "https://api.nango.dev/v1/hubspot/properties" } + let(:response) { instance_double('Response', success?: true) } + + before do + allow(LagoHttpClient::Client).to receive(:new) + .with(endpoint) + .and_return(http_client) + allow(http_client).to receive(:post_with_response).and_return(response) + + integration.subscriptions_properties_version = nil + integration.save! + end + + it 'successfully deploys subscriptions properties and updates the subscriptions_properties_version' do + deploy_properties_service.call + + aggregate_failures do + expect(LagoHttpClient::Client).to have_received(:new).with(endpoint) + expect(http_client).to have_received(:post_with_response) do |payload, headers| + expect(payload[:objectType]).to eq('LagoSubscriptions') + expect(headers['Authorization']).to include('Bearer') + end + expect(integration.reload.subscriptions_properties_version).to eq(described_class::VERSION) + end + end + + context 'when subscriptions_properties_version is already up-to-date' do + before do + integration.subscriptions_properties_version = described_class::VERSION + integration.save! + end + + it 'does not make an API call and keeps the version unchanged' do + deploy_properties_service.call + + aggregate_failures do + expect(LagoHttpClient::Client).not_to have_received(:new) + expect(http_client).not_to have_received(:post_with_response) + expect(integration.reload.subscriptions_properties_version).to eq(described_class::VERSION) + end + end + end + + context 'when an HTTP error occurs' do + let(:error) { LagoHttpClient::HttpError.new('error message', '{"error": {"message": "unknown failure"}}', nil) } + + before do + allow(http_client).to receive(:post_with_response).and_raise(error) + end + + it 'delivers an integration error webhook' do + expect { deploy_properties_service.call }.to enqueue_job(SendWebhookJob) + .with( + 'integration.provider_error', + integration, + provider: 'hubspot', + provider_code: integration.code, + provider_error: { + message: 'unknown failure', + error_code: 'integration_error' + } + ) + end + end + end +end diff --git a/spec/services/webhooks/integrations/provider_error_service_spec.rb b/spec/services/webhooks/integrations/provider_error_service_spec.rb new file mode 100644 index 00000000000..f85e68adcb7 --- /dev/null +++ b/spec/services/webhooks/integrations/provider_error_service_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Webhooks::Integrations::ProviderErrorService do + subject(:webhook_service) { described_class.new(object: integration, options: webhook_options) } + + let(:integration) { create(:netsuite_integration, organization:) } + let(:organization) { create(:organization) } + let(:webhook_options) { {provider_error: {message: 'message', error_code: 'code'}} } + + describe '.call' do + it_behaves_like 'creates webhook', 'integration.provider_error', 'provider_error' + end +end