Skip to content

Commit 668e0ec

Browse files
author
Ricardo Bazan
committed
Adds 503 maintenance error handling
Ensures API requests return a consistent maintenance response when the service is unavailable instead of falling back to generic HTTP error handling. Adds a dedicated default message for maintenance mode and covers the new behavior with a feature test.
1 parent 924ccdb commit 668e0ec

File tree

5 files changed

+36
-1
lines changed

5 files changed

+36
-1
lines changed

.phpunit.cache/test-results

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"version":2,"defects":[],"times":{"Hardcodear\\ApiResponseService\\Tests\\Contract\\ResponseShapeTest::test_success_contract_shape_is_stable":0.057,"Hardcodear\\ApiResponseService\\Tests\\Contract\\ResponseShapeTest::test_error_contract_shape_is_stable":0,"Hardcodear\\ApiResponseService\\Tests\\Feature\\ExceptionApiRegistrarTest::test_bind_registers_renderer_and_maps_known_api_exceptions":0.014,"Hardcodear\\ApiResponseService\\Tests\\Feature\\ExceptionApiRegistrarTest::test_bind_does_not_intercept_non_api_routes":0,"Hardcodear\\ApiResponseService\\Tests\\Regression\\NullOmissionTest::test_data_key_is_omitted_when_null":0.001,"Hardcodear\\ApiResponseService\\Tests\\Regression\\NullOmissionTest::test_errors_key_is_omitted_when_null":0,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_success_response_includes_data_when_present":0,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_success_response_omits_data_when_null":0,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_error_response_wraps_object_errors_in_array":0.001,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_error_response_wraps_associative_array_errors_in_array":0,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_error_response_keeps_indexed_array_errors_as_is":0,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_error_response_omits_errors_when_null":0,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_success_method_uses_200_status":0,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_error_method_uses_500_status":0,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_not_found_method_uses_404_status":0,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_validation_method_uses_422_status":0,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_forbidden_method_uses_403_status":0,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_unauthorized_method_uses_401_status":0,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_server_error_method_uses_500_status":0,"Hardcodear\\ApiResponseService\\Tests\\Unit\\FacadeTest::test_facade_resolves_accessor_and_returns_json_response":0.001,"Hardcodear\\ApiResponseService\\Tests\\Unit\\HelperTest::test_apiresponse_helper_exists_and_resolves_service":0,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ServiceProviderTest::test_service_is_registered_in_container_as_singleton":0,"Hardcodear\\ApiResponseService\\Tests\\Feature\\ExceptionApiRegistrarTest::test_bind_maps_validation_exception_to_422_with_errors":0.006,"Hardcodear\\ApiResponseService\\Tests\\Feature\\ExceptionApiRegistrarTest::test_bind_maps_authorization_exception_to_403":0.001,"Hardcodear\\ApiResponseService\\Tests\\Feature\\ExceptionApiRegistrarTest::test_bind_maps_generic_http_exception_status_for_api_routes":0}}
1+
{"version":2,"defects":[],"times":{"Hardcodear\\ApiResponseService\\Tests\\Contract\\ResponseShapeTest::test_success_contract_shape_is_stable":0.113,"Hardcodear\\ApiResponseService\\Tests\\Contract\\ResponseShapeTest::test_error_contract_shape_is_stable":0.002,"Hardcodear\\ApiResponseService\\Tests\\Feature\\ExceptionApiRegistrarTest::test_bind_registers_renderer_and_maps_known_api_exceptions":0.01,"Hardcodear\\ApiResponseService\\Tests\\Feature\\ExceptionApiRegistrarTest::test_bind_does_not_intercept_non_api_routes":0.002,"Hardcodear\\ApiResponseService\\Tests\\Regression\\NullOmissionTest::test_data_key_is_omitted_when_null":0.002,"Hardcodear\\ApiResponseService\\Tests\\Regression\\NullOmissionTest::test_errors_key_is_omitted_when_null":0.003,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_success_response_includes_data_when_present":0.003,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_success_response_omits_data_when_null":0.003,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_error_response_wraps_object_errors_in_array":0.002,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_error_response_wraps_associative_array_errors_in_array":0.003,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_error_response_keeps_indexed_array_errors_as_is":0.002,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_error_response_omits_errors_when_null":0.002,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_success_method_uses_200_status":0.003,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_error_method_uses_500_status":0.003,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_not_found_method_uses_404_status":0.002,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_validation_method_uses_422_status":0.002,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_forbidden_method_uses_403_status":0.002,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_unauthorized_method_uses_401_status":0.002,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ApiResponseServiceTest::test_server_error_method_uses_500_status":0.002,"Hardcodear\\ApiResponseService\\Tests\\Unit\\FacadeTest::test_facade_resolves_accessor_and_returns_json_response":0.003,"Hardcodear\\ApiResponseService\\Tests\\Unit\\HelperTest::test_apiresponse_helper_exists_and_resolves_service":0.002,"Hardcodear\\ApiResponseService\\Tests\\Unit\\ServiceProviderTest::test_service_is_registered_in_container_as_singleton":0.002,"Hardcodear\\ApiResponseService\\Tests\\Feature\\ExceptionApiRegistrarTest::test_bind_maps_validation_exception_to_422_with_errors":0.014,"Hardcodear\\ApiResponseService\\Tests\\Feature\\ExceptionApiRegistrarTest::test_bind_maps_authorization_exception_to_403":0.003,"Hardcodear\\ApiResponseService\\Tests\\Feature\\ExceptionApiRegistrarTest::test_bind_maps_generic_http_exception_status_for_api_routes":0.003,"Hardcodear\\ApiResponseService\\Tests\\Feature\\ExceptionApiRegistrarTest::test_bind_maps_service_unavailable_exception_to_503":0.003}}

config/apiresponse.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@
1616
'authorization' => 'No autorizado para este recurso',
1717
'client_error' => 'Error en la solicitud',
1818
'server_error' => 'Error interno del servidor',
19+
'service_unavailable' => 'El sistema se encuentra en mantenimiento',
1920
],
2021
];

src/ApiResponseService.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class ApiResponseService
1616
public const HTTP_INTERNAL_SERVER_ERROR = 500;
1717
public const HTTP_TOO_MANY_REQUESTS = 429;
1818
public const HTTP_METHOD_NOT_ALLOWED = 405;
19+
public const HTTP_SERVICE_UNAVAILABLE = 503;
1920

2021
public function successResponse(int $status = self::HTTP_OK, ?string $message = null, mixed $data = null): JsonResponse
2122
{

src/ExceptionApiRegistrar.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
1313
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
1414
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
15+
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
1516
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
1617
use Symfony\Component\Routing\Exception\RouteNotFoundException;
1718

@@ -53,6 +54,10 @@ public static function bind(Exceptions $exceptions): void
5354
return apiresponse()->errorResponse(ApiResponseService::HTTP_METHOD_NOT_ALLOWED, self::message('method_not_allowed', 'Metodo HTTP no permitido para esta ruta'));
5455
}
5556

57+
if ($e instanceof ServiceUnavailableHttpException) {
58+
return apiresponse()->errorResponse(ApiResponseService::HTTP_SERVICE_UNAVAILABLE, self::message('service_unavailable', 'El sistema se encuentra en mantenimiento'));
59+
}
60+
5661
if ($e instanceof HttpExceptionInterface) {
5762
$status = $e->getStatusCode();
5863
$message = $e->getMessage();

tests/Feature/ExceptionApiRegistrarTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
1616
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
1717
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
18+
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
1819
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
1920
use Symfony\Component\Routing\Exception\RouteNotFoundException;
2021
use Throwable;
@@ -148,6 +149,33 @@ public function test_bind_maps_authorization_exception_to_403(): void
148149
$this->assertSame(ApiResponseService::HTTP_FORBIDDEN, $response->getStatusCode());
149150
}
150151

152+
public function test_bind_maps_service_unavailable_exception_to_503(): void
153+
{
154+
$renderer = null;
155+
156+
$exceptions = $this->getMockBuilder(Exceptions::class)
157+
->disableOriginalConstructor()
158+
->onlyMethods(['render'])
159+
->getMock();
160+
161+
$exceptions->expects($this->once())
162+
->method('render')
163+
->willReturnCallback(function (callable $callback) use (&$renderer) {
164+
$renderer = $callback;
165+
166+
return null;
167+
});
168+
169+
ExceptionApiRegistrar::bind($exceptions);
170+
171+
$request = Request::create('/api/status', 'GET');
172+
$response = $renderer(new ServiceUnavailableHttpException(null, 'maintenance'), $request);
173+
174+
$this->assertInstanceOf(JsonResponse::class, $response);
175+
$this->assertSame(ApiResponseService::HTTP_SERVICE_UNAVAILABLE, $response->getStatusCode());
176+
$this->assertSame(ApiResponseService::HTTP_SERVICE_UNAVAILABLE, $response->getData(true)['status']);
177+
}
178+
151179
public function test_bind_maps_generic_http_exception_status_for_api_routes(): void
152180
{
153181
$renderer = null;

0 commit comments

Comments
 (0)