Back to Repositories

Testing Template Update Webhook Request Processing in DocuSeal

This test suite validates the SendTemplateUpdatedWebhookRequestJob functionality in DocuSeal, focusing on webhook notifications for template updates. It ensures proper request handling, authentication, and retry logic for webhook deliveries.

Test Coverage Overview

The test suite provides comprehensive coverage of webhook request handling for template updates.

Key areas tested include:
  • Basic webhook request sending with correct payload format
  • Secret header authentication support
  • Event filtering based on webhook configurations
  • Retry mechanism for failed requests
  • Maximum retry attempt handling

Implementation Analysis

The testing approach utilizes RSpec’s behavior-driven development patterns with factory-based test data setup. The implementation leverages WebMock for HTTP request stubbing and uses let blocks for efficient test context creation.

Key patterns include:
  • Factory-based test data generation
  • Request stubbing with WebMock
  • Shared context setup using before blocks
  • Dynamic response validation

Technical Details

Testing tools and configuration:
  • RSpec as the testing framework
  • WebMock for HTTP request mocking
  • FactoryBot for test data generation
  • Rails test helpers for database integration
  • Encrypted configuration setup for E-Sign certificates

Best Practices Demonstrated

The test suite exemplifies several testing best practices in Ruby and RSpec.

Notable practices include:
  • Isolated test scenarios with proper setup and teardown
  • Comprehensive edge case coverage
  • Clear test descriptions using RSpec’s declarative syntax
  • Effective use of test doubles and stubs
  • Proper separation of concerns in test organization

docusealco/docuseal

spec/jobs/send_template_updated_webhook_request_job_spec.rb

            
# 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: {
          'event_type' => 'template.updated',
          'timestamp' => /.*/,
          'data' => JSON.parse(Templates::SerializeForApi.call(template.reload).to_json)
        },
        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: {
          'event_type' => 'template.updated',
          'timestamp' => /.*/,
          'data' => JSON.parse(Templates::SerializeForApi.call(template.reload).to_json)
        },
        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