From 947c090a3b34bdee6e9509bb934f2f5c2678599c Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 8 Jul 2026 16:12:41 +0200 Subject: [PATCH 1/3] test: pin the problem+json error/negotiation contract Add two ErrorContractTest cases for Accept: application/problem+json: an unauthenticated request is served as RFC 7807 problem+json (401, unprefixed title/detail/status/type), while requesting a resource item in problem+json yields 406 Not Acceptable (the resources only produce ld+json) with a problem+json error body. Closes the last untested corner of the error contract and documents that problem+json is not a resource representation. --- .../Contract/ErrorContractTest.php | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/ApiPlatform/Contract/ErrorContractTest.php b/tests/ApiPlatform/Contract/ErrorContractTest.php index 037d66e..4bf73b4 100644 --- a/tests/ApiPlatform/Contract/ErrorContractTest.php +++ b/tests/ApiPlatform/Contract/ErrorContractTest.php @@ -52,4 +52,51 @@ public function testNotFoundErrorShape(): void $this->assertSame(404, $data['status']); $this->assertSame('/errors/404', $data['type']); } + + /** + * When a client asks for problem+json, auth errors are served as RFC 7807 + * problem+json (unprefixed title/detail/status/type) rather than hydra. + * The 401 short-circuits before resource content negotiation, so it is + * available in the requested media type. + */ + public function testUnauthenticatedErrorIsAvailableAsProblemJson(): void + { + $client = self::createClient(defaultOptions: [ + 'headers' => ['accept' => ['application/problem+json']], + ]); + $response = $client->request('GET', '/api/v2/events'); + + $this->assertSame(Response::HTTP_UNAUTHORIZED, $response->getStatusCode()); + $this->assertStringContainsString('application/problem+json', $response->getHeaders(false)['content-type'][0] ?? ''); + + $data = $response->toArray(false); + $this->assertSame(401, $data['status']); + $this->assertSame('/errors/401', $data['type']); + $this->assertArrayHasKey('title', $data); + $this->assertArrayHasKey('detail', $data); + } + + /** + * The resources themselves only produce ld+json, so requesting an item with + * `Accept: application/problem+json` is not satisfiable — it yields 406 Not + * Acceptable (NOT a 404), and the 406 body is itself a problem+json error. + * Pinning this documents that problem+json is not a resource representation; + * consumers must read resources as ld+json. + */ + public function testProblemJsonIsNotAcceptableForResourceReads(): void + { + $client = self::createClient(defaultOptions: [ + 'headers' => [ + 'accept' => ['application/problem+json'], + 'x-api-key' => 'test_api_key', + ], + ]); + $response = $client->request('GET', '/api/v2/events/99999'); + + $this->assertSame(Response::HTTP_NOT_ACCEPTABLE, $response->getStatusCode()); + $this->assertStringContainsString('application/problem+json', $response->getHeaders(false)['content-type'][0] ?? ''); + + $data = $response->toArray(false); + $this->assertSame(406, $data['status']); + } } From e5c9d61dfc15647dedcdc94bda3ae5f1163c714a Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 8 Jul 2026 16:13:20 +0200 Subject: [PATCH 2/3] docs: add CHANGELOG entry for problem+json error contract test --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03d3236..878fd75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ See [keep a changelog] for information about writing changes to this log. ## [Unreleased] +- [PR-52](https://github.com/itk-dev/event-database-api/pull/52) + Pin the problem+json error contract: 401 served as RFC 7807, resource reads in problem+json yield 406 - [PR-51](https://github.com/itk-dev/event-database-api/pull/51) Show the API-spec diff summary inline in the PR comment (collapsed) alongside the oasdiff review link - [PR-49](https://github.com/itk-dev/event-database-api/pull/49) From 7fa98e3117870e33b1de9fc9a0c19fbaca838bc2 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 8 Jul 2026 16:27:29 +0200 Subject: [PATCH 3/3] docs: cite the formats/error config behind the problem+json contract --- .../Contract/ErrorContractTest.php | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/tests/ApiPlatform/Contract/ErrorContractTest.php b/tests/ApiPlatform/Contract/ErrorContractTest.php index 4bf73b4..5c39069 100644 --- a/tests/ApiPlatform/Contract/ErrorContractTest.php +++ b/tests/ApiPlatform/Contract/ErrorContractTest.php @@ -54,10 +54,13 @@ public function testNotFoundErrorShape(): void } /** - * When a client asks for problem+json, auth errors are served as RFC 7807 - * problem+json (unprefixed title/detail/status/type) rather than hydra. - * The 401 short-circuits before resource content negotiation, so it is - * available in the requested media type. + * `rfc_7807_compliant_errors: true` (config/packages/api_platform.yaml) + * registers problem+json as an error format, so an auth error requested as + * `application/problem+json` is served as RFC 7807 (unprefixed + * title/detail/status/type) rather than hydra. The 401 also short-circuits + * before resource content negotiation, so the format is honoured. + * + * @see https://api-platform.com/docs/core/content-negotiation/ */ public function testUnauthenticatedErrorIsAvailableAsProblemJson(): void { @@ -77,11 +80,15 @@ public function testUnauthenticatedErrorIsAvailableAsProblemJson(): void } /** - * The resources themselves only produce ld+json, so requesting an item with - * `Accept: application/problem+json` is not satisfiable — it yields 406 Not - * Acceptable (NOT a 404), and the 406 body is itself a problem+json error. - * Pinning this documents that problem+json is not a resource representation; - * consumers must read resources as ld+json. + * The only resource format configured under `formats` in + * config/packages/api_platform.yaml is `application/ld+json`, so content + * negotiation cannot satisfy `Accept: application/problem+json` on a resource + * read: it yields 406 Not Acceptable (NOT a 404), and the 406 body is itself + * a problem+json error. Pinning this documents that problem+json is not a + * resource representation (consumers must read resources as ld+json) and + * guards the `formats` allow-list — adding problem+json there would flip this. + * + * @see https://api-platform.com/docs/core/content-negotiation/ */ public function testProblemJsonIsNotAcceptableForResourceReads(): void {