Back to Repositories

Testing HTTP Header Management Functionality in Faraday

This test suite validates the header handling functionality in the Faraday HTTP client library’s Utils::Headers class. It ensures proper handling of HTTP headers with case-insensitive matching and various header manipulation methods.

Test Coverage Overview

The test suite provides comprehensive coverage of header manipulation operations:
  • Case-insensitive header access and storage
  • Content-Type header handling for different MIME types
  • Header fetch operations with default values and block handling
  • Header deletion and size tracking
  • Header parsing from raw HTTP response strings

Implementation Analysis

The testing approach uses RSpec’s context-based structure to organize related test cases. It leverages before blocks for test setup and employs expectation matchers to verify header behavior. The implementation focuses on isolated unit tests for each header manipulation method.

Key patterns include:
  • Shared context setup for repeated header scenarios
  • Multiple assertions for case-insensitive matching
  • Error condition testing for missing headers

Technical Details

Testing tools and configuration:
  • RSpec testing framework
  • Frozen string literals enabled
  • Subject-based test organization
  • Custom matchers for header presence
  • Block-based expectation testing

Best Practices Demonstrated

The test suite exemplifies several testing best practices:
  • Isolated test contexts for different scenarios
  • Comprehensive edge case coverage
  • Clear test case organization
  • Consistent naming conventions
  • Proper setup and teardown patterns

lostisland/faraday

spec/faraday/utils/headers_spec.rb

            
# frozen_string_literal: true

RSpec.describe Faraday::Utils::Headers do
  subject { Faraday::Utils::Headers.new }

  context 'when Content-Type is set to application/json' do
    before { subject['Content-Type'] = 'application/json' }

    it { expect(subject.keys).to eq(['Content-Type']) }
    it { expect(subject['Content-Type']).to eq('application/json') }
    it { expect(subject['CONTENT-TYPE']).to eq('application/json') }
    it { expect(subject['content-type']).to eq('application/json') }
    it { is_expected.to include('content-type') }
  end

  context 'when Content-Type is set to application/xml' do
    before { subject['Content-Type'] = 'application/xml' }

    it { expect(subject.keys).to eq(['Content-Type']) }
    it { expect(subject['Content-Type']).to eq('application/xml') }
    it { expect(subject['CONTENT-TYPE']).to eq('application/xml') }
    it { expect(subject['content-type']).to eq('application/xml') }
    it { is_expected.to include('content-type') }
  end

  describe '#fetch' do
    before { subject['Content-Type'] = 'application/json' }

    it { expect(subject.fetch('Content-Type')).to eq('application/json') }
    it { expect(subject.fetch('CONTENT-TYPE')).to eq('application/json') }
    it { expect(subject.fetch(:content_type)).to eq('application/json') }
    it { expect(subject.fetch('invalid', 'default')).to eq('default') }
    it { expect(subject.fetch('invalid', false)).to eq(false) }
    it { expect(subject.fetch('invalid', nil)).to be_nil }
    it { expect(subject.fetch('Invalid') { |key| "#{key} key" }).to eq('Invalid key') }
    it 'calls a block when provided' do
      block_called = false
      expect(subject.fetch('content-type') { block_called = true }).to eq('application/json')
      expect(block_called).to be_falsey
    end
    it 'raises an error if key not found' do
      expected_error = defined?(KeyError) ? KeyError : IndexError
      expect { subject.fetch('invalid') }.to raise_error(expected_error)
    end
  end

  describe '#delete' do
    before do
      subject['Content-Type'] = 'application/json'
      @deleted = subject.delete('content-type')
    end

    it { expect(@deleted).to eq('application/json') }
    it { expect(subject.size).to eq(0) }
    it { is_expected.not_to include('content-type') }
    it { expect(subject.delete('content-type')).to be_nil }
  end

  describe '#dig' do
    before { subject['Content-Type'] = 'application/json' }

    it { expect(subject&.dig('Content-Type')).to eq('application/json') }
    it { expect(subject&.dig('CONTENT-TYPE')).to eq('application/json') }
    it { expect(subject&.dig(:content_type)).to eq('application/json') }
    it { expect(subject&.dig('invalid')).to be_nil }
  end

  describe '#parse' do
    context 'when response headers leave http status line out' do
      let(:headers) { "HTTP/1.1 200 OK\r
Content-Type: text/html\r
\r
" }

      before { subject.parse(headers) }

      it { expect(subject.keys).to eq(%w[Content-Type]) }
      it { expect(subject['Content-Type']).to eq('text/html') }
      it { expect(subject['content-type']).to eq('text/html') }
    end

    context 'when response headers values include a colon' do
      let(:headers) { "HTTP/1.1 200 OK\r
Content-Type: text/html\r
Location: http://httpbingo.org/\r
\r
" }

      before { subject.parse(headers) }

      it { expect(subject['location']).to eq('http://httpbingo.org/') }
    end

    context 'when response headers include a blank line' do
      let(:headers) { "HTTP/1.1 200 OK\r
\r
Content-Type: text/html\r
\r
" }

      before { subject.parse(headers) }

      it { expect(subject['content-type']).to eq('text/html') }
    end

    context 'when response headers include already stored keys' do
      let(:headers) { "HTTP/1.1 200 OK\r
X-Numbers: 123\r
\r
" }

      before do
        h = subject
        h[:x_numbers] = 8
        h.parse(headers)
      end

      it do
        expect(subject[:x_numbers]).to eq('8, 123')
      end
    end
  end
end