Back to Repositories

Testing Path-Based Version Middleware in ruby-grape/grape

This test suite validates the Path versioning middleware functionality in Grape, focusing on URL-based API version handling. It ensures proper version extraction from paths and validates version constraints across different configuration scenarios.

Test Coverage Overview

The test suite comprehensively covers path-based version detection in Grape middleware.

Key areas tested include:
  • Basic version extraction from URL paths
  • Pattern-based version matching
  • Version validation against allowed versions
  • Path preservation during version extraction
  • Mount path integration
  • Prefix handling

Implementation Analysis

The testing approach uses RSpec to validate the Grape::Middleware::Versioner::Path class functionality. Tests employ mock Rack environments and various middleware configurations to verify version extraction behavior.

Notable patterns include:
  • Subject/let block pattern for test setup
  • Context-based test organization
  • Dynamic version format testing
  • Error handling validation

Technical Details

Testing infrastructure includes:
  • RSpec as the testing framework
  • Rack test environments
  • Mock app implementation using lambda
  • Version pattern matching using regex
  • Multiple version format support (strings and symbols)

Best Practices Demonstrated

The test suite demonstrates excellent testing practices through comprehensive coverage and clear organization.

Notable practices include:
  • Isolated test cases with clear purpose
  • Thorough edge case coverage
  • DRY test implementation using shared contexts
  • Clear test descriptions
  • Consistent error handling validation

ruby-grape/grape

spec/grape/middleware/versioner/path_spec.rb

            
# frozen_string_literal: true

describe Grape::Middleware::Versioner::Path do
  subject { described_class.new(app, options) }

  let(:app) { ->(env) { [200, env, env[Grape::Env::API_VERSION]] } }
  let(:options) { {} }

  it 'sets the API version based on the first path' do
    expect(subject.call(Rack::PATH_INFO => '/v1/awesome').last).to eq('v1')
  end

  it 'does not cut the version out of the path' do
    expect(subject.call(Rack::PATH_INFO => '/v1/awesome')[1][Rack::PATH_INFO]).to eq('/v1/awesome')
  end

  it 'provides a nil version if no path is given' do
    expect(subject.call(Rack::PATH_INFO => '/').last).to be_nil
  end

  context 'with a pattern' do
    let(:options) { { pattern: /v./i } }

    it 'sets the version if it matches' do
      expect(subject.call(Rack::PATH_INFO => '/v1/awesome').last).to eq('v1')
    end

    it 'ignores the version if it fails to match' do
      expect(subject.call(Rack::PATH_INFO => '/awesome/radical').last).to be_nil
    end
  end

  [%w[v1 v2], %i[v1 v2], [:v1, 'v2'], ['v1', :v2]].each do |versions|
    context "with specified versions as #{versions}" do
      let(:options) { { versions: versions } }

      it 'throws an error if a non-allowed version is specified' do
        expect(catch(:error) { subject.call(Rack::PATH_INFO => '/v3/awesome') }[:status]).to eq(404)
      end

      it 'allows versions that have been specified' do
        expect(subject.call(Rack::PATH_INFO => '/v1/asoasd').last).to eq('v1')
      end
    end
  end

  context 'with prefix, but requested version is not matched' do
    let(:options) { { prefix: '/v1', pattern: /v./i } }

    it 'recognizes potential version' do
      expect(subject.call(Rack::PATH_INFO => '/v3/foo').last).to eq('v3')
    end
  end

  context 'with mount path' do
    let(:options) { { mount_path: '/mounted', versions: [:v1] } }

    it 'recognizes potential version' do
      expect(subject.call(Rack::PATH_INFO => '/mounted/v1/foo').last).to eq('v1')
    end
  end
end