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) diff --git a/tests/ApiPlatform/Contract/ErrorContractTest.php b/tests/ApiPlatform/Contract/ErrorContractTest.php index 037d66e..5c39069 100644 --- a/tests/ApiPlatform/Contract/ErrorContractTest.php +++ b/tests/ApiPlatform/Contract/ErrorContractTest.php @@ -52,4 +52,58 @@ public function testNotFoundErrorShape(): void $this->assertSame(404, $data['status']); $this->assertSame('/errors/404', $data['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 + { + $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 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 + { + $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']); + } }