From 0afc5ce316e31955fa5083b57a864aef61856533 Mon Sep 17 00:00:00 2001 From: Ben Word Date: Sat, 18 Apr 2026 11:59:41 -0500 Subject: [PATCH] Add tests for q-value parsing, 406, and Vary: Accept --- tests/Feature/MarkdownOutputTest.php | 58 ++++++++++++++++++++++++++++ tests/Pest.php | 16 +++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/tests/Feature/MarkdownOutputTest.php b/tests/Feature/MarkdownOutputTest.php index 63a0a9d..e2644ec 100644 --- a/tests/Feature/MarkdownOutputTest.php +++ b/tests/Feature/MarkdownOutputTest.php @@ -48,3 +48,61 @@ ->and($response['headers']['content-type']) ->toContain('text/markdown'); }); + +test('html is served when Accept prefers html over markdown via q-values', function () { + $response = makeRequest('/hello-world/', [ + 'Accept' => 'text/html, text/markdown;q=0.5', + ]); + + expect($response['status'])->toBe(200) + ->and($response['headers']['content-type'])->toContain('text/html'); +}); + +test('markdown is served when Accept prefers markdown over html via q-values', function () { + $response = makeRequest('/hello-world/', [ + 'Accept' => 'text/html;q=0.5, text/markdown;q=0.9', + ]); + + expect($response['status'])->toBe(200) + ->and($response['headers']['content-type'])->toContain('text/markdown'); +}); + +test('html is served when Accept is */* (default representation wins)', function () { + $response = makeRequest('/hello-world/', [ + 'Accept' => '*/*', + ]); + + expect($response['headers']['content-type'])->toContain('text/html'); +}); + +test('406 is returned when Accept rules out every supported representation', function () { + $response = makeRequest('/hello-world/', [ + 'Accept' => 'application/x-content-negotiation-probe', + ]); + + expect($response['status'])->toBe(406); +}); + +test('406 is returned for application/json on a page that does not serve json', function () { + $response = makeRequest('/hello-world/', [ + 'Accept' => 'application/json', + ]); + + expect($response['status'])->toBe(406); +}); + +test('html response advertises Vary: Accept for cache correctness', function () { + $response = makeRequest('/hello-world/'); + + expect($response['headers'])->toHaveKey('vary') + ->and($response['headers']['vary'])->toContain('Accept'); +}); + +test('markdown response advertises Vary: Accept', function () { + $response = makeRequest('/hello-world/', [ + 'Accept' => 'text/markdown', + ]); + + expect($response['headers'])->toHaveKey('vary') + ->and($response['headers']['vary'])->toContain('Accept'); +}); diff --git a/tests/Pest.php b/tests/Pest.php index 848f548..c47a015 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -22,13 +22,24 @@ function makeRequest(string $path, array $headers = []): array $context = stream_context_create($context_options); $response = @file_get_contents($url, false, $context); - // Parse response headers + // Parse status line and response headers. Repeated headers (e.g. Vary) are + // concatenated with ", " so assertions can inspect the combined value. + $status = 0; $responseHeaders = []; if (isset($http_response_header)) { foreach ($http_response_header as $header) { + if (preg_match('#^HTTP/\S+\s+(\d{3})#', $header, $m)) { + $status = (int) $m[1]; + + continue; + } if (str_contains($header, ':')) { [$key, $value] = explode(':', $header, 2); - $responseHeaders[strtolower(trim($key))] = trim($value); + $key = strtolower(trim($key)); + $value = trim($value); + $responseHeaders[$key] = isset($responseHeaders[$key]) + ? $responseHeaders[$key].', '.$value + : $value; } } } @@ -36,5 +47,6 @@ function makeRequest(string $path, array $headers = []): array return [ 'body' => $response !== false ? $response : '', 'headers' => $responseHeaders, + 'status' => $status, ]; }