aboutsummaryrefslogtreecommitdiff
path: root/tests/Regression/KnownIssuesTest.php
blob: 5a6c6f79c24a64ec594c38a11b45ab2d19227e98 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
<?php

declare(strict_types=1);

namespace BrettParson\MiniRoute\Tests\Regression;

use BrettParson\MiniRoute\Routing\RouteLoader;
use BrettParson\MiniRoute\Routing\RouteNotFoundException;
use BrettParson\MiniRoute\Routing\Router;
use BrettParson\MiniRoute\Tests\Fixtures\DuplicateParamController;
use BrettParson\MiniRoute\Tests\Fixtures\FakeController;
use BrettParson\MiniRoute\Tests\Fixtures\FakeResolver;
use BrettParson\MiniRoute\Tests\Fixtures\MixedVisibilityController;
use BrettParson\MiniRoute\Tests\Fixtures\OnlyGetController;
use BrettParson\MiniRoute\Tests\Fixtures\StatefulController;
use BrettParson\MiniRoute\Tests\Fixtures\StatefulMiddleware;
use BrettParson\MiniRoute\Tests\Fixtures\TestRequest;
use PHPUnit\Framework\TestCase;

/**
 * Known-issue baselines: pin v0.1.0 behavior for every confirmed stress
 * finding so the suite is green today and each case flips to the desired
 * behavior when its v0.2.0 fix lands.
 *
 * Bead: miniroute-4dh.2. Fixes tracked in the v0.2.0 epic (miniroute-uhs).
 */
final class KnownIssuesTest extends TestCase
{
    private function routerWith(object $controller, string $class): Router
    {
        $resolver = new FakeResolver();
        $resolver->bind($class, $controller);

        return new Router($resolver);
    }

    /**
     * Baseline: duplicated parameter names produce a route that can never
     * match, and emit a PHP warning on every match attempt.
     *
     * Desired (miniroute-uhs.1): registration throws
     * RouteRegistrationException. Flip this test when the fix lands.
     */
    public function testDuplicateParamNamesAreADeadRoute(): void
    {
        $router = $this->routerWith(new DuplicateParamController(), DuplicateParamController::class);
        $router->registerController(DuplicateParamController::class);

        $warnings = [];
        set_error_handler(static function (int $errno, string $errstr) use (&$warnings): bool {
            $warnings[] = $errstr;

            return true;
        });

        try {
            $router->dispatch(new TestRequest('GET', '/dup/1/x/2'));
            $matched = true;
        } catch (RouteNotFoundException) {
            $matched = false;
        } finally {
            restore_error_handler();
        }

        $this->assertFalse($matched, 'duplicate-param route must not match');
        $this->assertNotEmpty($warnings, 'matching a duplicate-param route emits a preg warning');
    }

    /**
     * Baseline: a wrong method on an existing path is a 404, with no way to
     * distinguish method-not-allowed.
     *
     * Desired (miniroute-uhs.2): MethodNotAllowedException carrying the
     * allowed methods, so the app can render 405 + Allow. Flip this test.
     */
    public function testWrongMethodOnExistingPathIsNotFound(): void
    {
        $router = $this->routerWith(new OnlyGetController(), OnlyGetController::class);
        $router->registerController(OnlyGetController::class);

        $this->expectException(RouteNotFoundException::class);

        $router->dispatch(new TestRequest('POST', '/only'));
    }

    /**
     * Baseline: HEAD requests never map to GET handlers.
     *
     * Desired (miniroute-uhs.3): HEAD dispatches to the GET handler and the
     * app's response layer suppresses the body. Flip this test.
     */
    public function testHeadDoesNotMapToGet(): void
    {
        $router = $this->routerWith(new OnlyGetController(), OnlyGetController::class);
        $router->registerController(OnlyGetController::class);

        $this->expectException(RouteNotFoundException::class);

        $router->dispatch(new TestRequest('HEAD', '/only'));
    }

    /**
     * Baseline: route attributes on non-public methods are silently skipped
     * by the loader.
     *
     * Desired (miniroute-uhs.4): loading a controller with a route attribute
     * on a non-public method throws RouteRegistrationException. Flip this test.
     */
    public function testNonPublicMethodsWithRouteAttributesAreSkipped(): void
    {
        $routes = (new RouteLoader())->load(MixedVisibilityController::class);

        $this->assertCount(1, $routes);
        $this->assertSame('/pub', $routes[0]->path);
    }

    /**
     * Baseline: a resolver returning the same middleware instance across
     * requests accumulates state (singleton-container leak). This is a usage
     * contract, not a kernel bug: middleware must be stateless or
     * per-request scoped. ADR work tracked in miniroute-k4i.3.
     */
    public function testSingletonMiddlewareAccumulatesStateAcrossRequests(): void
    {
        $stateful = new StatefulMiddleware();

        $resolver = new FakeResolver();
        $resolver->bind(StatefulController::class, new StatefulController());
        $resolver->bind(StatefulMiddleware::class, $stateful);

        $router = new Router($resolver);
        $router->registerController(StatefulController::class);

        $router->dispatch(new TestRequest('GET', '/stateful'));
        $router->dispatch(new TestRequest('GET', '/stateful'));

        $this->assertSame(2, $stateful->count, 'state leaks across dispatches with a shared instance');
    }

    /**
     * Baseline: {param} segments are greedy — any single segment matches, so
     * a typo'd URL gets a 200 from the parameterized route, not a 404.
     *
     * Documented behavior, no kernel fix planned: controllers validate the
     * captured value (the blog controller 404s on an unknown slug). Decision
     * recorded in miniroute-k4i.3 (param constraints rejected).
     */
    public function testParamRoutesAreGreedy(): void
    {
        $router = $this->routerWith(new FakeController(), FakeController::class);
        $router->registerController(FakeController::class);

        $response = $router->dispatch(new TestRequest('GET', '/entries/typo-of-known-path'));

        $this->assertSame(200, $response->status());
        $this->assertSame('show:typo-of-known-path', $response->body());
    }
}