mirror of https://github.com/docusealco/docuseal
* ExportService handles the connection and post endpoint * separate subclasses for template and submission * add specspull/501/head
parent
c2a7bd8772
commit
b7963f63ae
@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'faraday'
|
||||
|
||||
class ExportService
|
||||
attr_reader :error_message
|
||||
|
||||
def initialize
|
||||
@error_message = nil
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def api_connection
|
||||
@api_connection ||= Faraday.new(url: ExportLocation.default_location.api_base_url) do |faraday|
|
||||
faraday.request :json
|
||||
faraday.response :json
|
||||
faraday.adapter Faraday.default_adapter
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("Failed to create API connection: #{e.message}")
|
||||
Rollbar.error(e) if defined?(Rollbar)
|
||||
nil
|
||||
end
|
||||
|
||||
def post_to_api(data, endpoint, extra_params = nil)
|
||||
connection = api_connection
|
||||
return nil unless connection
|
||||
|
||||
connection.post(endpoint) do |req|
|
||||
data = data.merge(extra_params) if extra_params.present? && data.is_a?(Hash)
|
||||
req.body = data.is_a?(String) ? data : data.to_json
|
||||
end
|
||||
end
|
||||
|
||||
def export_location
|
||||
@export_location ||= ExportLocation.default_location
|
||||
end
|
||||
|
||||
def set_error(message)
|
||||
@error_message = message
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ExportSubmissionService < ExportService
|
||||
attr_reader :submission
|
||||
|
||||
def initialize(submission)
|
||||
super()
|
||||
@submission = submission
|
||||
end
|
||||
|
||||
def call
|
||||
unless export_location&.submissions_endpoint.present?
|
||||
set_error('Export failed: Submission export endpoint is not configured.')
|
||||
return false
|
||||
end
|
||||
|
||||
payload = build_payload
|
||||
response = post_to_api(payload, export_location.submissions_endpoint, export_location.extra_params)
|
||||
|
||||
if response&.success?
|
||||
true
|
||||
else
|
||||
set_error("Failed to export submission ##{submission.id} events.")
|
||||
false
|
||||
end
|
||||
rescue Faraday::Error => e
|
||||
Rails.logger.error("Failed to export submission Faraday: #{e.message}")
|
||||
Rollbar.error("Failed to export submission: #{e.message}") if defined?(Rollbar)
|
||||
set_error("Network error occurred during export: #{e.message}")
|
||||
false
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("Failed to export submission: #{e.message}")
|
||||
Rollbar.error(e) if defined?(Rollbar)
|
||||
set_error("An unexpected error occurred during export: #{e.message}")
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_payload
|
||||
{
|
||||
submission_id: submission.id,
|
||||
template_name: submission.template&.name,
|
||||
events: submission.submission_events.order(updated_at: :desc).limit(1)
|
||||
}
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ExportTemplateService < ExportService
|
||||
def initialize(data)
|
||||
super()
|
||||
@data = data
|
||||
end
|
||||
|
||||
def call
|
||||
response = post_to_api(@data, export_location.templates_endpoint, export_location.extra_params)
|
||||
|
||||
if response&.success?
|
||||
Rails.logger.info("Successfully exported template #{@data[:template][:name]} to #{export_location.name}")
|
||||
true
|
||||
else
|
||||
Rails.logger.error("Failed to export template to third party: #{response&.status}")
|
||||
Rollbar.error("#{export_location.name} template export API error: #{response&.status}") if defined?(Rollbar)
|
||||
set_error("Failed to export template to third party")
|
||||
false
|
||||
end
|
||||
rescue Faraday::Error => e
|
||||
Rails.logger.error("Failed to export template Faraday: #{e.message}")
|
||||
Rollbar.error("Failed to export template: #{e.message}") if defined?(Rollbar)
|
||||
set_error("Network error occurred during template export: #{e.message}")
|
||||
false
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("Failed to export template: #{e.message}")
|
||||
Rollbar.error(e) if defined?(Rollbar)
|
||||
set_error("An unexpected error occurred during template export: #{e.message}")
|
||||
false
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :export_location do
|
||||
name { Faker::Company.name }
|
||||
api_base_url { 'https://api.example.com' }
|
||||
default_location { false }
|
||||
extra_params { {} }
|
||||
templates_endpoint { '/templates' }
|
||||
submissions_endpoint { nil }
|
||||
authorization_token { nil }
|
||||
|
||||
trait :with_submissions_endpoint do
|
||||
submissions_endpoint { '/submissions' }
|
||||
end
|
||||
|
||||
trait :with_authorization_token do
|
||||
authorization_token { SecureRandom.hex(32) }
|
||||
end
|
||||
|
||||
trait :default do
|
||||
default_location { true }
|
||||
end
|
||||
|
||||
trait :with_extra_params do
|
||||
extra_params { { 'api_key' => 'test_key', 'version' => '1.0' } }
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,200 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ExportSubmissionService do
|
||||
let(:account) { create(:account) }
|
||||
let(:user) { create(:user, account: account) }
|
||||
let(:template) { create(:template, account: account, author: user) }
|
||||
let(:submission) { create(:submission, template: template, account: account) }
|
||||
let(:export_location) { create(:export_location, :with_submissions_endpoint) }
|
||||
let(:service) { described_class.new(submission) }
|
||||
let(:faraday_connection) { instance_double(Faraday::Connection) }
|
||||
let(:faraday_response) { instance_double(Faraday::Response) }
|
||||
|
||||
before do
|
||||
allow(ExportLocation).to receive(:default_location).and_return(export_location)
|
||||
allow(Faraday).to receive(:new).and_return(faraday_connection)
|
||||
end
|
||||
|
||||
describe '#call' do
|
||||
context 'when export location is not configured' do
|
||||
before do
|
||||
allow(ExportLocation).to receive(:default_location).and_return(nil)
|
||||
end
|
||||
|
||||
it 'returns false and sets error message' do
|
||||
expect(service.call).to be false
|
||||
expect(service.error_message).to eq('Export failed: Submission export endpoint is not configured.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when export location has no submissions endpoint' do
|
||||
before do
|
||||
allow(export_location).to receive(:submissions_endpoint).and_return(nil)
|
||||
end
|
||||
|
||||
it 'returns false and sets error message' do
|
||||
expect(service.call).to be false
|
||||
expect(service.error_message).to eq('Export failed: Submission export endpoint is not configured.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when export location is properly configured' do
|
||||
let(:request_double) { double('request', body: nil) }
|
||||
|
||||
before do
|
||||
allow(request_double).to receive(:body=)
|
||||
allow(faraday_connection).to receive(:post).and_yield(request_double).and_return(faraday_response)
|
||||
end
|
||||
|
||||
context 'when API request succeeds' do
|
||||
before do
|
||||
allow(faraday_response).to receive(:success?).and_return(true)
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
expect(service.call).to be true
|
||||
end
|
||||
|
||||
it 'makes API call with correct endpoint' do
|
||||
expect(faraday_connection).to receive(:post).with(export_location.submissions_endpoint)
|
||||
service.call
|
||||
end
|
||||
end
|
||||
|
||||
context 'when API request fails' do
|
||||
before do
|
||||
allow(faraday_response).to receive(:success?).and_return(false)
|
||||
end
|
||||
|
||||
it 'returns false and sets error message' do
|
||||
expect(service.call).to be false
|
||||
expect(service.error_message).to eq("Failed to export submission ##{submission.id} events.")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when API response is nil' do
|
||||
before do
|
||||
allow(faraday_connection).to receive(:post).and_return(nil)
|
||||
end
|
||||
|
||||
it 'returns false and sets error message' do
|
||||
expect(service.call).to be false
|
||||
expect(service.error_message).to eq("Failed to export submission ##{submission.id} events.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when Faraday error occurs' do
|
||||
before do
|
||||
allow(faraday_connection).to receive(:post).and_raise(Faraday::ConnectionFailed.new('Connection failed'))
|
||||
end
|
||||
|
||||
it 'returns false and sets network error message' do
|
||||
expect(service.call).to be false
|
||||
expect(service.error_message).to eq('Network error occurred during export: Connection failed')
|
||||
end
|
||||
|
||||
it 'logs the error' do
|
||||
expect(Rails.logger).to receive(:error).with('Failed to export submission Faraday: Connection failed')
|
||||
service.call
|
||||
end
|
||||
|
||||
it 'reports to Rollbar if available' do
|
||||
stub_const('Rollbar', double)
|
||||
expect(Rollbar).to receive(:error).with('Failed to export submission: Connection failed')
|
||||
service.call
|
||||
end
|
||||
end
|
||||
|
||||
context 'when other standard error occurs' do
|
||||
before do
|
||||
allow(ExportLocation).to receive(:default_location).and_raise(StandardError.new('Database error'))
|
||||
end
|
||||
|
||||
it 'returns false and sets generic error message' do
|
||||
expect(service.call).to be false
|
||||
expect(service.error_message).to eq('An unexpected error occurred during export: Database error')
|
||||
end
|
||||
|
||||
it 'logs the error' do
|
||||
expect(Rails.logger).to receive(:error).with('Failed to export submission: Database error')
|
||||
service.call
|
||||
end
|
||||
|
||||
it 'reports to Rollbar if available' do
|
||||
stub_const('Rollbar', double)
|
||||
error = StandardError.new('Database error')
|
||||
allow(ExportLocation).to receive(:default_location).and_raise(error)
|
||||
expect(Rollbar).to receive(:error).with(error)
|
||||
service.call
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'payload building' do
|
||||
let(:request_double) { double('request', body: nil) }
|
||||
|
||||
before do
|
||||
allow(request_double).to receive(:body=)
|
||||
allow(faraday_connection).to receive(:post).and_yield(request_double).and_return(faraday_response)
|
||||
allow(faraday_response).to receive(:success?).and_return(true)
|
||||
end
|
||||
|
||||
it 'includes submission_id in payload' do
|
||||
expect(request_double).to receive(:body=) do |body|
|
||||
expect(JSON.parse(body)).to include('submission_id' => submission.id)
|
||||
end
|
||||
service.call
|
||||
end
|
||||
|
||||
it 'includes template_name in payload' do
|
||||
expect(request_double).to receive(:body=) do |body|
|
||||
expect(JSON.parse(body)).to include('template_name' => submission.template.name)
|
||||
end
|
||||
service.call
|
||||
end
|
||||
|
||||
it 'includes recent events in payload' do
|
||||
expect(request_double).to receive(:body=) do |body|
|
||||
parsed_body = JSON.parse(body)
|
||||
expect(parsed_body).to have_key('events')
|
||||
end
|
||||
service.call
|
||||
end
|
||||
|
||||
context 'when template is nil' do
|
||||
before do
|
||||
allow(submission).to receive(:template).and_return(nil)
|
||||
end
|
||||
|
||||
it 'includes nil template_name in payload' do
|
||||
expect(request_double).to receive(:body=) do |body|
|
||||
expect(JSON.parse(body)).to include('template_name' => nil)
|
||||
end
|
||||
service.call
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'extra_params handling' do
|
||||
let(:extra_params) { { 'api_key' => 'test_key', 'version' => '1.0' } }
|
||||
let(:request_double) { double('request', body: nil) }
|
||||
|
||||
before do
|
||||
allow(export_location).to receive(:extra_params).and_return(extra_params)
|
||||
allow(request_double).to receive(:body=)
|
||||
allow(faraday_connection).to receive(:post).and_yield(request_double).and_return(faraday_response)
|
||||
allow(faraday_response).to receive(:success?).and_return(true)
|
||||
end
|
||||
|
||||
it 'merges extra_params into the payload' do
|
||||
expect(request_double).to receive(:body=) do |body|
|
||||
parsed_body = JSON.parse(body)
|
||||
expect(parsed_body).to include('api_key' => 'test_key', 'version' => '1.0')
|
||||
end
|
||||
service.call
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,158 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ExportTemplateService do
|
||||
let(:export_location) { create(:export_location, :default) }
|
||||
let(:data) { { template: { name: 'Test Template' } } }
|
||||
let(:service) { described_class.new(data) }
|
||||
let(:faraday_connection) { instance_double(Faraday::Connection) }
|
||||
let(:faraday_response) { instance_double(Faraday::Response) }
|
||||
|
||||
before do
|
||||
allow(ExportLocation).to receive(:default_location).and_return(export_location)
|
||||
allow(Faraday).to receive(:new).and_return(faraday_connection)
|
||||
end
|
||||
|
||||
describe '#call' do
|
||||
let(:request_double) { double('request', body: nil) }
|
||||
|
||||
before do
|
||||
allow(request_double).to receive(:body=)
|
||||
allow(faraday_connection).to receive(:post).and_yield(request_double).and_return(faraday_response)
|
||||
end
|
||||
|
||||
context 'when API request succeeds' do
|
||||
before do
|
||||
allow(faraday_response).to receive(:success?).and_return(true)
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
expect(service.call).to be true
|
||||
end
|
||||
|
||||
it 'makes API call with correct endpoint' do
|
||||
expect(faraday_connection).to receive(:post).with(export_location.templates_endpoint)
|
||||
service.call
|
||||
end
|
||||
|
||||
it 'logs success message' do
|
||||
expect(Rails.logger).to receive(:info).with("Successfully exported template Test Template to #{export_location.name}")
|
||||
service.call
|
||||
end
|
||||
end
|
||||
|
||||
context 'when API request fails' do
|
||||
before do
|
||||
allow(faraday_response).to receive(:success?).and_return(false)
|
||||
allow(faraday_response).to receive(:status).and_return(422)
|
||||
end
|
||||
|
||||
it 'returns false and sets error message' do
|
||||
expect(service.call).to be false
|
||||
expect(service.error_message).to eq('Failed to export template to third party')
|
||||
end
|
||||
|
||||
it 'logs error message' do
|
||||
expect(Rails.logger).to receive(:error).with('Failed to export template to third party: 422')
|
||||
service.call
|
||||
end
|
||||
|
||||
it 'reports to Rollbar if available' do
|
||||
stub_const('Rollbar', double)
|
||||
expect(Rollbar).to receive(:error).with("#{export_location.name} template export API error: 422")
|
||||
service.call
|
||||
end
|
||||
end
|
||||
|
||||
context 'when API response is nil' do
|
||||
before do
|
||||
allow(faraday_connection).to receive(:post).and_return(nil)
|
||||
end
|
||||
|
||||
it 'returns false and sets error message' do
|
||||
expect(service.call).to be false
|
||||
expect(service.error_message).to eq('Failed to export template to third party')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when Faraday error occurs' do
|
||||
before do
|
||||
allow(faraday_connection).to receive(:post).and_raise(Faraday::ConnectionFailed.new('Connection failed'))
|
||||
end
|
||||
|
||||
it 'returns false and sets network error message' do
|
||||
expect(service.call).to be false
|
||||
expect(service.error_message).to eq('Network error occurred during template export: Connection failed')
|
||||
end
|
||||
|
||||
it 'logs the error' do
|
||||
expect(Rails.logger).to receive(:error).with('Failed to export template Faraday: Connection failed')
|
||||
service.call
|
||||
end
|
||||
|
||||
it 'reports to Rollbar if available' do
|
||||
stub_const('Rollbar', double)
|
||||
expect(Rollbar).to receive(:error).with('Failed to export template: Connection failed')
|
||||
service.call
|
||||
end
|
||||
end
|
||||
|
||||
context 'when other standard error occurs' do
|
||||
before do
|
||||
allow(ExportLocation).to receive(:default_location).and_raise(StandardError.new('Database error'))
|
||||
end
|
||||
|
||||
it 'returns false and sets generic error message' do
|
||||
expect(service.call).to be false
|
||||
expect(service.error_message).to eq('An unexpected error occurred during template export: Database error')
|
||||
end
|
||||
|
||||
it 'logs the error' do
|
||||
expect(Rails.logger).to receive(:error).with('Failed to export template: Database error')
|
||||
service.call
|
||||
end
|
||||
|
||||
it 'reports to Rollbar if available' do
|
||||
stub_const('Rollbar', double)
|
||||
error = StandardError.new('Database error')
|
||||
allow(ExportLocation).to receive(:default_location).and_raise(error)
|
||||
expect(Rollbar).to receive(:error).with(error)
|
||||
service.call
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'data handling' do
|
||||
let(:request_double) { double('request', body: nil) }
|
||||
|
||||
before do
|
||||
allow(request_double).to receive(:body=)
|
||||
allow(faraday_connection).to receive(:post).and_yield(request_double).and_return(faraday_response)
|
||||
allow(faraday_response).to receive(:success?).and_return(true)
|
||||
end
|
||||
|
||||
it 'sends the data in the request body' do
|
||||
expect(request_double).to receive(:body=) do |body|
|
||||
expect(JSON.parse(body)).to eq(data.deep_stringify_keys)
|
||||
end
|
||||
service.call
|
||||
end
|
||||
|
||||
context 'when extra_params are provided' do
|
||||
let(:extra_params) { { 'api_key' => 'test_key', 'version' => '1.0' } }
|
||||
|
||||
before do
|
||||
allow(export_location).to receive(:extra_params).and_return(extra_params)
|
||||
end
|
||||
|
||||
it 'merges extra_params into the data' do
|
||||
expect(request_double).to receive(:body=) do |body|
|
||||
parsed_body = JSON.parse(body)
|
||||
expect(parsed_body).to include('api_key' => 'test_key', 'version' => '1.0')
|
||||
end
|
||||
service.call
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in new issue