Skip to content

Cucumber Specifications

These Cucumber specifications codify idempot.dev's interpretation of draft-ietf-httpapi-idempotency-key-header-07. The IETF draft defines the requirements; these specs translate them into implementation choices shared by all idempot.dev projects.

Overview

These specifications define the expected behavior for idempotency middleware implementations. They cover:

  • Header Validation (Section 2.1) — Syntax and key format requirements
  • First Request Handling (Section 2.6) — Processing and storing new requests
  • Duplicate Request Handling (Section 2.6 - Retry) — Returning cached responses
  • Fingerprint Conflict (Section 2.2) — Detecting same body, different key
  • Key Reuse Conflict (Section 2.2) — Detecting same key, different body
  • Concurrent Request Handling (Section 2.6) — Handling in-flight requests
  • Error Response Format (Section 2.7) — RFC 7807 Problem Details format

Download

Download idempotency.feature (raw) — Use in your test suite

Specification

gherkin
Feature: Idempotency-Key Header Compliance
  As a developer implementing the IETF Idempotency-Key Header specification
  I want to verify my middleware complies with the spec
  So that my API handles idempotent requests correctly

  Background:
    Given a POST endpoint at "/api" that creates an order
    And the SQLite store is clean

  # Header Validation (Section 2.1 - Syntax)

  Scenario: Missing Idempotency-Key header on POST returns 400
    When I send a POST request to "/api" without an Idempotency-Key header
    Then the response status should be 400
    And the response content-type should be "application/problem+json"
    And the response body should contain "section-2.1"

  Scenario: Empty Idempotency-Key header returns 400
    Given an empty Idempotency-Key header
    When I send a POST request to "/api" with body '{"foo":"bar"}'
    Then the response status should be 400
    And the response content-type should be "application/problem+json"
    And the response body should contain "section-2.1"
    And the response body should contain "type"
    And the response body should contain "title"
    And the response body should contain "detail"

  Scenario: Idempotency-Key exceeding 255 characters returns 400
    Given an Idempotency-Key of 256 characters
    When I send a POST request to "/api" with body '{"foo":"bar"}'
    Then the response status should be 400
    And the response content-type should be "application/problem+json"
    And the response body should contain "section-2.1"
    And the response body should contain "type"
    And the response body should contain "title"
    And the response body should contain "detail"

  Scenario: Idempotency-Key with commas returns 400
    Given an Idempotency-Key "key,with,commas,longer-than-twenty-chars"
    When I send a POST request to "/api" with body '{"foo":"bar"}'
    Then the response status should be 400
    And the response content-type should be "application/problem+json"
    And the response body should contain "section-2.1"
    And the response body should contain "type"
    And the response body should contain "title"
    And the response body should contain "detail"

  Scenario: Valid Idempotency-Key is accepted
    Given an Idempotency-Key "8e03978e-40d5-43e8-bc93-6894a57f9324"
    When I send a POST request to "/api" with body '{"foo":"bar"}'
    Then the response status should be 200

  # First Request Handling (Section 2.6 - Idempotency Enforcement)

  Scenario: First request creates idempotency record
    Given an Idempotency-Key "abc123456789012345678"
    When I send a POST request to "/api" with body '{"foo":"bar"}'
    Then an idempotency record should exist with key "abc123456789012345678"
    And the idempotency record status should be "complete"

  Scenario: First request stores response status
    Given an Idempotency-Key "abc123456789012345678"
    When I send a POST request to "/api" with body '{"foo":"bar"}'
    Then the idempotency record response status should be 200

  Scenario: First request stores response body
    Given an Idempotency-Key "abc123456789012345678"
    When I send a POST request to "/api" with body '{"foo":"bar"}'
    Then the idempotency record response body should contain "success"

  Scenario: First request creates expected resource
    Given an Idempotency-Key "abc123456789012345678"
    When I send a POST request to "/api" with body '{"foo":"bar"}'
    Then 1 orders should exist in the database

  Scenario: First request returns correct response body
    Given an Idempotency-Key "abc123456789012345678"
    When I send a POST request to "/api" with body '{"foo":"bar"}'
    Then the response status should be 200
    And the response body should contain "success"

  # Duplicate Request Handling (Section 2.6 - Retry)

  Scenario: Duplicate request returns cached response
    Given an Idempotency-Key "abc123456789012345678"
    And I previously sent a POST request to "/api" with body '{"foo":"bar"}'
    When I send a POST request to "/api" with body '{"foo":"bar"}'
    Then the response status should be 200
    And the response body should contain "success"

  Scenario: Duplicate request sets replay header
    Given an Idempotency-Key "abc123456789012345678"
    And I previously sent a POST request to "/api" with body '{"foo":"bar"}'
    When I send a POST request to "/api" with body '{"foo":"bar"}'
    Then the response should have header "x-idempotent-replayed" with value "true"

  Scenario: Duplicate request does not create additional idempotency records
    Given an Idempotency-Key "abc123456789012345678"
    And I previously sent a POST request to "/api" with body '{"foo":"bar"}'
    When I send a POST request to "/api" with body '{"foo":"bar"}'
    Then 1 idempotency records should exist with key "abc123456789012345678"

  Scenario: Duplicate request does not duplicate resource creation
    Given an Idempotency-Key "abc123456789012345678"
    And I previously sent a POST request to "/api" with body '{"foo":"bar"}'
    When I send a POST request to "/api" with body '{"foo":"bar"}'
    Then 1 orders should exist in the database

  Scenario: Multiple duplicate requests return cached response
    Given an Idempotency-Key "abc123456789012345678"
    And I previously sent a POST request to "/api" with body '{"foo":"bar"}'
    When I send a POST request to "/api" with body '{"foo":"bar"}'
    And I send a POST request to "/api" with body '{"foo":"bar"}'
    And I send a POST request to "/api" with body '{"foo":"bar"}'
    Then the response status should be 200
    And 1 orders should exist in the database

  # Fingerprint Conflict (Section 2.2 - Uniqueness)

  Scenario: Different key with same body returns 409
    Given an Idempotency-Key "key1-12345678901234567"
    And I previously sent a POST request to "/api" with body '{"foo":"bar"}'
    And an Idempotency-Key "key2-12345678901234567"
    When I send a POST request to "/api" with body '{"foo":"bar"}'
    Then the response status should be 409
    And the response content-type should be "application/problem+json"
    And the response body should contain "section-2.2"

  Scenario: Fingerprint conflict response contains error message
    Given an Idempotency-Key "key1-12345678901234567"
    And I previously sent a POST request to "/api" with body '{"foo":"bar"}'
    And an Idempotency-Key "key2-12345678901234567"
    When I send a POST request to "/api" with body '{"foo":"bar"}'
    Then the response status should be 409
    And the response content-type should be "application/problem+json"
    And the response body should contain "section-2.2"
    And the response body should contain "title"

  Scenario: Fingerprint conflict does not create duplicate resources
    Given an Idempotency-Key "key1-12345678901234567"
    And I previously sent a POST request to "/api" with body '{"foo":"bar"}'
    And an Idempotency-Key "key2-12345678901234567"
    When I send a POST request to "/api" with body '{"foo":"bar"}'
    Then the response status should be 409
    And the response content-type should be "application/problem+json"
    And the response body should contain "section-2.2"
    And 1 orders should exist in the database

  # Key Reuse Conflict (Section 2.2 - Uniqueness)

  Scenario: Same key with different body returns 422
    Given an Idempotency-Key "abc123456789012345678"
    And I previously sent a POST request to "/api" with body '{"foo":"bar"}'
    When I send a POST request to "/api" with body '{"baz":"qux"}'
    Then the response status should be 422
    And the response content-type should be "application/problem+json"
    And the response body should contain "section-2.2"

  Scenario: Key reuse conflict response contains error message
    Given an Idempotency-Key "abc123456789012345678"
    And I previously sent a POST request to "/api" with body '{"foo":"bar"}'
    When I send a POST request to "/api" with body '{"baz":"qux"}'
    Then the response status should be 422
    And the response content-type should be "application/problem+json"
    And the response body should contain "section-2.2"
    And the response body should contain "title"

  # Concurrent Request Handling (Section 2.6 - Concurrent Request)

  Scenario: Request during processing returns 409
    Given an Idempotency-Key "abc123456789012345678"
    And the key "abc123456789012345678" is currently being processed
    When I send a POST request to "/api" with body '{"foo":"bar"}'
    Then the response status should be 409
    And the response content-type should be "application/problem+json"
    And the response body should contain "section-2.6"

  Scenario: Concurrent request response contains error message
    Given an Idempotency-Key "abc123456789012345678"
    And the key "abc123456789012345678" is currently being processed
    When I send a POST request to "/api" with body '{"foo":"bar"}'
    Then the response status should be 409
    And the response content-type should be "application/problem+json"
    And the response body should contain "section-2.6"
    And the response body should contain "processed"

  # Error Response Format (Section 2.7 - Error Handling)

  Scenario: Missing key error follows RFC 7807
    When I send a POST request to "/api" without an Idempotency-Key header
    Then the response status should be 400
    And the response content-type should be "application/problem+json"
    And the response body should contain "type"
    And the response body should contain "title"
    And the response body should contain "detail"

  Scenario: Key reuse error follows RFC 7807
    Given an Idempotency-Key "abc123456789012345678"
    And I previously sent a POST request to "/api" with body '{"foo":"bar"}'
    When I send a POST request to "/api" with body '{"baz":"qux"}'
    Then the response status should be 422
    And the response content-type should be "application/problem+json"
    And the response body should contain "type"
    And the response body should contain "title"
    And the response body should contain "detail"

  Scenario: Fingerprint conflict error follows RFC 7807
    Given an Idempotency-Key "key1-12345678901234567"
    And I previously sent a POST request to "/api" with body '{"foo":"bar"}'
    And an Idempotency-Key "key2-12345678901234567"
    When I send a POST request to "/api" with body '{"foo":"bar"}'
    Then the response status should be 409
    And the response content-type should be "application/problem+json"
    And the response body should contain "type"
    And the response body should contain "title"
    And the response body should contain "detail"

  # Edge Cases

  Scenario: GET requests bypass idempotency processing
    Given an Idempotency-Key "abc123456789012345678"
    When I send a GET request to "/api"
    Then the response status should be 200

  Scenario: PUT requests bypass idempotency processing
    Given an Idempotency-Key "abc123456789012345678"
    When I send a PUT request to "/api" with body '{"foo":"bar"}'
    Then the response status should be 200

  Scenario: DELETE requests bypass idempotency processing
    Given an Idempotency-Key "abc123456789012345678"
    When I send a DELETE request to "/api"
    Then the response status should be 200

  Scenario: Empty request body is handled
    Given an Idempotency-Key "abc123456789012345678"
    When I send a POST request to "/api" with empty body
    Then the response status should be 200

  Scenario: PATCH requests are protected by idempotency
    Given a PATCH endpoint at "/api" that creates an order
    And an Idempotency-Key "abc123456789012345678"
    When I send a PATCH request to "/api" with body '{"foo":"bar"}'
    Then the response status should be 200
    And an idempotency record should exist with key "abc123456789012345678"