Back to Repositories

Testing Form Viewed Webhook Request Processing in DocuSeal

This test suite validates the webhook notification system for form viewing events in DocuSeal, ensuring proper handling of webhook requests and retries. It focuses on testing the SendFormViewedWebhookRequestJob functionality for notifying external systems when forms are viewed.

Test Coverage Overview

The test suite provides comprehensive coverage of webhook request handling for form view events.

Key areas tested include:
  • Basic webhook request sending with proper formatting
  • Secret header handling in requests
  • Event filtering based on webhook configuration
  • Retry logic for failed requests
  • Maximum retry attempt handling

Implementation Analysis

The testing approach uses RSpec to simulate various webhook scenarios and validate request handling.

Notable patterns include:
  • Factory-based test data setup using let blocks
  • WebMock for HTTP request stubbing
  • Comprehensive request header and body validation
  • Dynamic response status testing

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 handling for certificates

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test cases with clear separation of concerns
  • Comprehensive edge case coverage
  • Proper test setup and teardown
  • Clear test descriptions and organization
  • Effective use of test doubles and stubs

docusealco/docuseal

spec/jobs/send_form_viewed_webhook_request_job_spec.rb

            
# 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: {
          'event_type' => 'form.viewed',
          'timestamp' => /.*/,
          'data' => JSON.parse(Submitters::SerializeForWebhook.call(submitter.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('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)

      expect(WebMock).to have_requested(:post, webhook_url.url).with(
        body: {
          'event_type' => 'form.viewed',
          'timestamp' => /.*/,
          'data' => JSON.parse(Submitters::SerializeForWebhook.call(submitter.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: ['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