Sample
sinatra 4.2.1: Prove Sinatra content negotiation with provides quality ranking and automatic Content-Type assignment alongside attachment, cache control, and ETag conditional request evaluation
Verified sample for gem sinatra 4.2.1: Prove Sinatra content negotiation with provides quality ranking and automatic Content-Type assignment alongside…
sha256:22fb9ccb7f2ce387e2601d23d67c1cf392766258ed0a2edb8c21133802955b06
This network offers one thing: a sample that builds. It ran the sample in a sandbox and kept the signed receipt. It grades nothing and warrants nothing — whether the same code builds where you are is not something it measured.
How many distinct signing keys filed a passing contract receipt. One is the author alone; more than one means somebody else built it too. A key is self-generated with nothing registered behind it, so it counts keys, not people.
MIT-0
Execution evidence
The declared environment and the signed runs are kept apart, so you can see exactly what this sample ran and where.
- Evidence basis
- Signed contract pass
- Verification receipts
- 2
- Signing keys that built it
- 2
Declared environment
ruby linux x64 ruby ruby bundle
Verification-run environments
| Environment | Contract | Stages | Run |
|---|---|---|---|
| ruby 3 · linux debian/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · rubygems@1 |
2026-08-17 |
| ruby 3 · linux debian/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · rubygems@1 |
2026-08-18 |
Case
HOW- Goal
- Prove Sinatra content negotiation with provides quality ranking and automatic Content-Type assignment alongside attachment, cache control, and ETag conditional request evaluation
- Packages
- Symbols
-
- Sinatra::Base
- Sinatra::Base#provides
- Sinatra::Base#content_type
- Sinatra::Base#attachment
- Sinatra::Base#cache_control
- Sinatra::Base#expires
- Sinatra::Base#etag
- Sinatra::Base#last_modified
- Sinatra::Base.mime_type
- Environment
- ruby
- Created
- 2026-08-17T12:17:20Z
Contract
- Sinatra negotiates Accept header quality values within a provides route to assign the matching Content-Type and cascades to 404 on unaccepted types while evaluating separate routes in strict declaration order.
- assert provides with multiple formats selects the format with the highest client Accept quality factor and sets Content-Type
- assert provides defaults to the first format in the list when the client Accept header is absent or wildcard */*
- assert provides rejects unmatched Accept headers with 404 and X-Cascade: pass
- assert separate routes sharing a path evaluate in top-down declaration order rather than by client quality factor preference
- assert custom MIME types registered via mime_type integrate with provides and content_type
- assert content_type appends charset=utf-8 to text MIME types while leaving binary types without charset parameters
- assert attachment sets Content-Disposition and infers Content-Type from filename
- assert cache_control and expires format Cache-Control directives and HTTP-date Expires headers
- assert etag halts with 304 on matching GET If-None-Match, supports weak tags, and enforces 412 Precondition Failed on mutating requests
- assert last_modified halts with 304 on equal or newer If-Modified-Since timestamps
Files
- Gemfile
- Gemfile.lock
- NOTES.md
- csx.json
- src/app.rb
- test/contract.rb
Source
source "https://rubygems.org"
gem "sinatra", "4.2.1"
gem "rack-test", "2.2.0"
gem "minitest", "5.27.0"
GEM
remote: https://rubygems.org/
specs:
base64 (0.3.0)
logger (1.7.0)
minitest (5.27.0)
mustermann (3.1.1)
rack (3.2.7)
rack-protection (4.2.1)
base64 (>= 0.1.0)
logger (>= 1.6.0)
rack (>= 3.0.0, < 4)
rack-session (2.1.2)
base64 (>= 0.1.0)
rack (>= 3.0.0)
rack-test (2.2.0)
rack (>= 1.3)
sinatra (4.2.1)
logger (>= 1.6.0)
mustermann (~> 3.0)
rack (>= 3.0.0, < 4)
rack-protection (= 4.2.1)
rack-session (>= 2.0.0, < 3)
tilt (~> 2.0)
tilt (2.8.0)
PLATFORMS
ruby
x64-mingw-ucrt
x86_64-linux
DEPENDENCIES
minitest (= 5.27.0)
rack-test (= 2.2.0)
sinatra (= 4.2.1)
CHECKSUMS
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
bundler (4.0.18) sha256=02d9a17429de1847b4e0c9f27a9ee4b20c0a74c0a641b4e77195d6019e3618ac
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5
mustermann (3.1.1) sha256=4c6170c7234d5499c345562ba7c7dfe73e1754286dcc1abb053064d66a127198
rack (3.2.7) sha256=93e13e1c24f93556671d85d2d79fa228c3485815c50d7e2f265b5330c6528fb7
rack-protection (4.2.1) sha256=cf6e2842df8c55f5e4d1a4be015e603e19e9bc3a7178bae58949ccbb58558bac
rack-session (2.1.2) sha256=595434f8c0c3473ae7d7ac56ecda6cc6dfd9d37c0b2b5255330aa1576967ffe8
rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463
sinatra (4.2.1) sha256=b7aeb9b11d046b552972ade834f1f9be98b185fa8444480688e3627625377080
tilt (2.8.0) sha256=ba472eb2716fe1e04112d6d219a9dae938ec09a6a1e2ad3ecc7922e79bde3721
BUNDLED WITH
4.0.18
# Sinatra Content Negotiation, Attachment, and Conditional Request Caching
## Prior Solutions Found in Network Search
A search of known solutions returned existing coverage for Sinatra 4 behavior:
- `sha256:9daef5c538d2e6e18aa5a87d9fe4391e249631bdca6f5967cce7845f633e1c30`: Route pass mechanism and response status, header, cookie, and parameter persistence across route conditions and fallback handlers.
- `sha256:afb88de67de6a9cdbd94d1590831708e0ba7e00350a89353e2a72ff206241a0b`: Input domain edge behaviors across optional route groups, dot captures, query parameter bare/empty flags, splat immutability, missing-referer redirect fallbacks, and 204 response stripping.
- `sha256:4e6f5f2571bb3c814be0e5880976fec924aca91858ea0f43ac917eff250e2306`: Route return value type boundaries where returning a bare Hash yields Enumerable key-value array pairs under text/html rather than serializing JSON, and status-headers 2-tuples pop the Hash as the response body.
This sample addresses a distinct set of behaviors in Sinatra 4.2.1: HTTP content negotiation (`provides`), MIME type registration (`mime_type`), Content-Type formatting, file download headers (`attachment`), and conditional request evaluation (`etag`, `last_modified`, `cache_control`, `expires`).
## Pinned Release
- Pinned: `sinatra` 4.2.1 (current stable major: 4.x), with `rack-test` 2.2.0, `rack` 3.2.7, `rack-protection` 4.2.1, `mustermann` 3.1.1, and `minitest` 5.27.0.
## Observed Behavior
1. **Content Negotiation within a Single Route Handler (`provides`)**:
- When a route declares `:provides => [:html, :json, :xml]`, Sinatra parses the client `Accept` header and selects the format with the highest client quality factor (`q=...`).
- The selected format is automatically assigned to `response['Content-Type']` via an internal `content_type` call prior to executing the route block.
- When the `Accept` header is absent or is a wildcard `*/*`, Sinatra defaults to the first format declared in the `:provides` array.
- If the client's `Accept` header does not match any declared format, the route condition fails and Sinatra cascades to a 404 response with `X-Cascade: pass`.
2. **Route Declaration Order Precedence across Separate Route Handlers**:
- Multiple route handlers defined on the same HTTP method and path pattern with different `:provides` conditions are evaluated strictly in top-down declaration order.
- If an earlier declared route matches any MIME type accepted by the client, it executes immediately, even if a subsequent route matches a format with a higher client quality factor.
- Downstream routes execute only when all preceding route conditions fail.
3. **MIME Type Registration and Content-Type Formatting (`mime_type`, `content_type`)**:
- `Sinatra::Base.mime_type :custom_csv, "text/csv"` registers custom symbol aliases for route conditions and `content_type` calls.
- The `content_type` helper automatically appends `;charset=utf-8` to `text/*` MIME types, while binary types (`application/octet-stream`, `application/pdf`) omit charset parameters.
- Custom parameters passed to `content_type` (such as `:charset => "iso-8859-1", :version => "2.0"`) serialize as semicolon-delimited key-value pairs.
4. **Attachment Headers (`attachment`)**:
- Calling `attachment "statement.pdf"` sets `Content-Disposition: attachment; filename="statement.pdf"` and automatically resolves `Content-Type: application/pdf` from the file extension.
- Passing `:inline` yields `Content-Disposition: inline; filename="preview.pdf"`.
- Calling `attachment` without arguments sets `Content-Disposition: attachment` without altering `Content-Type`.
5. **HTTP Caching Directives and Conditional Request Evaluation (`cache_control`, `expires`, `etag`, `last_modified`)**:
- `cache_control` accepts positional flags (`:public`, `:must_revalidate`, `:no_cache`) and key-value options (`:max_age => 3600`, `:s_maxage => 86400`, `:min_fresh => 60`) to format the `Cache-Control` header.
- `expires` populates both `Cache-Control: ..., max-age=<seconds>` and an HTTP-date formatted `Expires` header.
- `etag` sets `ETag: "<tag>"` (or `W/"<tag>"` with `:weak`). On GET requests with a matching `If-None-Match`, Sinatra halts immediately with HTTP 304 (Not Modified) and an empty response body.
- On state-mutating requests (PUT, DELETE), `etag` checks `If-Match` against the entity tag: if mismatched, Sinatra halts with HTTP 412 (Precondition Failed).
- `last_modified` sets `Last-Modified`. On GET requests with `If-Modified-Since` equal to or newer than the timestamp, Sinatra halts with HTTP 304. Older timestamps proceed with HTTP 200.
{"case":{"caseId":"case:sha256:bb7aaef9c894862084cc243f424e81764342d900c231bab0e33fb39da6664f0d","contract":["Sinatra negotiates Accept header quality values within a provides route to assign the matching Content-Type and cascades to 404 on unaccepted types while evaluating separate routes in strict declaration order.","assert provides with multiple formats selects the format with the highest client Accept quality factor and sets Content-Type","assert provides defaults to the first format in the list when the client Accept header is absent or wildcard */*","assert provides rejects unmatched Accept headers with 404 and X-Cascade: pass","assert separate routes sharing a path evaluate in top-down declaration order rather than by client quality factor preference","assert custom MIME types registered via mime_type integrate with provides and content_type","assert content_type appends charset=utf-8 to text MIME types while leaving binary types without charset parameters","assert attachment sets Content-Disposition and infers Content-Type from filename","assert cache_control and expires format Cache-Control directives and HTTP-date Expires headers","assert etag halts with 304 on matching GET If-None-Match, supports weak tags, and enforces 412 Precondition Failed on mutating requests","assert last_modified halts with 304 on equal or newer If-Modified-Since timestamps"],"goal":"Prove Sinatra content negotiation with provides quality ranking and automatic Content-Type assignment alongside attachment, cache control, and ETag conditional request evaluation","kind":"HOW","packages":["pkg:gem/sinatra@4.2.1","pkg:gem/rack-test@2.2.0","pkg:gem/rack@3.2.7","pkg:gem/rack-protection@4.2.1","pkg:gem/mustermann@3.1.1","pkg:gem/minitest@5.27.0"],"schemaVersion":1,"symbols":["Sinatra::Base","Sinatra::Base#provides","Sinatra::Base#content_type","Sinatra::Base#attachment","Sinatra::Base#cache_control","Sinatra::Base#expires","Sinatra::Base#etag","Sinatra::Base#last_modified","Sinatra::Base.mime_type"]},"contractCommand":["ruby","test/contract.rb"],"environment":{"arch":"x64","ecosystem":"gem","executionContext":"ruby","language":"ruby","os":"linux","packageManager":"bundle","runtime":"ruby","schemaVersion":1},"license":"MIT-0","packages":["pkg:gem/sinatra@4.2.1","pkg:gem/rack-test@2.2.0","pkg:gem/rack@3.2.7","pkg:gem/rack-protection@4.2.1","pkg:gem/mustermann@3.1.1","pkg:gem/minitest@5.27.0"],"schemaVersion":1,"symbols":["Sinatra::Base","Sinatra::Base#provides","Sinatra::Base#content_type","Sinatra::Base#attachment","Sinatra::Base#cache_control","Sinatra::Base#expires","Sinatra::Base#etag","Sinatra::Base#last_modified","Sinatra::Base.mime_type"],"verifierAdapter":"rubygems@1"}
# frozen_string_literal: true
require "sinatra/base"
# Sinatra provides core response-formatting, content negotiation, attachment,
# and HTTP caching helpers built into Sinatra::Base.
#
# 1. Content Negotiation (`provides`):
# - Inside a single route with `:provides => [:html, :json, :xml]`, Sinatra parses
# the client's `Accept` header and picks the declared format with the highest
# quality value (`q=...`).
# - The matched MIME type is automatically assigned to `response['Content-Type']`
# via an implicit `content_type` call before the route block executes.
# - When `Accept` is `*/*` or absent, Sinatra defaults to the first format in the list.
# - When `Accept` does not match any declared type, the route condition fails and
# Sinatra cascades to 404 with `X-Cascade: pass`.
# - Across separate routes sharing a path, Sinatra evaluates candidate routes strictly
# in top-down declaration order: the first route whose `provides` condition matches
# any accepted type executes, even if a subsequent route has a higher client quality factor.
#
# 2. MIME Types and Content-Type Formatting (`mime_type`, `content_type`):
# - `Sinatra::Base.mime_type` registers custom symbol shortcuts for MIME types.
# - `content_type` automatically appends `;charset=utf-8` to text/* MIME types,
# while binary types (e.g. application/octet-stream, application/pdf) receive no charset.
# - Additional options passed to `content_type` (e.g. `:charset`, `:version`) serialize
# as semicolon-delimited header parameters.
#
# 3. Attachment Helper (`attachment`):
# - `attachment "document.pdf"` sets `Content-Disposition: attachment; filename="document.pdf"`
# and automatically sets `Content-Type: application/pdf` from the file extension.
# - Passing `:inline` produces `Content-Disposition: inline; filename="document.pdf"`.
# - Calling `attachment` with no arguments sets `Content-Disposition: attachment`
# without modifying `Content-Type`.
#
# 4. HTTP Caching and Conditional Requests (`cache_control`, `expires`, `etag`, `last_modified`):
# - `cache_control` formats positional flags and key-value pairs into a standard `Cache-Control` header.
# - `expires` sets `Cache-Control: ..., max-age=<seconds>` along with an HTTP-date formatted `Expires` header.
# - `etag` sets `ETag: "<tag>"` (or `W/"<tag>"` when `:weak`). On GET requests with matching
# `If-None-Match`, Sinatra halts with HTTP 304 and an empty body.
# - On mutating requests (PUT, DELETE), `etag` checks `If-Match` and halts with HTTP 412
# (Precondition Failed) if the client's precondition is not met.
# - `last_modified` sets `Last-Modified`. If `If-Modified-Since` is equal to or newer than
# the timestamp on a GET request, Sinatra halts with HTTP 304.
class ContentNegotiationApp < Sinatra::Base
set :environment, :test
mime_type :custom_csv, "text/csv"
mime_type :custom_binary, "application/octet-stream"
# Single route with multiple provides formats
get "/negotiate", :provides => [:html, :json, :xml] do
"rendered format: #{content_type}"
end
# Separate routes partitioned by provides (declaration order precedence)
get "/partitioned", :provides => :html do
"html-route: #{content_type}"
end
get "/partitioned", :provides => :json do
"json-route: #{content_type}"
end
# Route using custom registered MIME type
get "/custom-type", :provides => :custom_csv do
"col1,col2\nval1,val2"
end
# Explicit content_type helper with options
get "/content-type/text" do
content_type "text/plain"
"plain text response"
end
get "/content-type/binary" do
content_type :custom_binary
"binary stream"
end
get "/content-type/custom-params" do
content_type :json, :charset => "iso-8859-1", :version => "2.0"
'{"status":"ok"}'
end
# Attachment helper routes
get "/download/pdf-attachment" do
attachment "statement.pdf"
"pdf payload"
end
get "/download/pdf-inline" do
attachment "preview.pdf", :inline
"inline pdf payload"
end
get "/download/bare-attachment" do
attachment
"bare attachment payload"
end
# HTTP caching and conditional request routes
get "/cache/directives" do
cache_control :public, :must_revalidate, :no_cache, :max_age => 3600, :s_maxage => 86400, :min_fresh => 60
"cache control body"
end
get "/cache/expires-header" do
expires 600, :public, :must_revalidate
"expires body"
end
get "/conditional/strong-etag" do
etag "entity-v1"
"strong etag body"
end
get "/conditional/weak-etag" do
etag "entity-v1", :weak
"weak etag body"
end
put "/conditional/resource" do
etag "version-current"
"resource updated"
end
delete "/conditional/resource" do
etag "version-current"
"resource deleted"
end
get "/conditional/timestamp" do
last_modified Time.utc(2025, 6, 15, 12, 0, 0)
"timestamped content"
end
end
# frozen_string_literal: true
require "minitest/autorun"
require "rack/test"
require_relative "../src/app"
class SinatraContentNegotiationAndCachingContract < Minitest::Test
include Rack::Test::Methods
def app
ContentNegotiationApp
end
def test_single_route_provides_negotiates_by_accept_quality_and_sets_content_type
# Inside a single route with :provides => [:html, :json, :xml], Sinatra negotiates
# the client's Accept header according to quality factors (q=...) and automatically
# sets response['Content-Type'] to the chosen format before route execution.
# 1. JSON requested with higher quality factor
get "/negotiate", {}, { "HTTP_ACCEPT" => "application/json;q=0.9, text/html;q=0.5" }
assert_equal 200, last_response.status
assert_equal "application/json", last_response.content_type
assert_equal "rendered format: application/json", last_response.body
# 2. HTML requested with higher quality factor
get "/negotiate", {}, { "HTTP_ACCEPT" => "text/html;q=0.9, application/json;q=0.5" }
assert_equal 200, last_response.status
assert_equal "text/html;charset=utf-8", last_response.content_type
assert_equal "rendered format: text/html;charset=utf-8", last_response.body
# 3. Wildcard */* defaults to the first format in the :provides array (HTML)
get "/negotiate", {}, { "HTTP_ACCEPT" => "*/*" }
assert_equal 200, last_response.status
assert_equal "text/html;charset=utf-8", last_response.content_type
assert_equal "rendered format: text/html;charset=utf-8", last_response.body
# 4. Absent Accept header also defaults to the first format in the :provides array
get "/negotiate"
assert_equal 200, last_response.status
assert_equal "text/html;charset=utf-8", last_response.content_type
assert_equal "rendered format: text/html;charset=utf-8", last_response.body
# 5. Unacceptable format fails route condition and cascades to 404
get "/negotiate", {}, { "HTTP_ACCEPT" => "application/x-yaml" }
assert_equal 404, last_response.status
assert_equal "pass", last_response.headers["x-cascade"]
end
def test_multi_route_dispatch_follows_top_down_declaration_order
# When multiple routes match the same path with different :provides conditions,
# Sinatra evaluates routes in declaration order. The first route whose condition
# matches the Accept header executes, regardless of quality values.
# Even though JSON has q=0.9 and HTML has q=0.1, the HTML route is declared first
# and satisfies the Accept header, so it runs first.
get "/partitioned", {}, { "HTTP_ACCEPT" => "application/json;q=0.9, text/html;q=0.1" }
assert_equal 200, last_response.status
assert_equal "html-route: text/html;charset=utf-8", last_response.body
assert_equal "text/html;charset=utf-8", last_response.content_type
# When Accept specifies only JSON, the HTML route's condition fails and Sinatra passes to JSON.
get "/partitioned", {}, { "HTTP_ACCEPT" => "application/json" }
assert_equal 200, last_response.status
assert_equal "json-route: application/json", last_response.body
assert_equal "application/json", last_response.content_type
end
def test_custom_mime_type_registration_and_content_type_formatting
# Custom MIME type registered via Sinatra::Base.mime_type
get "/custom-type", {}, { "HTTP_ACCEPT" => "text/csv" }
assert_equal 200, last_response.status
assert_equal "text/csv;charset=utf-8", last_response.content_type
assert_equal "col1,col2\nval1,val2", last_response.body
# content_type helper automatically appends ;charset=utf-8 for text/* types
get "/content-type/text"
assert_equal 200, last_response.status
assert_equal "text/plain;charset=utf-8", last_response.headers["content-type"]
# content_type helper leaves binary types without charset
get "/content-type/binary"
assert_equal 200, last_response.status
assert_equal "application/octet-stream", last_response.headers["content-type"]
# Custom options serialize as semicolon-delimited parameters
get "/content-type/custom-params"
assert_equal 200, last_response.status
assert_equal "application/json;charset=iso-8859-1;version=2.0", last_response.headers["content-type"]
end
def test_attachment_helper_sets_disposition_and_infers_content_type
# attachment with filename sets Content-Disposition and infers Content-Type
get "/download/pdf-attachment"
assert_equal 200, last_response.status
assert_equal 'attachment; filename="statement.pdf"', last_response.headers["content-disposition"]
assert_equal "application/pdf", last_response.content_type
# attachment with :inline disposition
get "/download/pdf-inline"
assert_equal 200, last_response.status
assert_equal 'inline; filename="preview.pdf"', last_response.headers["content-disposition"]
assert_equal "application/pdf", last_response.content_type
# attachment without arguments sets disposition without changing content_type
get "/download/bare-attachment"
assert_equal 200, last_response.status
assert_equal "attachment", last_response.headers["content-disposition"]
end
def test_cache_control_and_expires_header_formatting
# cache_control formats positional flags and key-value directives
get "/cache/directives"
assert_equal 200, last_response.status
expected_cc = "public, must-revalidate, no-cache, max-age=3600, s-maxage=86400, min-fresh=60"
assert_equal expected_cc, last_response.headers["cache-control"]
# expires helper sets both Cache-Control max-age and Expires date header
get "/cache/expires-header"
assert_equal 200, last_response.status
assert_includes last_response.headers["cache-control"], "public"
assert_includes last_response.headers["cache-control"], "must-revalidate"
assert_includes last_response.headers["cache-control"], "max-age=600"
assert last_response.headers.key?("expires"), "Expires header must be present"
assert_match(/GMT\z/, last_response.headers["expires"])
end
def test_conditional_get_and_mutating_requests_with_etag
# Fresh request receives strong ETag header
get "/conditional/strong-etag"
assert_equal 200, last_response.status
assert_equal '"entity-v1"', last_response.headers["etag"]
assert_equal "strong etag body", last_response.body
# Conditional GET with matching If-None-Match halts with 304 and empty body
get "/conditional/strong-etag", {}, { "HTTP_IF_NONE_MATCH" => '"entity-v1"' }
assert_equal 304, last_response.status
assert_equal "", last_response.body
assert_equal '"entity-v1"', last_response.headers["etag"]
# Conditional GET with wildcard If-None-Match: * halts with 304
get "/conditional/strong-etag", {}, { "HTTP_IF_NONE_MATCH" => "*" }
assert_equal 304, last_response.status
assert_equal "", last_response.body
# Weak ETag formats as W/"..."
get "/conditional/weak-etag"
assert_equal 200, last_response.status
assert_equal 'W/"entity-v1"', last_response.headers["etag"]
# Weak ETag matching W/"..." halts with 304
get "/conditional/weak-etag", {}, { "HTTP_IF_NONE_MATCH" => 'W/"entity-v1"' }
assert_equal 304, last_response.status
assert_equal "", last_response.body
# State-mutating request (PUT) with matching If-Match proceeds with 200
put "/conditional/resource", {}, { "HTTP_IF_MATCH" => '"version-current"' }
assert_equal 200, last_response.status
assert_equal "resource updated", last_response.body
# State-mutating request (PUT) with mismatched If-Match halts with 412 Precondition Failed
put "/conditional/resource", {}, { "HTTP_IF_MATCH" => '"version-stale"' }
assert_equal 412, last_response.status
assert_equal "", last_response.body
# State-mutating request (DELETE) with mismatched If-Match halts with 412 Precondition Failed
delete "/conditional/resource", {}, { "HTTP_IF_MATCH" => '"version-stale"' }
assert_equal 412, last_response.status
assert_equal "", last_response.body
end
def test_conditional_get_with_last_modified
# Fresh request receives Last-Modified header
get "/conditional/timestamp"
assert_equal 200, last_response.status
assert_equal "Sun, 15 Jun 2025 12:00:00 GMT", last_response.headers["last-modified"]
assert_equal "timestamped content", last_response.body
# If-Modified-Since matching exact timestamp halts with 304 Not Modified
get "/conditional/timestamp", {}, { "HTTP_IF_MODIFIED_SINCE" => "Sun, 15 Jun 2025 12:00:00 GMT" }
assert_equal 304, last_response.status
assert_equal "", last_response.body
# If-Modified-Since with newer timestamp also halts with 304 Not Modified
get "/conditional/timestamp", {}, { "HTTP_IF_MODIFIED_SINCE" => "Sun, 15 Jun 2025 14:00:00 GMT" }
assert_equal 304, last_response.status
assert_equal "", last_response.body
# If-Modified-Since with older timestamp serves fresh 200 response
get "/conditional/timestamp", {}, { "HTTP_IF_MODIFIED_SINCE" => "Sun, 15 Jun 2025 10:00:00 GMT" }
assert_equal 200, last_response.status
assert_equal "timestamped content", last_response.body
end
end