mirror of https://github.com/docusealco/docuseal
				
				
				
			
						commit
						50acff2e81
					
				| @ -1,37 +1,22 @@ | ||||
| # frozen_string_literal: true | ||||
| 
 | ||||
| class WebhookPreferencesController < ApplicationController | ||||
|   EVENTS = %w[ | ||||
|     form.viewed | ||||
|     form.started | ||||
|     form.completed | ||||
|     form.declined | ||||
|     template.created | ||||
|     template.updated | ||||
|     submission.created | ||||
|     submission.archived | ||||
|   ].freeze | ||||
|   load_and_authorize_resource :webhook_url, parent: false | ||||
| 
 | ||||
|   before_action :load_account_config | ||||
|   authorize_resource :account_config, parent: false | ||||
|   def update | ||||
|     webhook_preferences_params[:events].each do |event, val| | ||||
|       @webhook_url.events.delete(event) if val == '0' | ||||
|       @webhook_url.events.push(event) if val == '1' && @webhook_url.events.exclude?(event) | ||||
|     end | ||||
| 
 | ||||
|   def create | ||||
|     @account_config.value[account_config_params[:event]] = account_config_params[:value] == '1' | ||||
| 
 | ||||
|     @account_config.save! | ||||
|     @webhook_url.save! | ||||
| 
 | ||||
|     head :ok | ||||
|   end | ||||
| 
 | ||||
|   private | ||||
| 
 | ||||
|   def load_account_config | ||||
|     @account_config = | ||||
|       current_account.account_configs.find_or_initialize_by(key: AccountConfig::WEBHOOK_PREFERENCES_KEY) | ||||
|     @account_config.value ||= {} | ||||
|   end | ||||
| 
 | ||||
|   def account_config_params | ||||
|     params.permit(:event, :value) | ||||
|   def webhook_preferences_params | ||||
|     params.require(:webhook_url).permit(events: {}) | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -1,29 +1,21 @@ | ||||
| # frozen_string_literal: true | ||||
| 
 | ||||
| class WebhookSecretController < ApplicationController | ||||
|   before_action :load_encrypted_config | ||||
|   authorize_resource :encrypted_config, parent: false | ||||
|   load_and_authorize_resource :webhook_url, parent: false | ||||
| 
 | ||||
|   def index; end | ||||
|   def show; end | ||||
| 
 | ||||
|   def create | ||||
|     @encrypted_config.assign_attributes(value: { | ||||
|       encrypted_config_params[:key] => encrypted_config_params[:value] | ||||
|   def update | ||||
|     @webhook_url.update!(secret: { | ||||
|       webhook_secret_params[:key] => webhook_secret_params[:value] | ||||
|     }.compact_blank) | ||||
| 
 | ||||
|     @encrypted_config.value.present? ? @encrypted_config.save! : @encrypted_config.delete | ||||
| 
 | ||||
|     redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_secret_has_been_saved')) | ||||
|   end | ||||
| 
 | ||||
|   private | ||||
| 
 | ||||
|   def load_encrypted_config | ||||
|     @encrypted_config = | ||||
|       current_account.encrypted_configs.find_or_initialize_by(key: EncryptedConfig::WEBHOOK_SECRET_KEY) | ||||
|   end | ||||
| 
 | ||||
|   def encrypted_config_params | ||||
|     params.require(:encrypted_config).permit(value: %i[key value]).fetch(:value, {}) | ||||
|   def webhook_secret_params | ||||
|     params.require(:webhook_url).permit(secret: %i[key value]).fetch(:secret, {}) | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -1,13 +1,13 @@ | ||||
| <%= render 'shared/turbo_modal', title: 'Webhook Secret' do %> | ||||
|   <%= form_for @encrypted_config, url: webhook_secret_index_path, method: :post, html: { class: 'space-y-4' }, data: { turbo_frame: :_top } do |f| %> | ||||
| <%= render 'shared/turbo_modal', title: t('webhook_secret') do %> | ||||
|   <%= form_for @webhook_url, url: webhook_secret_path, method: :patch, html: { class: 'space-y-4' }, data: { turbo_frame: :_top } do |f| %> | ||||
|     <div class="space-y-2"> | ||||
|       <%= f.fields_for :value, Struct.new(:key, :value).new(*@encrypted_config.value.to_a.first) do |ff| %> | ||||
|       <%= f.fields_for :secret, Struct.new(:key, :value).new(*@webhook_url.secret.to_a.first) do |ff| %> | ||||
|         <div class="form-control"> | ||||
|           <%= ff.label :key, class: 'label' %> | ||||
|           <%= ff.label :key, t('key'), class: 'label' %> | ||||
|           <%= ff.text_field :key, class: 'base-input', placeholder: 'X-Example-Header' %> | ||||
|         </div> | ||||
|         <div class="form-control"> | ||||
|           <%= ff.label :value, class: 'label' %> | ||||
|           <%= ff.label :value, t('value'), class: 'label' %> | ||||
|           <%= ff.text_field :value, class: 'base-input' %> | ||||
|         </div> | ||||
|       <% end %> | ||||
| @ -0,0 +1,21 @@ | ||||
| # frozen_string_literal: true | ||||
| 
 | ||||
| class AddSecretToWebhookUrls < ActiveRecord::Migration[7.2] | ||||
|   class MigrationWebhookUrl < ApplicationRecord | ||||
|     self.table_name = 'webhook_urls' | ||||
| 
 | ||||
|     serialize :secret, coder: JSON | ||||
| 
 | ||||
|     encrypts :url, :secret | ||||
|   end | ||||
| 
 | ||||
|   def change | ||||
|     add_column :webhook_urls, :secret, :text | ||||
| 
 | ||||
|     MigrationWebhookUrl.all.each do |url| | ||||
|       url.update_columns(secret: {}) | ||||
|     end | ||||
| 
 | ||||
|     change_column_null :webhook_urls, :secret, false | ||||
|   end | ||||
| end | ||||
| @ -0,0 +1,53 @@ | ||||
| # frozen_string_literal: true | ||||
| 
 | ||||
| class PopulateWebhookUrls < ActiveRecord::Migration[7.2] | ||||
|   disable_ddl_transaction | ||||
| 
 | ||||
|   class MigrationWebhookUrl < ActiveRecord::Base | ||||
|     self.table_name = 'webhook_urls' | ||||
| 
 | ||||
|     serialize :events, coder: JSON | ||||
|     serialize :secret, coder: JSON | ||||
| 
 | ||||
|     encrypts :url, :secret | ||||
|   end | ||||
| 
 | ||||
|   class MigrationEncryptedConfig < ActiveRecord::Base | ||||
|     self.table_name = 'encrypted_configs' | ||||
| 
 | ||||
|     encrypts :value | ||||
|     serialize :value, coder: JSON | ||||
|   end | ||||
| 
 | ||||
|   class MigrationAccountConfig < ActiveRecord::Base | ||||
|     self.table_name = 'account_configs' | ||||
| 
 | ||||
|     serialize :value, coder: JSON | ||||
|   end | ||||
| 
 | ||||
|   def up | ||||
|     MigrationEncryptedConfig.where(key: 'webhook_url').find_each do |config| | ||||
|       webhook_url = MigrationWebhookUrl.find_or_initialize_by(account_id: config.account_id, | ||||
|                                                               sha1: Digest::SHA1.hexdigest(config.value)) | ||||
| 
 | ||||
|       webhook_url.secret = | ||||
|         MigrationEncryptedConfig.find_by(account_id: config.account_id, key: 'webhook_secret')&.value.to_h | ||||
| 
 | ||||
|       preferences = | ||||
|         MigrationAccountConfig.find_by(account_id: config.account_id, key: 'webhook_preferences')&.value.to_h | ||||
| 
 | ||||
|       events = %w[form.viewed form.started form.completed form.declined].reject { |event| preferences[event] == false } | ||||
| 
 | ||||
|       events += preferences.compact_blank.keys | ||||
| 
 | ||||
|       webhook_url.events = events.uniq | ||||
|       webhook_url.url = config.value | ||||
| 
 | ||||
|       webhook_url.save! | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def down | ||||
|     nil | ||||
|   end | ||||
| end | ||||
| @ -0,0 +1,27 @@ | ||||
| # frozen_string_literal: true | ||||
| 
 | ||||
| module WebhookUrls | ||||
|   module_function | ||||
| 
 | ||||
|   def for_account_id(account_id, events) | ||||
|     events = Array.wrap(events) | ||||
| 
 | ||||
|     rel = WebhookUrl.where(account_id:) | ||||
| 
 | ||||
|     event_arel = events.map { |event| Arel::Table.new(:webhook_urls)[:events].matches("%\"#{event}\"%") }.reduce(:or) | ||||
| 
 | ||||
|     if Docuseal.multitenant? | ||||
|       rel.where(event_arel) | ||||
|     else | ||||
|       linked_account_rel = | ||||
|         AccountLinkedAccount.where(linked_account_id: account_id).where.not(account_type: :testing).select(:account_id) | ||||
| 
 | ||||
|       webhook_urls = rel.or(WebhookUrl.where(account_id: linked_account_rel).where(event_arel)) | ||||
| 
 | ||||
|       account_urls, linked_urls = webhook_urls.partition { |w| w.account_id == account_id } | ||||
| 
 | ||||
|       account_urls.select { |w| w.events.intersect?(events) }.presence || | ||||
|         (account_urls.present? ? WebhookUrl.none : linked_urls) | ||||
|     end | ||||
|   end | ||||
| end | ||||
| @ -0,0 +1,8 @@ | ||||
| # frozen_string_literal: true | ||||
| 
 | ||||
| FactoryBot.define do | ||||
|   factory :webhook_url do | ||||
|     account | ||||
|     url { Faker::Internet.url } | ||||
|   end | ||||
| end | ||||
| @ -0,0 +1,94 @@ | ||||
| # frozen_string_literal: true | ||||
| 
 | ||||
| require 'rails_helper' | ||||
| 
 | ||||
| RSpec.describe SendFormCompletedWebhookRequestJob do | ||||
|   let(:account) { create(:account) } | ||||
|   let(:user) { create(:user, account:) } | ||||
|   let(:template) { create(:template, account:, author: user) } | ||||
|   let(:submission) { create(:submission, template:, created_by_user: user) } | ||||
|   let(:submitter) do | ||||
|     create(:submitter, submission:, uuid: template.submitters.first['uuid'], completed_at: Time.current) | ||||
|   end | ||||
|   let(:webhook_url) { create(:webhook_url, account:, events: ['form.completed']) } | ||||
| 
 | ||||
|   before do | ||||
|     create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY, | ||||
|                               value: GenerateCertificate.call.transform_values(&:to_pem)) | ||||
|   end | ||||
| 
 | ||||
|   describe '#perform' do | ||||
|     before do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 200) | ||||
|     end | ||||
| 
 | ||||
|     it 'sends a webhook request' do | ||||
|       described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).with( | ||||
|         body: replace_timestamps({ | ||||
|           'event_type' => 'form.completed', | ||||
|           'timestamp' => Time.current, | ||||
|           'data' => Submitters::SerializeForWebhook.call(submitter.reload) | ||||
|         }.deep_stringify_keys), | ||||
|         headers: { | ||||
|           'Content-Type' => 'application/json', | ||||
|           'User-Agent' => 'DocuSeal.com Webhook' | ||||
|         } | ||||
|       ).once | ||||
|     end | ||||
| 
 | ||||
|     it 'sends a webhook request with the secret' do | ||||
|       webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' }) | ||||
|       described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).with( | ||||
|         body: replace_timestamps({ | ||||
|           'event_type' => 'form.completed', | ||||
|           'timestamp' => Time.current, | ||||
|           'data' => Submitters::SerializeForWebhook.call(submitter.reload) | ||||
|         }.deep_stringify_keys), | ||||
|         headers: { | ||||
|           'Content-Type' => 'application/json', | ||||
|           'User-Agent' => 'DocuSeal.com Webhook', | ||||
|           'X-Secret-Header' => 'secret_value' | ||||
|         } | ||||
|       ).once | ||||
|     end | ||||
| 
 | ||||
|     it "doesn't send a webhook request if the event is not in the webhook's events" do | ||||
|       webhook_url.update!(events: ['form.declined']) | ||||
| 
 | ||||
|       described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).not_to have_requested(:post, webhook_url.url) | ||||
|     end | ||||
| 
 | ||||
|     it 'sends again if the response status is 400 or higher' do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 401) | ||||
| 
 | ||||
|       expect do | ||||
|         described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id) | ||||
|       end.to change(described_class.jobs, :size).by(1) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).once | ||||
| 
 | ||||
|       args = described_class.jobs.last['args'].first | ||||
| 
 | ||||
|       expect(args['attempt']).to eq(1) | ||||
|       expect(args['last_status']).to eq(401) | ||||
|       expect(args['webhook_url_id']).to eq(webhook_url.id) | ||||
|       expect(args['submitter_id']).to eq(submitter.id) | ||||
|     end | ||||
| 
 | ||||
|     it "doesn't send again if the max attempts is reached" do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 401) | ||||
| 
 | ||||
|       expect do | ||||
|         described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id, 'attempt' => 11) | ||||
|       end.not_to change(described_class.jobs, :size) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).once | ||||
|     end | ||||
|   end | ||||
| end | ||||
| @ -0,0 +1,94 @@ | ||||
| # frozen_string_literal: true | ||||
| 
 | ||||
| require 'rails_helper' | ||||
| 
 | ||||
| RSpec.describe SendFormDeclinedWebhookRequestJob do | ||||
|   let(:account) { create(:account) } | ||||
|   let(:user) { create(:user, account:) } | ||||
|   let(:template) { create(:template, account:, author: user) } | ||||
|   let(:submission) { create(:submission, template:, created_by_user: user) } | ||||
|   let(:submitter) do | ||||
|     create(:submitter, submission:, uuid: template.submitters.first['uuid'], completed_at: Time.current) | ||||
|   end | ||||
|   let(:webhook_url) { create(:webhook_url, account:, events: ['form.declined']) } | ||||
| 
 | ||||
|   before do | ||||
|     create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY, | ||||
|                               value: GenerateCertificate.call.transform_values(&:to_pem)) | ||||
|   end | ||||
| 
 | ||||
|   describe '#perform' do | ||||
|     before do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 200) | ||||
|     end | ||||
| 
 | ||||
|     it 'sends a webhook request' do | ||||
|       described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).with( | ||||
|         body: replace_timestamps({ | ||||
|           'event_type' => 'form.declined', | ||||
|           'timestamp' => Time.current, | ||||
|           'data' => Submitters::SerializeForWebhook.call(submitter.reload) | ||||
|         }.deep_stringify_keys), | ||||
|         headers: { | ||||
|           'Content-Type' => 'application/json', | ||||
|           'User-Agent' => 'DocuSeal.com Webhook' | ||||
|         } | ||||
|       ).once | ||||
|     end | ||||
| 
 | ||||
|     it 'sends a webhook request with the secret' do | ||||
|       webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' }) | ||||
|       described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).with( | ||||
|         body: replace_timestamps({ | ||||
|           'event_type' => 'form.declined', | ||||
|           'timestamp' => Time.current, | ||||
|           'data' => Submitters::SerializeForWebhook.call(submitter.reload) | ||||
|         }.deep_stringify_keys), | ||||
|         headers: { | ||||
|           'Content-Type' => 'application/json', | ||||
|           'User-Agent' => 'DocuSeal.com Webhook', | ||||
|           'X-Secret-Header' => 'secret_value' | ||||
|         } | ||||
|       ).once | ||||
|     end | ||||
| 
 | ||||
|     it "doesn't send a webhook request if the event is not in the webhook's events" do | ||||
|       webhook_url.update!(events: ['form.completed']) | ||||
| 
 | ||||
|       described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).not_to have_requested(:post, webhook_url.url) | ||||
|     end | ||||
| 
 | ||||
|     it 'sends again if the response status is 400 or higher' do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 401) | ||||
| 
 | ||||
|       expect do | ||||
|         described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id) | ||||
|       end.to change(described_class.jobs, :size).by(1) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).once | ||||
| 
 | ||||
|       args = described_class.jobs.last['args'].first | ||||
| 
 | ||||
|       expect(args['attempt']).to eq(1) | ||||
|       expect(args['last_status']).to eq(401) | ||||
|       expect(args['webhook_url_id']).to eq(webhook_url.id) | ||||
|       expect(args['submitter_id']).to eq(submitter.id) | ||||
|     end | ||||
| 
 | ||||
|     it "doesn't send again if the max attempts is reached" do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 401) | ||||
| 
 | ||||
|       expect do | ||||
|         described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id, 'attempt' => 11) | ||||
|       end.not_to change(described_class.jobs, :size) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).once | ||||
|     end | ||||
|   end | ||||
| end | ||||
| @ -0,0 +1,94 @@ | ||||
| # frozen_string_literal: true | ||||
| 
 | ||||
| require 'rails_helper' | ||||
| 
 | ||||
| RSpec.describe SendFormStartedWebhookRequestJob do | ||||
|   let(:account) { create(:account) } | ||||
|   let(:user) { create(:user, account:) } | ||||
|   let(:template) { create(:template, account:, author: user) } | ||||
|   let(:submission) { create(:submission, template:, created_by_user: user) } | ||||
|   let(:submitter) do | ||||
|     create(:submitter, submission:, uuid: template.submitters.first['uuid'], completed_at: Time.current) | ||||
|   end | ||||
|   let(:webhook_url) { create(:webhook_url, account:, events: ['form.started']) } | ||||
| 
 | ||||
|   before do | ||||
|     create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY, | ||||
|                               value: GenerateCertificate.call.transform_values(&:to_pem)) | ||||
|   end | ||||
| 
 | ||||
|   describe '#perform' do | ||||
|     before do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 200) | ||||
|     end | ||||
| 
 | ||||
|     it 'sends a webhook request' do | ||||
|       described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).with( | ||||
|         body: replace_timestamps({ | ||||
|           'event_type' => 'form.started', | ||||
|           'timestamp' => Time.current, | ||||
|           'data' => Submitters::SerializeForWebhook.call(submitter.reload) | ||||
|         }.deep_stringify_keys), | ||||
|         headers: { | ||||
|           'Content-Type' => 'application/json', | ||||
|           'User-Agent' => 'DocuSeal.com Webhook' | ||||
|         } | ||||
|       ).once | ||||
|     end | ||||
| 
 | ||||
|     it 'sends a webhook request with the secret' do | ||||
|       webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' }) | ||||
|       described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).with( | ||||
|         body: replace_timestamps({ | ||||
|           'event_type' => 'form.started', | ||||
|           'timestamp' => Time.current, | ||||
|           'data' => Submitters::SerializeForWebhook.call(submitter.reload) | ||||
|         }.deep_stringify_keys), | ||||
|         headers: { | ||||
|           'Content-Type' => 'application/json', | ||||
|           'User-Agent' => 'DocuSeal.com Webhook', | ||||
|           'X-Secret-Header' => 'secret_value' | ||||
|         } | ||||
|       ).once | ||||
|     end | ||||
| 
 | ||||
|     it "doesn't send a webhook request if the event is not in the webhook's events" do | ||||
|       webhook_url.update!(events: ['form.declined']) | ||||
| 
 | ||||
|       described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).not_to have_requested(:post, webhook_url.url) | ||||
|     end | ||||
| 
 | ||||
|     it 'sends again if the response status is 400 or higher' do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 401) | ||||
| 
 | ||||
|       expect do | ||||
|         described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id) | ||||
|       end.to change(described_class.jobs, :size).by(1) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).once | ||||
| 
 | ||||
|       args = described_class.jobs.last['args'].first | ||||
| 
 | ||||
|       expect(args['attempt']).to eq(1) | ||||
|       expect(args['last_status']).to eq(401) | ||||
|       expect(args['webhook_url_id']).to eq(webhook_url.id) | ||||
|       expect(args['submitter_id']).to eq(submitter.id) | ||||
|     end | ||||
| 
 | ||||
|     it "doesn't send again if the max attempts is reached" do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 401) | ||||
| 
 | ||||
|       expect do | ||||
|         described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id, 'attempt' => 11) | ||||
|       end.not_to change(described_class.jobs, :size) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).once | ||||
|     end | ||||
|   end | ||||
| end | ||||
| @ -0,0 +1,94 @@ | ||||
| # frozen_string_literal: true | ||||
| 
 | ||||
| require 'rails_helper' | ||||
| 
 | ||||
| RSpec.describe SendFormViewedWebhookRequestJob do | ||||
|   let(:account) { create(:account) } | ||||
|   let(:user) { create(:user, account:) } | ||||
|   let(:template) { create(:template, account:, author: user) } | ||||
|   let(:submission) { create(:submission, template:, created_by_user: user) } | ||||
|   let(:submitter) do | ||||
|     create(:submitter, submission:, uuid: template.submitters.first['uuid'], completed_at: Time.current) | ||||
|   end | ||||
|   let(:webhook_url) { create(:webhook_url, account:, events: ['form.viewed']) } | ||||
| 
 | ||||
|   before do | ||||
|     create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY, | ||||
|                               value: GenerateCertificate.call.transform_values(&:to_pem)) | ||||
|   end | ||||
| 
 | ||||
|   describe '#perform' do | ||||
|     before do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 200) | ||||
|     end | ||||
| 
 | ||||
|     it 'sends a webhook request' do | ||||
|       described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).with( | ||||
|         body: replace_timestamps({ | ||||
|           'event_type' => 'form.viewed', | ||||
|           'timestamp' => Time.current, | ||||
|           'data' => Submitters::SerializeForWebhook.call(submitter.reload) | ||||
|         }.deep_stringify_keys), | ||||
|         headers: { | ||||
|           'Content-Type' => 'application/json', | ||||
|           'User-Agent' => 'DocuSeal.com Webhook' | ||||
|         } | ||||
|       ).once | ||||
|     end | ||||
| 
 | ||||
|     it 'sends a webhook request with the secret' do | ||||
|       webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' }) | ||||
|       described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).with( | ||||
|         body: replace_timestamps({ | ||||
|           'event_type' => 'form.viewed', | ||||
|           'timestamp' => Time.current, | ||||
|           'data' => Submitters::SerializeForWebhook.call(submitter.reload) | ||||
|         }.deep_stringify_keys), | ||||
|         headers: { | ||||
|           'Content-Type' => 'application/json', | ||||
|           'User-Agent' => 'DocuSeal.com Webhook', | ||||
|           'X-Secret-Header' => 'secret_value' | ||||
|         } | ||||
|       ).once | ||||
|     end | ||||
| 
 | ||||
|     it "doesn't send a webhook request if the event is not in the webhook's events" do | ||||
|       webhook_url.update!(events: ['form.started']) | ||||
| 
 | ||||
|       described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).not_to have_requested(:post, webhook_url.url) | ||||
|     end | ||||
| 
 | ||||
|     it 'sends again if the response status is 400 or higher' do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 401) | ||||
| 
 | ||||
|       expect do | ||||
|         described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id) | ||||
|       end.to change(described_class.jobs, :size).by(1) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).once | ||||
| 
 | ||||
|       args = described_class.jobs.last['args'].first | ||||
| 
 | ||||
|       expect(args['attempt']).to eq(1) | ||||
|       expect(args['last_status']).to eq(401) | ||||
|       expect(args['webhook_url_id']).to eq(webhook_url.id) | ||||
|       expect(args['submitter_id']).to eq(submitter.id) | ||||
|     end | ||||
| 
 | ||||
|     it "doesn't send again if the max attempts is reached" do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 401) | ||||
| 
 | ||||
|       expect do | ||||
|         described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id, 'attempt' => 11) | ||||
|       end.not_to change(described_class.jobs, :size) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).once | ||||
|     end | ||||
|   end | ||||
| end | ||||
| @ -0,0 +1,92 @@ | ||||
| # frozen_string_literal: true | ||||
| 
 | ||||
| require 'rails_helper' | ||||
| 
 | ||||
| RSpec.describe SendSubmissionArchivedWebhookRequestJob do | ||||
|   let(:account) { create(:account) } | ||||
|   let(:user) { create(:user, account:) } | ||||
|   let(:template) { create(:template, account:, author: user) } | ||||
|   let(:submission) { create(:submission, template:, created_by_user: user) } | ||||
|   let(:webhook_url) { create(:webhook_url, account:, events: ['submission.archived']) } | ||||
| 
 | ||||
|   before do | ||||
|     create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY, | ||||
|                               value: GenerateCertificate.call.transform_values(&:to_pem)) | ||||
|   end | ||||
| 
 | ||||
|   describe '#perform' do | ||||
|     before do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 200) | ||||
|     end | ||||
| 
 | ||||
|     it 'sends a webhook request' do | ||||
|       described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).with( | ||||
|         body: replace_timestamps({ | ||||
|           'event_type' => 'submission.archived', | ||||
|           'timestamp' => Time.current, | ||||
|           'data' => submission.reload.as_json(only: %i[id archived_at]) | ||||
|         }.deep_stringify_keys), | ||||
|         headers: { | ||||
|           'Content-Type' => 'application/json', | ||||
|           'User-Agent' => 'DocuSeal.com Webhook' | ||||
|         } | ||||
|       ).once | ||||
|     end | ||||
| 
 | ||||
|     it 'sends a webhook request with the secret' do | ||||
|       webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' }) | ||||
|       described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).with( | ||||
|         body: replace_timestamps({ | ||||
|           'event_type' => 'submission.archived', | ||||
|           'timestamp' => Time.current, | ||||
|           'data' => submission.reload.as_json(only: %i[id archived_at]) | ||||
|         }.deep_stringify_keys), | ||||
|         headers: { | ||||
|           'Content-Type' => 'application/json', | ||||
|           'User-Agent' => 'DocuSeal.com Webhook', | ||||
|           'X-Secret-Header' => 'secret_value' | ||||
|         } | ||||
|       ).once | ||||
|     end | ||||
| 
 | ||||
|     it "doesn't send a webhook request if the event is not in the webhook's events" do | ||||
|       webhook_url.update!(events: ['submission.created']) | ||||
| 
 | ||||
|       described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).not_to have_requested(:post, webhook_url.url) | ||||
|     end | ||||
| 
 | ||||
|     it 'sends again if the response status is 400 or higher' do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 401) | ||||
| 
 | ||||
|       expect do | ||||
|         described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id) | ||||
|       end.to change(described_class.jobs, :size).by(1) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).once | ||||
| 
 | ||||
|       args = described_class.jobs.last['args'].first | ||||
| 
 | ||||
|       expect(args['attempt']).to eq(1) | ||||
|       expect(args['last_status']).to eq(401) | ||||
|       expect(args['webhook_url_id']).to eq(webhook_url.id) | ||||
|       expect(args['submission_id']).to eq(submission.id) | ||||
|     end | ||||
| 
 | ||||
|     it "doesn't send again if the max attempts is reached" do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 401) | ||||
| 
 | ||||
|       expect do | ||||
|         described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id, | ||||
|                                     'attempt' => 11) | ||||
|       end.not_to change(described_class.jobs, :size) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).once | ||||
|     end | ||||
|   end | ||||
| end | ||||
| @ -0,0 +1,92 @@ | ||||
| # frozen_string_literal: true | ||||
| 
 | ||||
| require 'rails_helper' | ||||
| 
 | ||||
| RSpec.describe SendSubmissionCompletedWebhookRequestJob do | ||||
|   let(:account) { create(:account) } | ||||
|   let(:user) { create(:user, account:) } | ||||
|   let(:template) { create(:template, account:, author: user) } | ||||
|   let(:submission) { create(:submission, :with_submitters, template:, created_by_user: user) } | ||||
|   let(:webhook_url) { create(:webhook_url, account:, events: ['submission.completed']) } | ||||
| 
 | ||||
|   before do | ||||
|     create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY, | ||||
|                               value: GenerateCertificate.call.transform_values(&:to_pem)) | ||||
|   end | ||||
| 
 | ||||
|   describe '#perform' do | ||||
|     before do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 200) | ||||
|     end | ||||
| 
 | ||||
|     it 'sends a webhook request' do | ||||
|       described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).with( | ||||
|         body: replace_timestamps({ | ||||
|           'event_type' => 'submission.completed', | ||||
|           'timestamp' => Time.current, | ||||
|           'data' => Submissions::SerializeForApi.call(submission.reload) | ||||
|         }.deep_stringify_keys), | ||||
|         headers: { | ||||
|           'Content-Type' => 'application/json', | ||||
|           'User-Agent' => 'DocuSeal.com Webhook' | ||||
|         } | ||||
|       ).once | ||||
|     end | ||||
| 
 | ||||
|     it 'sends a webhook request with the secret' do | ||||
|       webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' }) | ||||
|       described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).with( | ||||
|         body: replace_timestamps({ | ||||
|           'event_type' => 'submission.completed', | ||||
|           'timestamp' => Time.current, | ||||
|           'data' => Submissions::SerializeForApi.call(submission.reload) | ||||
|         }.deep_stringify_keys), | ||||
|         headers: { | ||||
|           'Content-Type' => 'application/json', | ||||
|           'User-Agent' => 'DocuSeal.com Webhook', | ||||
|           'X-Secret-Header' => 'secret_value' | ||||
|         } | ||||
|       ).once | ||||
|     end | ||||
| 
 | ||||
|     it "doesn't send a webhook request if the event is not in the webhook's events" do | ||||
|       webhook_url.update!(events: ['submission.archived']) | ||||
| 
 | ||||
|       described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).not_to have_requested(:post, webhook_url.url) | ||||
|     end | ||||
| 
 | ||||
|     it 'sends again if the response status is 400 or higher' do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 401) | ||||
| 
 | ||||
|       expect do | ||||
|         described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id) | ||||
|       end.to change(described_class.jobs, :size).by(1) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).once | ||||
| 
 | ||||
|       args = described_class.jobs.last['args'].first | ||||
| 
 | ||||
|       expect(args['attempt']).to eq(1) | ||||
|       expect(args['last_status']).to eq(401) | ||||
|       expect(args['webhook_url_id']).to eq(webhook_url.id) | ||||
|       expect(args['submission_id']).to eq(submission.id) | ||||
|     end | ||||
| 
 | ||||
|     it "doesn't send again if the max attempts is reached" do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 401) | ||||
| 
 | ||||
|       expect do | ||||
|         described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id, | ||||
|                                     'attempt' => 11) | ||||
|       end.not_to change(described_class.jobs, :size) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).once | ||||
|     end | ||||
|   end | ||||
| end | ||||
| @ -0,0 +1,92 @@ | ||||
| # frozen_string_literal: true | ||||
| 
 | ||||
| require 'rails_helper' | ||||
| 
 | ||||
| RSpec.describe SendSubmissionCreatedWebhookRequestJob do | ||||
|   let(:account) { create(:account) } | ||||
|   let(:user) { create(:user, account:) } | ||||
|   let(:template) { create(:template, account:, author: user) } | ||||
|   let(:submission) { create(:submission, :with_submitters, template:, created_by_user: user) } | ||||
|   let(:webhook_url) { create(:webhook_url, account:, events: ['submission.created']) } | ||||
| 
 | ||||
|   before do | ||||
|     create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY, | ||||
|                               value: GenerateCertificate.call.transform_values(&:to_pem)) | ||||
|   end | ||||
| 
 | ||||
|   describe '#perform' do | ||||
|     before do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 200) | ||||
|     end | ||||
| 
 | ||||
|     it 'sends a webhook request' do | ||||
|       described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).with( | ||||
|         body: replace_timestamps({ | ||||
|           'event_type' => 'submission.created', | ||||
|           'timestamp' => Time.current, | ||||
|           'data' => Submissions::SerializeForApi.call(submission.reload) | ||||
|         }.deep_stringify_keys), | ||||
|         headers: { | ||||
|           'Content-Type' => 'application/json', | ||||
|           'User-Agent' => 'DocuSeal.com Webhook' | ||||
|         } | ||||
|       ).once | ||||
|     end | ||||
| 
 | ||||
|     it 'sends a webhook request with the secret' do | ||||
|       webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' }) | ||||
|       described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).with( | ||||
|         body: replace_timestamps({ | ||||
|           'event_type' => 'submission.created', | ||||
|           'timestamp' => Time.current, | ||||
|           'data' => Submissions::SerializeForApi.call(submission.reload) | ||||
|         }.deep_stringify_keys), | ||||
|         headers: { | ||||
|           'Content-Type' => 'application/json', | ||||
|           'User-Agent' => 'DocuSeal.com Webhook', | ||||
|           'X-Secret-Header' => 'secret_value' | ||||
|         } | ||||
|       ).once | ||||
|     end | ||||
| 
 | ||||
|     it "doesn't send a webhook request if the event is not in the webhook's events" do | ||||
|       webhook_url.update!(events: ['submission.completed']) | ||||
| 
 | ||||
|       described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).not_to have_requested(:post, webhook_url.url) | ||||
|     end | ||||
| 
 | ||||
|     it 'sends again if the response status is 400 or higher' do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 401) | ||||
| 
 | ||||
|       expect do | ||||
|         described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id) | ||||
|       end.to change(described_class.jobs, :size).by(1) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).once | ||||
| 
 | ||||
|       args = described_class.jobs.last['args'].first | ||||
| 
 | ||||
|       expect(args['attempt']).to eq(1) | ||||
|       expect(args['last_status']).to eq(401) | ||||
|       expect(args['webhook_url_id']).to eq(webhook_url.id) | ||||
|       expect(args['submission_id']).to eq(submission.id) | ||||
|     end | ||||
| 
 | ||||
|     it "doesn't send again if the max attempts is reached" do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 401) | ||||
| 
 | ||||
|       expect do | ||||
|         described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id, | ||||
|                                     'attempt' => 11) | ||||
|       end.not_to change(described_class.jobs, :size) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).once | ||||
|     end | ||||
|   end | ||||
| end | ||||
| @ -0,0 +1,90 @@ | ||||
| # frozen_string_literal: true | ||||
| 
 | ||||
| require 'rails_helper' | ||||
| 
 | ||||
| RSpec.describe SendTemplateCreatedWebhookRequestJob do | ||||
|   let(:account) { create(:account) } | ||||
|   let(:user) { create(:user, account:) } | ||||
|   let(:template) { create(:template, account:, author: user) } | ||||
|   let(:webhook_url) { create(:webhook_url, account:, events: ['template.created']) } | ||||
| 
 | ||||
|   before do | ||||
|     create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY, | ||||
|                               value: GenerateCertificate.call.transform_values(&:to_pem)) | ||||
|   end | ||||
| 
 | ||||
|   describe '#perform' do | ||||
|     before do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 200) | ||||
|     end | ||||
| 
 | ||||
|     it 'sends a webhook request' do | ||||
|       described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).with( | ||||
|         body: replace_timestamps({ | ||||
|           'event_type' => 'template.created', | ||||
|           'timestamp' => Time.current, | ||||
|           'data' => Templates::SerializeForApi.call(template.reload) | ||||
|         }.deep_stringify_keys), | ||||
|         headers: { | ||||
|           'Content-Type' => 'application/json', | ||||
|           'User-Agent' => 'DocuSeal.com Webhook' | ||||
|         } | ||||
|       ).once | ||||
|     end | ||||
| 
 | ||||
|     it 'sends a webhook request with the secret' do | ||||
|       webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' }) | ||||
|       described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).with( | ||||
|         body: replace_timestamps({ | ||||
|           'event_type' => 'template.created', | ||||
|           'timestamp' => Time.current, | ||||
|           'data' => Templates::SerializeForApi.call(template.reload) | ||||
|         }.deep_stringify_keys), | ||||
|         headers: { | ||||
|           'Content-Type' => 'application/json', | ||||
|           'User-Agent' => 'DocuSeal.com Webhook', | ||||
|           'X-Secret-Header' => 'secret_value' | ||||
|         } | ||||
|       ).once | ||||
|     end | ||||
| 
 | ||||
|     it "doesn't send a webhook request if the event is not in the webhook's events" do | ||||
|       webhook_url.update!(events: ['template.updated']) | ||||
| 
 | ||||
|       described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).not_to have_requested(:post, webhook_url.url) | ||||
|     end | ||||
| 
 | ||||
|     it 'sends again if the response status is 400 or higher' do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 401) | ||||
| 
 | ||||
|       expect do | ||||
|         described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id) | ||||
|       end.to change(described_class.jobs, :size).by(1) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).once | ||||
| 
 | ||||
|       args = described_class.jobs.last['args'].first | ||||
| 
 | ||||
|       expect(args['attempt']).to eq(1) | ||||
|       expect(args['last_status']).to eq(401) | ||||
|       expect(args['webhook_url_id']).to eq(webhook_url.id) | ||||
|       expect(args['template_id']).to eq(template.id) | ||||
|     end | ||||
| 
 | ||||
|     it "doesn't send again if the max attempts is reached" do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 401) | ||||
| 
 | ||||
|       expect do | ||||
|         described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id, 'attempt' => 11) | ||||
|       end.not_to change(described_class.jobs, :size) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).once | ||||
|     end | ||||
|   end | ||||
| end | ||||
| @ -0,0 +1,90 @@ | ||||
| # frozen_string_literal: true | ||||
| 
 | ||||
| require 'rails_helper' | ||||
| 
 | ||||
| RSpec.describe SendTemplateUpdatedWebhookRequestJob do | ||||
|   let(:account) { create(:account) } | ||||
|   let(:user) { create(:user, account:) } | ||||
|   let(:template) { create(:template, account:, author: user) } | ||||
|   let(:webhook_url) { create(:webhook_url, account:, events: ['template.updated']) } | ||||
| 
 | ||||
|   before do | ||||
|     create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY, | ||||
|                               value: GenerateCertificate.call.transform_values(&:to_pem)) | ||||
|   end | ||||
| 
 | ||||
|   describe '#perform' do | ||||
|     before do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 200) | ||||
|     end | ||||
| 
 | ||||
|     it 'sends a webhook request' do | ||||
|       described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).with( | ||||
|         body: replace_timestamps({ | ||||
|           'event_type' => 'template.updated', | ||||
|           'timestamp' => Time.current, | ||||
|           'data' => Templates::SerializeForApi.call(template.reload) | ||||
|         }.deep_stringify_keys), | ||||
|         headers: { | ||||
|           'Content-Type' => 'application/json', | ||||
|           'User-Agent' => 'DocuSeal.com Webhook' | ||||
|         } | ||||
|       ).once | ||||
|     end | ||||
| 
 | ||||
|     it 'sends a webhook request with the secret' do | ||||
|       webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' }) | ||||
|       described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).with( | ||||
|         body: replace_timestamps({ | ||||
|           'event_type' => 'template.updated', | ||||
|           'timestamp' => Time.current, | ||||
|           'data' => Templates::SerializeForApi.call(template.reload) | ||||
|         }.deep_stringify_keys), | ||||
|         headers: { | ||||
|           'Content-Type' => 'application/json', | ||||
|           'User-Agent' => 'DocuSeal.com Webhook', | ||||
|           'X-Secret-Header' => 'secret_value' | ||||
|         } | ||||
|       ).once | ||||
|     end | ||||
| 
 | ||||
|     it "doesn't send a webhook request if the event is not in the webhook's events" do | ||||
|       webhook_url.update!(events: ['template.created']) | ||||
| 
 | ||||
|       described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id) | ||||
| 
 | ||||
|       expect(WebMock).not_to have_requested(:post, webhook_url.url) | ||||
|     end | ||||
| 
 | ||||
|     it 'sends again if the response status is 400 or higher' do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 401) | ||||
| 
 | ||||
|       expect do | ||||
|         described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id) | ||||
|       end.to change(described_class.jobs, :size).by(1) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).once | ||||
| 
 | ||||
|       args = described_class.jobs.last['args'].first | ||||
| 
 | ||||
|       expect(args['attempt']).to eq(1) | ||||
|       expect(args['last_status']).to eq(401) | ||||
|       expect(args['webhook_url_id']).to eq(webhook_url.id) | ||||
|       expect(args['template_id']).to eq(template.id) | ||||
|     end | ||||
| 
 | ||||
|     it "doesn't send again if the max attempts is reached" do | ||||
|       stub_request(:post, webhook_url.url).to_return(status: 401) | ||||
| 
 | ||||
|       expect do | ||||
|         described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id, 'attempt' => 11) | ||||
|       end.not_to change(described_class.jobs, :size) | ||||
| 
 | ||||
|       expect(WebMock).to have_requested(:post, webhook_url.url).once | ||||
|     end | ||||
|   end | ||||
| end | ||||
					Loading…
					
					
				
		Reference in new issue