aboutsummaryrefslogtreecommitdiff
path: root/tests/Integration/RealisticRouteTableTest.php
diff options
context:
space:
mode:
Diffstat (limited to 'tests/Integration/RealisticRouteTableTest.php')
-rw-r--r--tests/Integration/RealisticRouteTableTest.php153
1 files changed, 153 insertions, 0 deletions
diff --git a/tests/Integration/RealisticRouteTableTest.php b/tests/Integration/RealisticRouteTableTest.php
new file mode 100644
index 0000000..f93896c
--- /dev/null
+++ b/tests/Integration/RealisticRouteTableTest.php
@@ -0,0 +1,153 @@
+<?php
+
+declare(strict_types=1);
+
+namespace BrettParson\MiniRoute\Tests\Integration;
+
+use ArrayObject;
+use BrettParson\MiniRoute\Http\RequestInterface;
+use BrettParson\MiniRoute\Http\ResponseInterface;
+use BrettParson\MiniRoute\Middleware\MiddlewareInterface;
+use BrettParson\MiniRoute\Middleware\MiddlewarePipeline;
+use BrettParson\MiniRoute\Routing\RouteLoader;
+use BrettParson\MiniRoute\Routing\RouteNotFoundException;
+use BrettParson\MiniRoute\Routing\Router;
+use BrettParson\MiniRoute\Tests\Fixtures\AdminLoggingMiddleware;
+use BrettParson\MiniRoute\Tests\Fixtures\CsrfLoggingMiddleware;
+use BrettParson\MiniRoute\Tests\Fixtures\FakeResolver;
+use BrettParson\MiniRoute\Tests\Fixtures\RealisticAdminController;
+use BrettParson\MiniRoute\Tests\Fixtures\RealisticPublicController;
+use BrettParson\MiniRoute\Tests\Fixtures\TestRequest;
+use BrettParson\MiniRoute\Tests\Fixtures\TestResponse;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Compatibility suite: the realistic brett-parson.com route table.
+ *
+ * Ported from the tmp-miniroute stress test so the design requirements it
+ * proved (22-route table, /admin group + POST-only CSRF group, literal
+ * preview beating {id}, param injection) stay pinned as PHPUnit coverage.
+ * Bead: miniroute-4dh.1.
+ */
+final class RealisticRouteTableTest extends TestCase
+{
+ private FakeResolver $resolver;
+
+ private Router $router;
+
+ private ArrayObject $log;
+
+ protected function setUp(): void
+ {
+ $this->log = new ArrayObject();
+ $this->resolver = new FakeResolver();
+ $this->resolver->bind(RealisticPublicController::class, new RealisticPublicController());
+ $this->resolver->bind(RealisticAdminController::class, new RealisticAdminController());
+ $this->resolver->bind(AdminLoggingMiddleware::class, new AdminLoggingMiddleware($this->log));
+ $this->resolver->bind(CsrfLoggingMiddleware::class, new CsrfLoggingMiddleware($this->log));
+
+ $this->router = new Router($this->resolver);
+ $this->router->group('/admin', [AdminLoggingMiddleware::class]);
+ $this->router->group('/admin', [CsrfLoggingMiddleware::class], ['POST']);
+ $this->router->registerController(RealisticPublicController::class);
+ $this->router->registerController(RealisticAdminController::class);
+ }
+
+ public function testRouteTableCompilesToTwentyTwoRoutes(): void
+ {
+ $loaded = count((new RouteLoader())->load(RealisticPublicController::class))
+ + count((new RouteLoader())->load(RealisticAdminController::class));
+
+ $this->assertSame(22, $loaded);
+ }
+
+ public function testLiteralPreviewBeatsParameterizedId(): void
+ {
+ $this->assertSame(
+ 'entries-preview-literal',
+ $this->dispatch('POST', '/admin/entries/preview')->body(),
+ );
+ }
+
+ public function testInjectsSlugAndIdParameters(): void
+ {
+ $this->assertSame('entry-show:hello-world', $this->dispatch('GET', '/entries/hello-world')->body());
+ $this->assertSame('note-show:42', $this->dispatch('GET', '/notes/42')->body());
+ $this->assertSame('entries-edit:7', $this->dispatch('GET', '/admin/entries/7/edit')->body());
+ $this->assertSame('notes-edit:9', $this->dispatch('GET', '/admin/notes/9/edit')->body());
+ }
+
+ public function testLiteralSegmentsWithDotsMatch(): void
+ {
+ $this->assertSame('feed', $this->dispatch('GET', '/feed.xml')->body());
+ $this->assertSame('robots', $this->dispatch('GET', '/robots.txt')->body());
+ $this->assertSame('sitemap', $this->dispatch('GET', '/sitemap.xml')->body());
+ }
+
+ public function testAdminGroupThenCsrfGroupOrderOnPost(): void
+ {
+ $this->dispatch('POST', '/admin/entries/preview');
+
+ $this->assertSame(
+ ['admin:before', 'csrf:before', 'csrf:after', 'admin:after'],
+ $this->log->getArrayCopy(),
+ );
+ }
+
+ public function testCsrfGroupSkippedForGet(): void
+ {
+ $this->dispatch('GET', '/admin/notes');
+
+ $this->assertSame(['admin:before', 'admin:after'], $this->log->getArrayCopy());
+ }
+
+ public function testNotFoundStillThrows(): void
+ {
+ $this->expectException(RouteNotFoundException::class);
+
+ $this->dispatch('GET', '/missing');
+ }
+
+ public function testFiveLayerOnionOrder(): void
+ {
+ $order = new ArrayObject();
+ $mk = static function (string $name) use ($order): MiddlewareInterface {
+ return new class ($name, $order) implements MiddlewareInterface {
+ /** @param ArrayObject<int, string> $order */
+ public function __construct(
+ private readonly string $name,
+ private readonly ArrayObject $order,
+ ) {
+ }
+
+ public function handle(RequestInterface $request, callable $next): ResponseInterface
+ {
+ $this->order->append($this->name . ':before');
+ $response = $next($request);
+ $this->order->append($this->name . ':after');
+
+ return $response;
+ }
+ };
+ };
+
+ $core = static function (RequestInterface $request) use ($order): ResponseInterface {
+ $order->append('core');
+
+ return new TestResponse(200, 'ok');
+ };
+
+ $pipeline = MiddlewarePipeline::compose([$mk('A'), $mk('B'), $mk('C'), $mk('D'), $mk('E')], $core);
+ $pipeline(new TestRequest('GET', '/'));
+
+ $this->assertSame(
+ ['A:before', 'B:before', 'C:before', 'D:before', 'E:before', 'core', 'E:after', 'D:after', 'C:after', 'B:after', 'A:after'],
+ $order->getArrayCopy(),
+ );
+ }
+
+ private function dispatch(string $method, string $path): ResponseInterface
+ {
+ return $this->router->dispatch(new TestRequest($method, $path));
+ }
+}