Issues (120)

tests/CompilerTest.php (6 issues)

Labels
Severity
1
<?php declare(strict_types=1);
2
3
/*
4
 * This file is part of Flight Routing.
5
 *
6
 * PHP version 8.0 and above required
7
 *
8
 * @author    Divine Niiquaye Ibok <[email protected]>
9
 * @copyright 2019 Divine Niiquaye Ibok (https://divinenii.com/)
10
 * @license   https://opensource.org/licenses/BSD-3-Clause License
11
 *
12
 * For the full copyright and license information, please view the LICENSE
13
 * file that was distributed with this source code.
14
 */
15
16
use Flight\Routing\Exceptions\{UriHandlerException, UrlGenerationException};
17
use Flight\Routing\RouteUri as GeneratedUri;
18
use PHPUnit\Framework as t;
19
20
dataset('patterns', [
21
    [
22
        '/foo',
23
        '{^/foo$}',
24
        [],
25
        ['/foo'],
26
    ],
27
    [
28
        '/foo/',
29
        '{^/foo/?$}',
30
        [],
31
        ['/foo', '/foo/'],
32
    ],
33
    [
34
        '/foo/{bar}',
35
        '{^/foo/(?P<bar>[^\/]+)$}',
36
        ['bar' => null],
37
        ['/foo/bar', '/foo/baz'],
38
    ],
39
    [
40
        '/foo/{bar}@',
41
        '{^/foo/(?P<bar>[^\/]+)@$}',
42
        ['bar' => null],
43
        ['/foo/bar@', '/foo/baz@'],
44
    ],
45
    [
46
        '/foo-{bar}',
47
        '{^/foo-(?P<bar>[^\/]+)$}',
48
        ['bar' => null],
49
        ['/foo-bar', '/foo-baz'],
50
    ],
51
    [
52
        '/foo/{bar}/{baz}/',
53
        '{^/foo/(?P<bar>[^\/]+)/(?P<baz>[^\/]+)/?$}',
54
        ['bar' => null, 'baz' => null],
55
        ['/foo/bar/baz', '/foo/bar/baz/'],
56
    ],
57
    [
58
        '/foo/{bar:\d+}',
59
        '{^/foo/(?P<bar>\d+)$}',
60
        ['bar' => null],
61
        ['/foo/123', '/foo/444'],
62
    ],
63
    [
64
        '/foo/{bar:\d+}/{baz}/',
65
        '{^/foo/(?P<bar>\d+)/(?P<baz>[^\/]+)/?$}',
66
        ['bar' => null, 'baz' => null],
67
        ['/foo/123/baz', '/foo/123/baz/'],
68
    ],
69
    [
70
        '/foo/{bar:\d+}/{baz:slug}',
71
        '{^/foo/(?P<bar>\d+)/(?P<baz>[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)$}',
72
        ['bar' => null, 'baz' => null],
73
        ['/foo/123/foo', '/foo/44/baz'],
74
    ],
75
    [
76
        '/foo/{bar=0}',
77
        '{^/foo/(?P<bar>[^\/]+)$}',
78
        ['bar' => '0'],
79
        ['/foo/0'],
80
    ],
81
    [
82
        '/foo/{bar=baz}/{baz}/',
83
        '{^/foo/(?P<bar>[^\/]+)/(?P<baz>[^\/]+)/?$}',
84
        ['bar' => 'baz', 'baz' => null],
85
        ['/foo/baz/baz', '/foo/baz/baz/'],
86
    ],
87
    [
88
        '/[{foo}]',
89
        '{^/?(?:(?P<foo>[^\/]+))?$}',
90
        ['foo' => null],
91
        ['/', '/foo', '/bar'],
92
    ],
93
    [
94
        '/[{bar:(foo|bar)}]',
95
        '{^/?(?:(?P<bar>(foo|bar)))?$}',
96
        ['bar' => null],
97
        ['/', '/foo', '/bar'],
98
    ],
99
    [
100
        '/foo[/{bar}]/',
101
        '{^/foo(?:/(?P<bar>[^\/]+))?/?$}',
102
        ['bar' => null],
103
        ['/foo', '/foo/', '/foo/bar', '/foo/bar/'],
104
    ],
105
    [
106
        '/[{foo:upper}]/[{bar:lower}]',
107
        '{^/?(?:(?P<foo>[A-Z]+))?/?(?:(?P<bar>[a-z]+))?$}',
108
        ['foo' => null, 'bar' => null],
109
        ['/', '/FOO', '/FOO/', '/FOO/bar', '/bar'],
110
    ],
111
    [
112
        '/[{foo}][/{bar:month}]',
113
        '{^/?(?:(?P<foo>[^\/]+))?(?:/(?P<bar>0[1-9]|1[012]+))?$}',
114
        ['foo' => null, 'bar' => null],
115
        ['/', '/foo', '/bar', '/foo/12', '/foo/01'],
116
    ],
117
    [
118
        '/[{foo:lower}/[{bar:upper}]]',
119
        '{^/?(?:(?P<foo>[a-z]+)/?(?:(?P<bar>[A-Z]+))?)?$}',
120
        ['foo' => null, 'bar' => null],
121
        ['/', '/foo', '/foo/', '/foo/BAR', '/foo/BAZ'],
122
    ],
123
    [
124
        '/[{foo}/{bar}]',
125
        '{^/?(?:(?P<foo>[^\/]+)/(?P<bar>[^\/]+))?$}',
126
        ['foo' => null, 'bar' => null],
127
        ['/', '/foo/bar', '/foo/baz'],
128
    ],
129
    [
130
        '/who{are}you',
131
        '{^/who(?P<are>[^\/]+)you$}',
132
        ['are' => null],
133
        ['/whoareyou', '/whoisyou'],
134
    ],
135
    [
136
        '/[{lang:[a-z]{2}}/]hello',
137
        '{^/?(?:(?P<lang>[a-z]{2})/)?hello$}',
138
        ['lang' => null],
139
        ['/hello', '/en/hello', '/fr/hello'],
140
    ],
141
    [
142
        '/[{lang:[\w+\-]+=english}/]hello',
143
        '{^/?(?:(?P<lang>[\w+\-]+)/)?hello$}',
144
        ['lang' => 'english'],
145
        ['/hello', '/en/hello', '/fr/hello'],
146
    ],
147
    [
148
        '/[{lang:[a-z]{2}}[-{sublang}]/]{name}[/page-{page=0}]',
149
        '{^/?(?:(?P<lang>[a-z]{2})(?:-(?P<sublang>[^\/]+))?/)?(?P<name>[^\/]+)(?:/page-(?P<page>[^\/]+))?$}',
150
        ['lang' => null, 'sublang' => null, 'name' => null, 'page' => '0'],
151
        ['/hello', '/en/hello', '/en-us/hello', '/en-us/hello/page-1', '/en-us/hello/page-2'],
152
    ],
153
    [
154
        '/hello/{foo:[a-z]{3}=bar}{baz}/[{id:[0-9a-fA-F]{1,8}}[.{format:html|php}]]',
155
        '{^/hello/(?P<foo>[a-z]{3})(?P<baz>[^\/]+)/?(?:(?P<id>[0-9a-fA-F]{1,8})(?:\.(?P<format>html|php))?)?$}',
156
        ['foo' => 'bar', 'baz' => null, 'id' => null, 'format' => null],
157
        ['/hello/barbaz', '/hello/barbaz/', '/hello/barbaz/1', '/hello/barbaz/1.html', '/hello/barbaz/1.php'],
158
    ],
159
    [
160
        '/hello/{foo:\w{3}}{bar=bar1}/world/[{name:[A-Za-z]+}[/{page:int=1}[/{baz:year}]]]/abs.{format:html|php}',
161
        '{^/hello/(?P<foo>\w{3})(?P<bar>[^\/]+)/world/?(?:(?P<name>[A-Za-z]+)(?:/(?P<page>[0-9]+)(?:/(?P<baz>[0-9]{4}))?)?)?/abs\.(?P<format>html|php)$}',
162
        ['foo' => null, 'bar' => 'bar1', 'name' => null, 'page' => '1', 'baz' => null, 'format' => null],
163
        [
164
            '/hello/foobar/world/abs.html',
165
            '/hello/barfoo/world/divine/abs.php',
166
            '/hello/foobaz/world/abs.php',
167
            '/hello/bar100/world/divine/11/abs.html',
168
            '/hello/true/world/divine/11/2022/abs.html',
169
        ],
170
    ],
171
    [
172
        '{foo}.example.com',
173
        '{^(?P<foo>[^\/]+)\.example\.com$}',
174
        ['foo' => null],
175
        ['foo.example.com', 'bar.example.com'],
176
    ],
177
    [
178
        '{locale}.example.{tld}',
179
        '{^(?P<locale>[^\/]+)\.example\.(?P<tld>[^\/]+)$}',
180
        ['locale' => null, 'tld' => null],
181
        ['en.example.com', 'en.example.org', 'en.example.co.uk'],
182
    ],
183
    [
184
        '[{lang:[a-z]{2}}.]example.com',
185
        '{^(?:(?P<lang>[a-z]{2})\.)?example\.com$}',
186
        ['lang' => null],
187
        ['en.example.com', 'example.com', 'fr.example.com'],
188
    ],
189
    [
190
        '[{lang:[a-z]{2}}.]example.{tld=com}',
191
        '{^(?:(?P<lang>[a-z]{2})\.)?example\.(?P<tld>[^\/]+)$}',
192
        ['lang' => null, 'tld' => 'com'],
193
        ['en.example.com', 'example.com', 'fr.example.gh'],
194
    ],
195
    [
196
        '{id:int}.example.com',
197
        '{^(?P<id>[0-9]+)\.example\.com$}',
198
        ['id' => null],
199
        ['1.example.com', '2.example.com'],
200
    ],
201
]);
202
203
dataset('reversed', [
204
    [
205
        '/foo',
206
        ['/foo' => []],
207
    ],
208
    [
209
        '/foo/{bar}',
210
        ['/foo/bar' => ['bar' => 'bar']],
211
    ],
212
    [
213
        '/foo/{bar}/{baz}',
214
        ['/foo/bar/baz' => ['bar' => 'bar', 1 => 'baz']],
215
    ],
216
    [
217
        '/divine/{id:\d+}[/{a}{b}[{c}]][.p[{d}]]',
218
        [
219
            '/divine/23' => ['id' => '23'],
220
            '/divine/23/ab' => ['23', 'a' => 'a', 'b' => 'b'],
221
            '/divine/23/abc' => ['23', 'a' => 'a', 'b' => 'b', 'c' => 'c'],
222
            '/divine/23/abc.php' => ['23', 'a' => 'a', 'b' => 'b', 'c' => 'c', 'd' => 'hp'],
223
            '/divine/23.phtml' => ['id' => '23', 'd' => 'html'],
224
        ],
225
    ],
226
]);
227
228
test('if route path is a valid regex', function (string $path, string $regex, array $vars, array $matches): void {
229
    $compiler = new \Flight\Routing\RouteCompiler();
230
    [$pathRegex, $pathVar] = $compiler->compile($path);
231
232
    t\assertEquals($regex, $pathRegex);
0 ignored issues
show
The function assertEquals was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

232
    /** @scrutinizer ignore-call */ 
233
    t\assertEquals($regex, $pathRegex);
Loading history...
233
    t\assertSame($vars, $pathVar);
0 ignored issues
show
The function assertSame was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

233
    /** @scrutinizer ignore-call */ 
234
    t\assertSame($vars, $pathVar);
Loading history...
234
235
    // Match every pattern...
236
    foreach ($matches as $match) {
237
        t\assertMatchesRegularExpression($pathRegex, $match);
0 ignored issues
show
The function assertMatchesRegularExpression was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

237
        /** @scrutinizer ignore-call */ 
238
        t\assertMatchesRegularExpression($pathRegex, $match);
Loading history...
238
    }
239
})->with('patterns');
240
241
test('if compiled route path is reversible', function (string $path, array $matches): void {
242
    $compiler = new \Flight\Routing\RouteCompiler();
243
244
    foreach ($matches as $match => $params) {
245
        t\assertEquals($match, (string) $compiler->generateUri(['path' => $path], $params));
0 ignored issues
show
The function assertEquals was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

245
        /** @scrutinizer ignore-call */ 
246
        t\assertEquals($match, (string) $compiler->generateUri(['path' => $path], $params));
Loading history...
246
    }
247
})->with('reversed');
248
249
test('if route path placeholder is characters length is invalid', function (): void {
250
    $compiler = new \Flight\Routing\RouteCompiler();
251
    $compiler->compile('/{sfkdfglrjfdgrfhgklfhgjhfdjghrtnhrnktgrelkrngldrjhglhkjdfhgkj}');
252
})->throws(
253
    UriHandlerException::class,
254
    \sprintf(
255
        'Variable name "%s" cannot be longer than 32 characters in route pattern "/{%1$s}".',
256
        'sfkdfglrjfdgrfhgklfhgjhfdjghrtnhrnktgrelkrngldrjhglhkjdfhgkj'
257
    )
258
);
259
260
test('if route path placeholder begins with a number', function (): void {
261
    $compiler = new \Flight\Routing\RouteCompiler();
262
    $compiler->compile('/{1foo}');
263
})->throws(
264
    UriHandlerException::class,
265
    'Variable name "1foo" cannot start with a digit in route pattern "/{1foo}". Use a different name.'
266
);
267
268
test('if route path placeholder is used more than once', function (): void {
269
    $compiler = new \Flight\Routing\RouteCompiler();
270
    $compiler->compile('/{foo}/{foo}');
271
})->throws(
272
    UriHandlerException::class,
273
    'Route pattern "/{foo}/{foo}" cannot reference variable name "foo" more than once.'
274
);
275
276
test('if route path placeholder has regex values', function (string $path, array $segment): void {
277
    $compiler = new \Flight\Routing\RouteCompiler();
278
    [$pathRegex] = $compiler->compile($path, $segment);
279
    t\assertMatchesRegularExpression($pathRegex, '/a');
0 ignored issues
show
The function assertMatchesRegularExpression was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

279
    /** @scrutinizer ignore-call */ 
280
    t\assertMatchesRegularExpression($pathRegex, '/a');
Loading history...
280
    t\assertMatchesRegularExpression($pathRegex, '/b');
281
})->with([
282
    ['/{foo}', ['foo' => ['a', 'b']]],
283
    ['/{foo}', ['foo' => 'a|b']],
284
]);
285
286
test('if route path placeholder requirement is empty', function (string $assert): void {
287
    $compiler = new \Flight\Routing\RouteCompiler();
288
    $compiler->compile('/{foo}', ['foo' => $assert]);
289
})->with(['', '^$', '^', '$', '\A\z', '\A', '\z'])->throws(
290
    UriHandlerException::class,
291
    'Routing requirement for "foo" cannot be empty.'
292
);
293
294
test('if reversed generated route path can contain http scheme and host', function (): void {
295
    $compiler = new \Flight\Routing\RouteCompiler();
296
    $_SERVER['HTTP_HOST'] = 'example.com';
297
    $route = ['path' => '/{foo}', 'schemes' => ['http' => true]];
298
299
    t\assertEquals('/a', (string) $compiler->generateUri($route, ['foo' => 'a']));
0 ignored issues
show
The function assertEquals was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

299
    /** @scrutinizer ignore-call */ 
300
    t\assertEquals('/a', (string) $compiler->generateUri($route, ['foo' => 'a']));
Loading history...
300
    t\assertEquals('./b', (string) $compiler->generateUri($route, ['foo' => 'b'], GeneratedUri::RELATIVE_PATH));
301
    t\assertEquals('//example.com/c', (string) $compiler->generateUri($route, ['c'], GeneratedUri::NETWORK_PATH));
302
    t\assertEquals('http://example.com/d', (string) $compiler->generateUri($route, [0 => 'd'], GeneratedUri::ABSOLUTE_URL));
303
    t\assertEquals('http://localhost/e', (string) $compiler->generateUri(
304
        $route += ['hosts' => ['localhost' => true]],
305
        ['foo' => 'e'],
306
        GeneratedUri::ABSOLUTE_URL
307
    ));
308
});
309
310
test('if reversed generated route fails to certain placeholders', function (): void {
311
    $compiler = new \Flight\Routing\RouteCompiler();
312
    $compiler->generateUri(['path' => '/{foo:int}'], ['foo' => 'a']);
313
})->throws(
314
    UriHandlerException::class,
315
    'Expected route path "/<foo>" placeholder "foo" value "a" to match "[0-9]+".'
316
);
317
318
test('if reversed generate route is missing required placeholders', function (): void {
319
    $compiler = new \Flight\Routing\RouteCompiler();
320
    $compiler->generateUri(['path' => '/{foo}'], []);
321
})->throws(
322
    UrlGenerationException::class,
323
    'Some mandatory parameters are missing ("foo") to generate a URL for route path "/<foo>".'
324
);
325
326
test('if reversed generate route can contain a negative port port', function (): void {
327
    $compiler = new \Flight\Routing\RouteCompiler();
328
    $compiler->generateUri(['path' => '/{foo}'], ['flight-routing'])->withPort(-9);
329
})->throws(UrlGenerationException::class, 'Invalid port: -9. Must be between 0 and 65535');
330