render($exception, $request); // THEN expect($response)->toBeNull(); }); it('dispatches api/v1 requests to V1ApiExceptionRenderer', function (): void { // GIVEN $dispatcher = new ApiExceptionDispatcher(new V1ApiExceptionRenderer()); $request = Request::create('http://localhost/api/v1/employees/999', 'GET'); $exception = new NotFoundException('Employee not found'); // WHEN $response = $dispatcher->render($exception, $request); // THEN expect($response)->not->toBeNull() ->and($response?->getStatusCode())->toBe(404) ->and($response?->headers->get('Content-Type'))->toBe('application/problem+json'); /** @var array $data */ $data = json_decode((string) $response?->getContent(), true); expect($data['status'])->toBe(404) ->and($data['title'])->toBe('Resource Not Found') ->and($data['detail'])->toBe('Employee not found'); }); it('falls back to V1ApiExceptionRenderer for unversioned api requests', function (): void { // GIVEN $dispatcher = new ApiExceptionDispatcher(new V1ApiExceptionRenderer()); $request = Request::create('http://localhost/api/ping', 'GET'); $exception = new \DomainException('Domain invariant error'); // WHEN $response = $dispatcher->render($exception, $request); // THEN expect($response)->not->toBeNull() ->and($response?->getStatusCode())->toBe(422); /** @var array $data */ $data = json_decode((string) $response?->getContent(), true); expect($data['status'])->toBe(422) ->and($data['detail'])->toBe('Domain invariant error'); }); it('dispatches to V1ApiExceptionRenderer when request accepts JSON even on non-api path', function (): void { // GIVEN $dispatcher = new ApiExceptionDispatcher(new V1ApiExceptionRenderer()); $request = Request::create('http://localhost/custom-endpoint', 'GET', [], [], [], ['HTTP_ACCEPT' => 'application/json']); $exception = new NotFoundException('Custom endpoint not found'); // WHEN $response = $dispatcher->render($exception, $request); // THEN expect($response)->not->toBeNull() ->and($response?->getStatusCode())->toBe(404); }); });