145 lines
6 KiB
PHP
145 lines
6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Integration\Presentation\Api\V1;
|
|
|
|
use Application\Shared\Exceptions\NotFoundException;
|
|
use Illuminate\Http\Exceptions\ThrottleRequestsException;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Config;
|
|
use Illuminate\Validation\ValidationException;
|
|
use Presentation\Api\V1\Exceptions\ApiExceptionRenderer;
|
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
|
|
|
covers(ApiExceptionRenderer::class);
|
|
|
|
describe('V1 API Exception Renderer Integration', function (): void {
|
|
it('renders NotFoundException as 404 Problem Details JSON', function (): void {
|
|
// GIVEN
|
|
$renderer = new ApiExceptionRenderer();
|
|
$request = Request::create('http://localhost/api/v1/employees/999', 'GET');
|
|
$exception = new NotFoundException('Employee with ID 999 not found.');
|
|
|
|
// WHEN
|
|
$response = $renderer->render($exception, $request);
|
|
|
|
// THEN
|
|
expect($response->getStatusCode())->toBe(404)
|
|
->and($response->headers->get('Content-Type'))->toBe('application/problem+json');
|
|
|
|
/** @var array<string, mixed> $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 with ID 999 not found.')
|
|
->and($data['type'])->toBe('http://localhost/errors/404')
|
|
->and($data['instance'])->toBe('/api/v1/employees/999');
|
|
});
|
|
|
|
it('renders ValidationException as 422 Problem Details JSON with invalidParams', function (): void {
|
|
// GIVEN
|
|
$renderer = new ApiExceptionRenderer();
|
|
$request = Request::create('http://localhost/api/v1/employees', 'POST');
|
|
$validator = validator(['email' => 'invalid'], ['email' => 'email|required']);
|
|
|
|
try {
|
|
$validator->validate();
|
|
} catch (ValidationException $exception) {
|
|
// WHEN
|
|
$response = $renderer->render($exception, $request);
|
|
|
|
// THEN
|
|
expect($response->getStatusCode())->toBe(422)
|
|
->and($response->headers->get('Content-Type'))->toBe('application/problem+json');
|
|
|
|
/** @var array<string, mixed> $data */
|
|
$data = json_decode((string) $response->getContent(), true);
|
|
expect($data['status'])->toBe(422)
|
|
->and($data['title'])->toBe('Unprocessable Entity')
|
|
->and($data['invalidParams'])->toBeArray()
|
|
->and($data['invalidParams'][0]['name'])->toBe('email');
|
|
}
|
|
});
|
|
|
|
it('renders DomainException as 422 Problem Details JSON', function (): void {
|
|
// GIVEN
|
|
$renderer = new ApiExceptionRenderer();
|
|
$request = Request::create('http://localhost/api/v1/employees', 'POST');
|
|
$exception = new \DomainException('Invalid employee number format');
|
|
|
|
// WHEN
|
|
$response = $renderer->render($exception, $request);
|
|
|
|
// THEN
|
|
expect($response->getStatusCode())->toBe(422)
|
|
->and($response->headers->get('Content-Type'))->toBe('application/problem+json');
|
|
|
|
/** @var array<string, mixed> $data */
|
|
$data = json_decode((string) $response->getContent(), true);
|
|
expect($data['status'])->toBe(422)
|
|
->and($data['title'])->toBe('Unprocessable Entity')
|
|
->and($data['invalidParams'][0]['name'])->toBe('domain_invariant')
|
|
->and($data['invalidParams'][0]['reason'])->toBe('Invalid employee number format');
|
|
});
|
|
|
|
it('renders ThrottleRequestsException as 429 Problem Details JSON with rate headers', function (): void {
|
|
// GIVEN
|
|
$renderer = new ApiExceptionRenderer();
|
|
$request = Request::create('http://localhost/api/v1/employees', 'GET');
|
|
$exception = new ThrottleRequestsException('Too Many Attempts.', null, ['Retry-After' => '60']);
|
|
|
|
// WHEN
|
|
$response = $renderer->render($exception, $request);
|
|
|
|
// THEN
|
|
expect($response->getStatusCode())->toBe(429)
|
|
->and($response->headers->get('Content-Type'))->toBe('application/problem+json')
|
|
->and($response->headers->get('Retry-After'))->toBe('60');
|
|
|
|
/** @var array<string, mixed> $data */
|
|
$data = json_decode((string) $response->getContent(), true);
|
|
expect($data['status'])->toBe(429)
|
|
->and($data['title'])->toBe('Too Many Requests');
|
|
});
|
|
|
|
it('renders HttpExceptionInterface exception with status code and detail', function (): void {
|
|
// GIVEN
|
|
$renderer = new ApiExceptionRenderer();
|
|
$request = Request::create('http://localhost/api/v1/employees', 'DELETE');
|
|
$exception = new AccessDeniedHttpException('Access denied');
|
|
|
|
// WHEN
|
|
$response = $renderer->render($exception, $request);
|
|
|
|
// THEN
|
|
expect($response->getStatusCode())->toBe(403)
|
|
->and($response->headers->get('Content-Type'))->toBe('application/problem+json');
|
|
|
|
/** @var array<string, mixed> $data */
|
|
$data = json_decode((string) $response->getContent(), true);
|
|
expect($data['status'])->toBe(403)
|
|
->and($data['title'])->toBe('Forbidden')
|
|
->and($data['detail'])->toBe('Access denied');
|
|
});
|
|
|
|
it('renders generic Throwable as 500 Problem Details JSON with debug info based on config', function (): void {
|
|
// GIVEN
|
|
Config::set('app.debug', true);
|
|
$renderer = new ApiExceptionRenderer();
|
|
$request = Request::create('http://localhost/api/v1/employees', 'GET');
|
|
$exception = new \RuntimeException('Database connection failure');
|
|
|
|
// WHEN
|
|
$response = $renderer->render($exception, $request);
|
|
|
|
// THEN
|
|
expect($response->getStatusCode())->toBe(500);
|
|
|
|
/** @var array<string, mixed> $data */
|
|
$data = json_decode((string) $response->getContent(), true);
|
|
expect($data['status'])->toBe(500)
|
|
->and($data['title'])->toBe('Internal Server Error')
|
|
->and($data['detail'])->toBe('Database connection failure');
|
|
});
|
|
});
|