diff --git a/tests/Unit/RouterTest.php b/tests/Unit/RouterTest.php index a5584cd..f294816 100644 --- a/tests/Unit/RouterTest.php +++ b/tests/Unit/RouterTest.php @@ -4,6 +4,7 @@ namespace Tests\Unit; use Exception; use Libs\Neuron; +use Libs\Request; use Libs\Router; use LogicException; use PDOException; @@ -25,10 +26,14 @@ use TypeError; final class RouterTest extends TestCase { private array $serverBackup = []; + private array $getBackup = []; + private array $postBackup = []; protected function setUp(): void { $this->serverBackup = $_SERVER; + $this->getBackup = $_GET; + $this->postBackup = $_POST; $_SERVER['REQUEST_METHOD'] = 'GET'; unset($_SERVER['HTTP_ACCEPT']); http_response_code(200); @@ -38,6 +43,8 @@ final class RouterTest extends TestCase protected function tearDown(): void { $_SERVER = $this->serverBackup; + $_GET = $this->getBackup; + $_POST = $this->postBackup; http_response_code(200); $this->resetRouterState(); } @@ -206,4 +213,56 @@ final class RouterTest extends TestCase $this->assertSame('{"status":"ok"}', $output); $this->assertSame(200, http_response_code()); } + + #[Test] + public function customOnInvalidExceptionReachesTheExceptionCallbackThroughTheBoundary(): void + { + $_SERVER['REQUEST_METHOD'] = 'POST'; + $_SERVER['REQUEST_URI'] = '/signup'; + $_SERVER['DOCUMENT_ROOT'] = '/nonexistent-docroot'; + $_POST = ['age' => '5']; + + $caught = []; + Router::$exceptionCallback = function (\Throwable $exception) use (&$caught): void { + $caught[] = $exception; + }; + + $finalRan = false; + Router::post('/signup', function (StrictSignup $request) use (&$finalRan): void { + $finalRan = true; + }); + + Router::apply('/signup'); + + // The Request was built by the container for the final callback and its + // custom onInvalid() threw during dependency resolution: the custom type + // must arrive intact to the callback, and the final callback must not run. + $this->assertCount(1, $caught); + $this->assertInstanceOf(RequestRejected::class, $caught[0]); + $this->assertSame('The age must be at least 18.', $caught[0]->getMessage()); + $this->assertSame(400, $caught[0]->getCode(), 'The status channel must survive custom exception types'); + $this->assertFalse($finalRan, 'A Request that throws during DI resolution must stop the route'); + } +} + +// phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses +/** + * Fixtures locales de la prueba de simetrĂ­a: un tipo de excepcion propio que + * un Request hijo lanza desde su onInvalid() reescrito. + */ +final class RequestRejected extends Exception +{ +} + +final class StrictSignup extends Request +{ + public function rules(): array + { + return ['age' => 'required|min:18']; + } + + public function onInvalid(string $error): never + { + throw new RequestRejected($error, 400); + } }