diff options
| author | Brett Parson <brett@brett-parson.com> | 2026-08-29 15:56:31 -0500 |
|---|---|---|
| committer | Brett Parson <brett@brett-parson.com> | 2026-08-29 15:56:31 -0500 |
| commit | 965551532d684456b74baf960157c0c2cef7335d (patch) | |
| tree | 1445afbc47730caa11d08fd84d9d65db132821f5 /tests/Fixtures | |
| parent | d039a5d62a9ac6ababae70d9d96422ca59f2b2d4 (diff) | |
| download | miniroute-965551532d684456b74baf960157c0c2cef7335d.tar.gz miniroute-965551532d684456b74baf960157c0c2cef7335d.zip | |
Add regression and integration test suite
Port the stress-test findings into PHPUnit: realistic brett-parson.com
route model, known-issue baselines, router boundary cases, and a
standalone scaling benchmark (composer bench).
Diffstat (limited to 'tests/Fixtures')
| -rw-r--r-- | tests/Fixtures/AdminLoggingMiddleware.php | 31 | ||||
| -rw-r--r-- | tests/Fixtures/CollidingController.php | 21 | ||||
| -rw-r--r-- | tests/Fixtures/CsrfLoggingMiddleware.php | 30 | ||||
| -rw-r--r-- | tests/Fixtures/DuplicateParamController.php | 21 | ||||
| -rw-r--r-- | tests/Fixtures/MixedVisibilityController.php | 27 | ||||
| -rw-r--r-- | tests/Fixtures/OnlyGetController.php | 21 | ||||
| -rw-r--r-- | tests/Fixtures/RealisticAdminController.php | 98 | ||||
| -rw-r--r-- | tests/Fixtures/RealisticPublicController.php | 72 | ||||
| -rw-r--r-- | tests/Fixtures/StatefulController.php | 23 | ||||
| -rw-r--r-- | tests/Fixtures/StatefulMiddleware.php | 26 |
10 files changed, 370 insertions, 0 deletions
diff --git a/tests/Fixtures/AdminLoggingMiddleware.php b/tests/Fixtures/AdminLoggingMiddleware.php new file mode 100644 index 0000000..8613475 --- /dev/null +++ b/tests/Fixtures/AdminLoggingMiddleware.php @@ -0,0 +1,31 @@ +<?php + +declare(strict_types=1); + +namespace BrettParson\MiniRoute\Tests\Fixtures; + +use ArrayObject; +use BrettParson\MiniRoute\Http\RequestInterface; +use BrettParson\MiniRoute\Http\ResponseInterface; +use BrettParson\MiniRoute\Middleware\MiddlewareInterface; + +/** + * Logs before/after into a shared log. Stands in for the real app's admin + * gate; distinct class from CsrfLoggingMiddleware so both group layers can + * be resolved independently and their onion order asserted. + */ +final class AdminLoggingMiddleware implements MiddlewareInterface +{ + public function __construct(private readonly ArrayObject $log) + { + } + + public function handle(RequestInterface $request, callable $next): ResponseInterface + { + $this->log->append('admin:before'); + $response = $next($request); + $this->log->append('admin:after'); + + return $response; + } +} diff --git a/tests/Fixtures/CollidingController.php b/tests/Fixtures/CollidingController.php new file mode 100644 index 0000000..434ffbf --- /dev/null +++ b/tests/Fixtures/CollidingController.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +namespace BrettParson\MiniRoute\Tests\Fixtures; + +use BrettParson\MiniRoute\Attribute\Get; +use BrettParson\MiniRoute\Http\ResponseInterface; + +/** + * Collides with FakeController::index (GET /entries) to probe + * cross-controller duplicate detection at registration. + */ +final class CollidingController +{ + #[Get('/entries')] + public function otherIndex(): ResponseInterface + { + return new TestResponse(200, 'other'); + } +} diff --git a/tests/Fixtures/CsrfLoggingMiddleware.php b/tests/Fixtures/CsrfLoggingMiddleware.php new file mode 100644 index 0000000..4fa25d5 --- /dev/null +++ b/tests/Fixtures/CsrfLoggingMiddleware.php @@ -0,0 +1,30 @@ +<?php + +declare(strict_types=1); + +namespace BrettParson\MiniRoute\Tests\Fixtures; + +use ArrayObject; +use BrettParson\MiniRoute\Http\RequestInterface; +use BrettParson\MiniRoute\Http\ResponseInterface; +use BrettParson\MiniRoute\Middleware\MiddlewareInterface; + +/** + * Logs before/after into a shared log. Stands in for the real app's CSRF + * guard, applied to the /admin prefix for POST only. + */ +final class CsrfLoggingMiddleware implements MiddlewareInterface +{ + public function __construct(private readonly ArrayObject $log) + { + } + + public function handle(RequestInterface $request, callable $next): ResponseInterface + { + $this->log->append('csrf:before'); + $response = $next($request); + $this->log->append('csrf:after'); + + return $response; + } +} diff --git a/tests/Fixtures/DuplicateParamController.php b/tests/Fixtures/DuplicateParamController.php new file mode 100644 index 0000000..8ea3060 --- /dev/null +++ b/tests/Fixtures/DuplicateParamController.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +namespace BrettParson\MiniRoute\Tests\Fixtures; + +use BrettParson\MiniRoute\Attribute\Get; +use BrettParson\MiniRoute\Http\ResponseInterface; + +/** + * Declares a route with a duplicated parameter name — a route that can + * never match (v0.1.0 baseline; see miniroute-uhs.1). + */ +final class DuplicateParamController +{ + #[Get('/dup/{id}/x/{id}')] + public function run(): ResponseInterface + { + return new TestResponse(200, 'dup'); + } +} diff --git a/tests/Fixtures/MixedVisibilityController.php b/tests/Fixtures/MixedVisibilityController.php new file mode 100644 index 0000000..2f5ccff --- /dev/null +++ b/tests/Fixtures/MixedVisibilityController.php @@ -0,0 +1,27 @@ +<?php + +declare(strict_types=1); + +namespace BrettParson\MiniRoute\Tests\Fixtures; + +use BrettParson\MiniRoute\Attribute\Get; +use BrettParson\MiniRoute\Http\ResponseInterface; + +/** + * One public and one private method carrying route attributes. The private + * route is invisible to the v0.1.0 loader (see miniroute-uhs.4). + */ +final class MixedVisibilityController +{ + #[Get('/pub')] + public function pub(): ResponseInterface + { + return new TestResponse(200, 'pub'); + } + + #[Get('/secret')] + private function secret(): ResponseInterface + { + return new TestResponse(200, 'secret'); + } +} diff --git a/tests/Fixtures/OnlyGetController.php b/tests/Fixtures/OnlyGetController.php new file mode 100644 index 0000000..0ab18f4 --- /dev/null +++ b/tests/Fixtures/OnlyGetController.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +namespace BrettParson\MiniRoute\Tests\Fixtures; + +use BrettParson\MiniRoute\Attribute\Get; +use BrettParson\MiniRoute\Http\ResponseInterface; + +/** + * A single GET route, used to probe method handling: POST and HEAD on the + * same path (v0.1.0 baselines; see miniroute-uhs.2 / miniroute-uhs.3). + */ +final class OnlyGetController +{ + #[Get('/only')] + public function get(): ResponseInterface + { + return new TestResponse(200, 'only-get'); + } +} diff --git a/tests/Fixtures/RealisticAdminController.php b/tests/Fixtures/RealisticAdminController.php new file mode 100644 index 0000000..e49fcd2 --- /dev/null +++ b/tests/Fixtures/RealisticAdminController.php @@ -0,0 +1,98 @@ +<?php + +declare(strict_types=1); + +namespace BrettParson\MiniRoute\Tests\Fixtures; + +use BrettParson\MiniRoute\Attribute\Get; +use BrettParson\MiniRoute\Attribute\Post; +use BrettParson\MiniRoute\Http\RequestInterface; +use BrettParson\MiniRoute\Http\ResponseInterface; + +/** + * Admin half of the brett-parson.com route model (13 routes). + * + * Includes the ordering nightmare case: POST /admin/entries/preview is a + * literal that must beat POST /admin/entries/{id}, independent of + * registration order. + */ +final class RealisticAdminController +{ + #[Get('/admin')] + public function dashboard(): ResponseInterface + { + return new TestResponse(200, 'admin-dashboard'); + } + + #[Get('/admin/notes')] + public function notesIndex(): ResponseInterface + { + return new TestResponse(200, 'notes-index'); + } + + #[Get('/admin/notes/new')] + public function notesNew(): ResponseInterface + { + return new TestResponse(200, 'notes-new'); + } + + #[Get('/admin/notes/{id}/edit')] + public function notesEdit(RequestInterface $request): ResponseInterface + { + return new TestResponse(200, 'notes-edit:' . $request->param('id')); + } + + #[Post('/admin/notes')] + public function notesStore(): ResponseInterface + { + return new TestResponse(200, 'notes-store'); + } + + #[Post('/admin/notes/{id}')] + public function notesUpdate(RequestInterface $request): ResponseInterface + { + return new TestResponse(200, 'notes-update:' . $request->param('id')); + } + + #[Post('/admin/notes/{id}/delete')] + public function notesDelete(): ResponseInterface + { + return new TestResponse(200, 'notes-delete'); + } + + #[Post('/admin/notes/{id}/restore')] + public function notesRestore(): ResponseInterface + { + return new TestResponse(200, 'notes-restore'); + } + + #[Get('/admin/entries/new')] + public function entriesNew(): ResponseInterface + { + return new TestResponse(200, 'entries-new'); + } + + #[Post('/admin/entries')] + public function entriesStore(): ResponseInterface + { + return new TestResponse(200, 'entries-store'); + } + + #[Post('/admin/entries/preview')] + public function entriesPreview(): ResponseInterface + { + return new TestResponse(200, 'entries-preview-literal'); + } + + #[Get('/admin/entries/{id}/edit')] + public function entriesEdit(RequestInterface $request): ResponseInterface + { + return new TestResponse(200, 'entries-edit:' . $request->param('id')); + } + + #[Post('/admin/entries/{id}')] + public function entriesUpdate(): ResponseInterface + { + return new TestResponse(200, 'entries-update'); + } +} diff --git a/tests/Fixtures/RealisticPublicController.php b/tests/Fixtures/RealisticPublicController.php new file mode 100644 index 0000000..992b17b --- /dev/null +++ b/tests/Fixtures/RealisticPublicController.php @@ -0,0 +1,72 @@ +<?php + +declare(strict_types=1); + +namespace BrettParson\MiniRoute\Tests\Fixtures; + +use BrettParson\MiniRoute\Attribute\Get; +use BrettParson\MiniRoute\Http\RequestInterface; +use BrettParson\MiniRoute\Http\ResponseInterface; + +/** + * Public half of the brett-parson.com route model (9 routes). + * + * Mirrors the real public/index.php table so the integration suite pins + * the compatibility the stress test proved. + */ +final class RealisticPublicController +{ + #[Get('/')] + public function home(): ResponseInterface + { + return new TestResponse(200, 'home'); + } + + #[Get('/health')] + public function health(): ResponseInterface + { + return new TestResponse(200, 'health'); + } + + #[Get('/entries')] + public function entries(): ResponseInterface + { + return new TestResponse(200, 'entries'); + } + + #[Get('/entries/{slug}')] + public function entryShow(RequestInterface $request): ResponseInterface + { + return new TestResponse(200, 'entry-show:' . $request->param('slug')); + } + + #[Get('/feed.xml')] + public function feed(): ResponseInterface + { + return new TestResponse(200, 'feed'); + } + + #[Get('/robots.txt')] + public function robots(): ResponseInterface + { + return new TestResponse(200, 'robots'); + } + + #[Get('/sitemap.xml')] + public function sitemap(): ResponseInterface + { + return new TestResponse(200, 'sitemap'); + } + + #[Get('/notes')] + public function notes(): ResponseInterface + { + return new TestResponse(200, 'notes'); + } + + #[Get('/notes/{id}')] + public function noteShow(RequestInterface $request): ResponseInterface + { + return new TestResponse(200, 'note-show:' . $request->param('id')); + } +} diff --git a/tests/Fixtures/StatefulController.php b/tests/Fixtures/StatefulController.php new file mode 100644 index 0000000..bd09592 --- /dev/null +++ b/tests/Fixtures/StatefulController.php @@ -0,0 +1,23 @@ +<?php + +declare(strict_types=1); + +namespace BrettParson\MiniRoute\Tests\Fixtures; + +use BrettParson\MiniRoute\Attribute\Get; +use BrettParson\MiniRoute\Attribute\Middleware; +use BrettParson\MiniRoute\Http\ResponseInterface; + +/** + * Route carrying method-level middleware, used with a shared + * StatefulMiddleware instance to probe resolver-singleton behavior. + */ +final class StatefulController +{ + #[Get('/stateful')] + #[Middleware(StatefulMiddleware::class)] + public function run(): ResponseInterface + { + return new TestResponse(200, 'stateful'); + } +} diff --git a/tests/Fixtures/StatefulMiddleware.php b/tests/Fixtures/StatefulMiddleware.php new file mode 100644 index 0000000..d374238 --- /dev/null +++ b/tests/Fixtures/StatefulMiddleware.php @@ -0,0 +1,26 @@ +<?php + +declare(strict_types=1); + +namespace BrettParson\MiniRoute\Tests\Fixtures; + +use BrettParson\MiniRoute\Http\RequestInterface; +use BrettParson\MiniRoute\Http\ResponseInterface; +use BrettParson\MiniRoute\Middleware\MiddlewareInterface; + +/** + * Deliberately stateful middleware. Probes the singleton-container leak: + * when a resolver returns the same instance per request, state accumulates + * across dispatches (usage contract; ADR work in miniroute-k4i.3). + */ +final class StatefulMiddleware implements MiddlewareInterface +{ + public int $count = 0; + + public function handle(RequestInterface $request, callable $next): ResponseInterface + { + $this->count++; + + return $next($request); + } +} |
