Back to Repositories

Testing Template Webhook Request Processing in DocuSeal

This test suite validates the SendTemplateCreatedWebhookRequestJob functionality in DocuSeal, focusing on webhook request handling for template creation events. The tests ensure proper webhook delivery, authentication, and retry mechanisms.

Test Coverage Overview

The test suite provides comprehensive coverage of webhook request handling:

  • Basic webhook request sending with proper headers and payload
  • Secret header authentication implementation
  • Event filtering based on webhook configuration
  • Retry mechanism for failed requests
  • Maximum retry attempt validation

Implementation Analysis

The testing approach utilizes RSpec’s behavior-driven development patterns with WebMock for HTTP request stubbing. The implementation leverages factory patterns for test data setup and employs context-specific test scenarios to validate different webhook behaviors.

Key testing patterns include request header validation, JSON payload verification, and retry logic assessment.

Technical Details

Testing tools and configuration:

  • RSpec for test framework
  • WebMock for HTTP request stubbing
  • FactoryBot for test data generation
  • Rails test helpers for integration
  • Custom certificate generation for E-sign functionality

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test scenarios with proper setup and teardown
  • Comprehensive edge case coverage
  • Clear test descriptions and organization
  • Effective use of test doubles and stubs
  • Proper validation of asynchronous job behavior

docusealco/docuseal

spec/jobs/send_template_created_webhook_request_job_spec.rb

            
# 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: {
          'event_type' => 'template.created',
          '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.created',
          '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.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