Back to Repositories

Testing Submitter API Authorization and Management in DocuSeal

This test suite validates the Submitter API functionality in a Ruby on Rails application, focusing on submitter management operations and access control between production and testing environments. The tests ensure proper handling of submitter data retrieval, updates, and authorization across different account types.

Test Coverage Overview

The test suite provides comprehensive coverage of the Submitter API endpoints, including:

  • GET requests for listing all submitters and retrieving individual submitters
  • PUT requests for updating submitter information
  • Authorization checks between production and testing environments
  • Pagination handling for submitter listings
  • Response structure validation for API endpoints

Implementation Analysis

The testing approach utilizes RSpec’s request specs to verify API behavior, implementing factory-based test data generation and detailed response validation. The suite employs let blocks for efficient test setup and context sharing, while maintaining isolated test environments for production and testing accounts.

Key patterns include:

  • Factory-based test data generation
  • Shared context setup using let blocks
  • Detailed JSON response validation
  • Environment-specific authorization testing

Technical Details

Testing tools and configuration:

  • RSpec for test framework
  • FactoryBot for test data generation
  • Rails helper integration
  • JSON response parsing and validation
  • Custom helper methods for response body generation
  • HTTP status code verification

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolation of test environments between production and testing accounts
  • Comprehensive error case coverage
  • DRY principle application through helper methods
  • Clear test organization and naming
  • Thorough validation of API responses
  • Proper setup and teardown of test data

docusealco/docuseal

spec/requests/submitters_spec.rb

            
# frozen_string_literal: true

require 'rails_helper'

describe 'Submitter API', type: :request do
  let(:account) { create(:account, :with_testing_account) }
  let(:testing_account) { account.testing_accounts.first }
  let(:author) { create(:user, account:) }
  let(:testing_author) { create(:user, account: testing_account) }
  let(:folder) { create(:template_folder, account:) }
  let(:testing_folder) { create(:template_folder, account: testing_account) }
  let(:templates) { create_list(:template, 2, account:, author:, folder:) }
  let(:testing_templates) do
    create_list(:template, 2, account: testing_account, author: testing_author, folder: testing_folder)
  end

  describe 'GET /api/submitters' do
    it 'returns a list of submitters' do
      submitters = [
        create(:submission, :with_submitters, :with_events,
               template: templates[0],
               created_by_user: author),
        create(:submission, :with_submitters,
               template: templates[1],
               created_by_user: author)
      ].map(&:submitters).flatten.reverse

      get '/api/submitters', headers: { 'x-auth-token': author.access_token.token }

      expect(response).to have_http_status(:ok)
      expect(response.parsed_body['pagination']).to eq(JSON.parse({
        count: submitters.size,
        next: submitters.last.id,
        prev: submitters.first.id
      }.to_json))
      expect(response.parsed_body['data']).to eq(JSON.parse(submitters.map { |t| submitter_body(t) }.to_json))
    end
  end

  describe 'GET /api/submitters/:id' do
    it 'returns a submitter' do
      submitter = create(:submission, :with_submitters, :with_events,
                         template: templates[0],
                         created_by_user: author).submitters.first

      get "/api/submitters/#{submitter.id}", headers: { 'x-auth-token': author.access_token.token }

      expect(response).to have_http_status(:ok)
      expect(response.parsed_body).to eq(JSON.parse(submitter_body(submitter).to_json))
    end

    it 'returns an authorization error if test account API token is used with a production submitter' do
      submitter = create(:submission, :with_submitters, :with_events,
                         template: templates[0],
                         created_by_user: author).submitters.first

      get "/api/submitters/#{submitter.id}", headers: { 'x-auth-token': testing_author.access_token.token }

      expect(response).to have_http_status(:forbidden)
      expect(response.parsed_body).to eq(
        JSON.parse({ error: "Submitter #{submitter.id} not found using " \
                            'testing API key; Use production API key to access production submitters.' }.to_json)
      )
    end

    it 'returns an authorization error if production account API token is used with a test submitter' do
      submitter = create(:submission, :with_submitters, :with_events,
                         template: testing_templates[0],
                         created_by_user: testing_author).submitters.first

      get "/api/submitters/#{submitter.id}", headers: { 'x-auth-token': author.access_token.token }

      expect(response).to have_http_status(:forbidden)
      expect(response.parsed_body).to eq(
        JSON.parse({ error: "Submitter #{submitter.id} not found using production API key; " \
                            'Use testing API key to access testing submitters.' }.to_json)
      )
    end
  end

  describe 'PUT /api/submitters' do
    it 'update a submitter' do
      submitter = create(:submission, :with_submitters, :with_events,
                         template: templates[0],
                         created_by_user: author).submitters.first

      put "/api/submitters/#{submitter.id}", headers: { 'x-auth-token': author.access_token.token }, params: {
        email: '[email protected]'
      }.to_json

      expect(response).to have_http_status(:ok)

      submitter.reload

      expect(submitter.email).to eq('[email protected]')
      expect(response.parsed_body).to eq(JSON.parse(update_submitter_body(submitter).to_json))
    end
  end

  private

  def submitter_body(submitter)
    {
      id: submitter.id,
      submission_id: submitter.submission_id,
      uuid: submitter.uuid,
      email: submitter.email,
      status: submitter.status,
      slug: submitter.slug,
      sent_at: submitter.sent_at,
      opened_at: submitter.opened_at,
      completed_at: submitter.completed_at,
      declined_at: submitter.declined_at,
      created_at: submitter.created_at,
      updated_at: submitter.updated_at,
      name: submitter.name,
      phone: submitter.phone,
      external_id: nil,
      application_key: nil, # Backward compatibility
      template: {
        id: submitter.template.id,
        name: submitter.template.name,
        created_at: submitter.template.created_at,
        updated_at: submitter.template.updated_at
      },
      metadata: {},
      preferences: {},
      submission_events: submitter.submission_events.map do |event|
        {
          id: event.id,
          submitter_id: event.submitter_id,
          event_type: event.event_type,
          event_timestamp: event.event_timestamp,
          data: event.data.slice(:reason)
        }
      end,
      values: [],
      documents: [],
      role: submitter.template.submitters.find { |s| s['uuid'] == submitter.uuid }['name']
    }
  end

  def update_submitter_body(submitter)
    submitter_body(submitter).except(:template, :submission_events)
                             .merge(embed_src: "#{Docuseal::DEFAULT_APP_URL}/s/#{submitter.slug}")
  end
end