From ba2beca107af015c23e4a13c0120648c27ab7382 Mon Sep 17 00:00:00 2001 From: Ron Rise Date: Mon, 1 Dec 2025 14:55:34 -0500 Subject: [PATCH] feat: implement NotFoundResponse and ServerErrorResponse classes with corresponding tests --- config.php | 1 + docker-compose.yml | 1 + src/Api.php | 25 ++---- src/Http/Middleware/JwtMiddleware.php | 2 +- src/Http/Responses/NotFoundResponse.php | 40 +++++++++ src/Http/Responses/ServerErrorResponse.php | 56 ++++++++++++ tests/Http/Responses/NotFoundResponseTest.php | 22 +++++ .../Responses/ServerErrorResponseTest.php | 89 +++++++++++++++++++ 8 files changed, 219 insertions(+), 17 deletions(-) create mode 100644 src/Http/Responses/NotFoundResponse.php create mode 100644 src/Http/Responses/ServerErrorResponse.php create mode 100644 tests/Http/Responses/NotFoundResponseTest.php create mode 100644 tests/Http/Responses/ServerErrorResponseTest.php diff --git a/config.php b/config.php index 9da772b..48dd820 100644 --- a/config.php +++ b/config.php @@ -6,6 +6,7 @@ return [ 'app' => [ 'log_level' => Env::get('LOG_LEVEL', 'debug'), + 'dev_mode' => Env::get('DEV_MODE', false, 'bool'), ], /** diff --git a/docker-compose.yml b/docker-compose.yml index 5d91921..a7c1f4c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -106,6 +106,7 @@ services: DEBUG: 1 REDIS_HOST: redis DB_HOST: postgres + DEV_MODE: 1 ## Kafka and Zookeeper for local development kafka-ui: diff --git a/src/Api.php b/src/Api.php index c3816a7..173bb2f 100644 --- a/src/Api.php +++ b/src/Api.php @@ -16,9 +16,10 @@ use Siteworxpro\App\Http\JsonResponseFactory; use Siteworxpro\App\Http\Middleware\CorsMiddleware; use Siteworxpro\App\Http\Middleware\JwtMiddleware; use Siteworxpro\App\Http\Middleware\ScopeMiddleware; +use Siteworxpro\App\Http\Responses\NotFoundResponse; +use Siteworxpro\App\Http\Responses\ServerErrorResponse; use Siteworxpro\App\Services\Facades\Config; use Siteworxpro\App\Services\Facades\Logger; -use Siteworxpro\HttpStatus\CodesEnum; use Spiral\RoadRunner\Http\PSR7Worker; use Spiral\RoadRunner\Worker; @@ -112,28 +113,20 @@ class Api $response = $this->router->handle($request); $this->worker->respond($response); } catch (MethodNotAllowedException | NotFoundException) { + $uri = ''; + if (isset($request)) { + $uri = $request->getUri()->getPath(); + } + $this->worker->respond( - JsonResponseFactory::createJsonResponse( - ['status_code' => 404, 'reason_phrase' => 'Not Found'], - CodesEnum::NOT_FOUND - ) + JsonResponseFactory::createJsonResponse(new NotFoundResponse($uri)) ); } catch (\Throwable $e) { Logger::error($e->getMessage()); Logger::error($e->getTraceAsString()); - $json = ['status_code' => 500, 'reason_phrase' => 'Server Error']; - if (Config::get("server.dev_mode")) { - $json = [ - 'status_code' => 500, - 'reason_phrase' => 'Server Error', - 'message' => $e->getMessage(), - 'trace' => $e->getTraceAsString(), - ]; - } - $this->worker->respond( - JsonResponseFactory::createJsonResponse($json, CodesEnum::INTERNAL_SERVER_ERROR) + JsonResponseFactory::createJsonResponse(new ServerErrorResponse($e)) ); } } diff --git a/src/Http/Middleware/JwtMiddleware.php b/src/Http/Middleware/JwtMiddleware.php index a9f87e1..2a27099 100644 --- a/src/Http/Middleware/JwtMiddleware.php +++ b/src/Http/Middleware/JwtMiddleware.php @@ -187,7 +187,7 @@ class JwtMiddleware extends Middleware } elseif (str_contains($keyConfig, '.well-known/')) { $jwt = explode('.', $token); if (count($jwt) !== 3) { - throw new \RuntimeException('Invalid JWT structure for JWKS key retrieval.'); + throw new InvalidTokenStructure('Invalid JWT structure for JWKS key retrieval.'); } $header = json_decode(base64_decode($jwt[0]), true, 512, JSON_THROW_ON_ERROR); $keyId = $header['kid'] ?? '0'; // Default to '0' if no kid present diff --git a/src/Http/Responses/NotFoundResponse.php b/src/Http/Responses/NotFoundResponse.php new file mode 100644 index 0000000..4a62504 --- /dev/null +++ b/src/Http/Responses/NotFoundResponse.php @@ -0,0 +1,40 @@ + CodesEnum::NOT_FOUND->value, + 'message' => 'The requested resource ' . $this->uri . ' was not found.', + 'context' => $this->context, + ]; + } +} diff --git a/src/Http/Responses/ServerErrorResponse.php b/src/Http/Responses/ServerErrorResponse.php new file mode 100644 index 0000000..36e0ca3 --- /dev/null +++ b/src/Http/Responses/ServerErrorResponse.php @@ -0,0 +1,56 @@ + $this->e->getCode() != 0 ? + $this->e->getCode() : + CodesEnum::INTERNAL_SERVER_ERROR->value, + 'message' => $this->e->getMessage(), + 'file' => $this->e->getFile(), + 'line' => $this->e->getLine(), + 'trace' => $this->e->getTrace(), + 'context' => $this->context, + ]; + } + + return [ + 'status_code' => $this->e->getCode() != 0 ? + $this->e->getCode() : + CodesEnum::INTERNAL_SERVER_ERROR->value, + 'message' => 'An internal server error occurred.', + ]; + } +} diff --git a/tests/Http/Responses/NotFoundResponseTest.php b/tests/Http/Responses/NotFoundResponseTest.php new file mode 100644 index 0000000..23d1449 --- /dev/null +++ b/tests/Http/Responses/NotFoundResponseTest.php @@ -0,0 +1,22 @@ + 'value']); + + $expected = [ + 'status_code' => 404, + 'message' => 'The requested resource /api/resource was not found.', + 'context' => ['key' => 'value'], + ]; + + $this->assertEquals($expected, $response->toArray()); + } +} diff --git a/tests/Http/Responses/ServerErrorResponseTest.php b/tests/Http/Responses/ServerErrorResponseTest.php new file mode 100644 index 0000000..3fe30a4 --- /dev/null +++ b/tests/Http/Responses/ServerErrorResponseTest.php @@ -0,0 +1,89 @@ + 'data_processing']); + + $expected = [ + 'status_code' => 500, + 'message' => 'A Test Error occurred.', + 'context' => [ + 'operation' => 'data_processing' + ], + 'file' => '/app/tests/Http/Responses/ServerErrorResponseTest.php', + 'line' => $e->getLine(), + 'trace' => $e->getTrace(), + ]; + + $this->assertEquals($expected, $response->toArray()); + } + } + + public function testToArrayNotInDevMode(): void + { + try { + throw new \Exception('A Test Error occurred.'); + } catch (\Exception $exception) { + $response = new ServerErrorResponse($exception); + + $expected = [ + 'status_code' => 500, + 'message' => 'An internal server error occurred.', + ]; + + $this->assertEquals($expected, $response->toArray()); + } + } + + public function testToArrayIfCodeIsSet(): void + { + try { + throw new \Exception('A Test Error occurred.', 1234); + } catch (\Exception $exception) { + $response = new ServerErrorResponse($exception); + + $expected = [ + 'status_code' => 1234, + 'message' => 'An internal server error occurred.', + ]; + + $this->assertEquals($expected, $response->toArray()); + } + } + + public function testToArrayIfCodeIsSetDevMode(): void + { + Config::set('app.dev_mode', true); + + try { + throw new \Exception('A Test Error occurred.', 1234); + } catch (\Exception $exception) { + $response = new ServerErrorResponse($exception); + + $expected = [ + 'status_code' => 1234, + 'message' => 'A Test Error occurred.', + 'file' => '/app/tests/Http/Responses/ServerErrorResponseTest.php', + 'line' => $exception->getLine(), + 'trace' => $exception->getTrace(), + 'context' => [], + ]; + + $this->assertEquals($expected, $response->toArray()); + } + } +}