Issues (101)

spec/Suite/Route.spec.php (5 issues)

1
<?php
2
declare(strict_types=1);
3
4
namespace Lead\Router\Spec\Suite;
5
6
use Lead\Router\Exception\RouteNotFoundException;
7
use Lead\Router\Scope;
8
use stdClass;
9
use Lead\Router\Exception\RouterException;
10
use Lead\Router\Router;
11
use Lead\Router\Route;
12
13
describe("Route", function() {
14
15
    describe("->pattern()", function() {
16
17
        it("gets/sets the pattern", function() {
18
19
            $route = new Route();
20
            expect($route->setPattern('/foo/bar/{id}[/{paths}]*'))->toBe($route);
21
            expect($route->getPattern())->toBe('/foo/bar/{id}[/{paths}]*');
22
            expect($route->getRegex())->toBe('/foo/bar/([^/]+)((?:/[^/]+)*)');
23
            expect($route->getVariables())->toBe([
24
                'id'    => false,
25
                'paths' => '/{paths}'
26
            ]);
27
28
        });
29
30
        it("updates the regex", function() {
31
32
            $route = new Route();
33
            expect($route->setPattern('/foo/bar/{id}[/{paths}]*'))->toBe($route);
34
            expect($route->getRegex())->toBe('/foo/bar/([^/]+)((?:/[^/]+)*)');
35
36
            expect($route->setPattern('/foo/baz/{id}[/{paths}]*'))->toBe($route);
37
            expect($route->getRegex())->toBe('/foo/baz/([^/]+)((?:/[^/]+)*)');
38
39
        });
40
41
        it("updates the variables", function() {
42
43
            $route = new Route();
44
            expect($route->setPattern('/foo/bar/{id}[/{paths}]*'))->toBe($route);
45
            expect($route->getVariables())->toBe([
46
                'id'    => false,
47
                'paths' => '/{paths}'
48
            ]);
49
50
            expect($route->setPattern('/foo/bar/{baz}[/{paths}]'))->toBe($route);
51
            expect($route->getVariables())->toBe([
52
                'baz'   => false,
53
                'paths' => false
54
            ]);
55
56
        });
57
58
    });
59
60
    describe("->scope()", function() {
61
62
        it("gets/sets route scope", function() {
63
64
            $scope = new Scope();
65
            $route = new Route();
66
            expect($route->setScope($scope))->toBe($route);
67
            expect($route->getScope())->toBe($scope);
68
69
        });
70
71
    });
72
73
    describe("->methods()", function() {
74
75
        it("gets/sets route methods", function() {
76
77
            $route = new Route();
78
            expect($route->setMethods(['POST', 'PUT']))->toBe($route);
79
            expect($route->getMethods())->toBe(['POST', 'PUT']);
80
81
        });
82
83
        it("formats method names", function() {
84
85
            $route = new Route();
86
            expect($route->setMethods(['post', 'put']))->toBe($route);
87
            expect($route->getMethods())->toBe(['POST', 'PUT']);
88
89
        });
90
91
    });
92
93
    describe("->allow()", function() {
94
95
        it("adds some extra allowed methods", function() {
96
97
            $route = new Route(['methods' => []]);
98
            expect($route->allow(['POST', 'PUT']))->toBe($route);
99
            expect($route->getMethods())->toBe(['POST', 'PUT']);
100
101
        });
102
103
        it("formats newly allowed method names", function() {
104
105
            $route = new Route(['methods' => []]);
106
            expect($route->allow(['post', 'put']))->toBe($route);
107
            expect($route->getMethods())->toBe(['POST', 'PUT']);
108
109
        });
110
111
    });
112
113
    describe("->apply()", function() {
114
115
        it("applies middlewares", function() {
116
117
            $r = new Router();
118
            $route = $r->bind('foo/bar', function($route) {
0 ignored issues
show
The parameter $route is not used and could be removed. ( Ignorable by Annotation )

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

118
            $route = $r->bind('foo/bar', function(/** @scrutinizer ignore-unused */ $route) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The assignment to $route is dead and can be removed.
Loading history...
119
                return 'A';
120
            })->apply(function($request, $response, $next) {
0 ignored issues
show
The method apply() does not exist on Lead\Router\RouteInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Lead\Router\RouteInterface. ( Ignorable by Annotation )

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

120
            })->/** @scrutinizer ignore-call */ apply(function($request, $response, $next) {
Loading history...
121
                return '1' . $next() . '1';
122
            })->apply(function($request, $response, $next) {
123
                return '2' . $next() . '2';
124
            });
125
126
            $route = $r->route('foo/bar');
127
            $actual = $route->dispatch();
0 ignored issues
show
The method dispatch() does not exist on Lead\Router\RouteInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Lead\Router\RouteInterface. ( Ignorable by Annotation )

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

127
            /** @scrutinizer ignore-call */ 
128
            $actual = $route->dispatch();
Loading history...
128
129
            expect($actual)->toBe('21A12');
130
131
        });
132
133
    });
134
135
    describe("->link()", function() {
136
137
        it("creates relative links", function() {
138
139
            $route = new Route(['pattern' => '/foo/{bar}']);
140
141
            $link = $route->link(['bar' => 'baz']);
142
            expect($link)->toBe('/foo/baz');
143
144
        });
145
146
        it("supports optionnal parameters", function() {
147
148
            $route = new Route(['pattern' => '/foo[/{bar}]']);
149
150
            $link = $route->link();
151
            expect($link)->toBe('/foo');
152
153
            $link = $route->link(['bar' => 'baz']);
154
            expect($link)->toBe('/foo/baz');
155
156
        });
157
158
        it("supports multiple optionnal parameters", function() {
159
160
            $route = new Route(['pattern' => '/file[/{paths}]*']);
161
162
            $link = $route->link();
163
            expect($link)->toBe('/file');
164
165
            $link = $route->link(['paths' => ['some', 'file', 'path']]);
166
            expect($link)->toBe('/file/some/file/path');
167
168
        });
169
170
        it("merges default params", function() {
171
172
            $route = new Route(['pattern' => '/foo/{bar}', 'params' => ['bar' => 'baz']]);
173
174
            $link = $route->link();
175
            expect($link)->toBe('/foo/baz');
176
177
        });
178
179
        it("creates absolute links with custom base path", function() {
180
181
            $route = new Route([
182
                'pattern' => 'foo/{bar}',
183
                'host' => 'www.{domain}.com',
184
                'scheme' => 'https'
185
            ]);
186
187
            $link = $route->link([
188
                'bar'    => 'baz',
189
                'domain' => 'example'
190
            ], [
191
                'basePath' => 'app',
192
                'absolute' => true
193
            ]);
194
            expect($link)->toBe('https://www.example.com/app/foo/baz');
195
196
        });
197
198
        it("allows host and scheme overriding", function() {
199
200
            $route = new Route([
201
                'pattern' => 'foo/{bar}',
202
                'host' => 'www.{domain}.com',
203
                'scheme' => 'https'
204
            ]);
205
206
            $link =$route->link([
207
                'bar'    => 'baz',
208
                'domain' => 'example'
209
            ], [
210
                'scheme'   => 'http',
211
                'host'     => 'www.overrided.com',
212
                'basePath' => 'app',
213
                'absolute' => true
214
            ]);
215
            expect($link)->toBe('http://www.overrided.com/app/foo/baz');
216
217
        });
218
219
        it("supports repeatable parameter placeholders as an array", function() {
220
221
            $route = new Route(['pattern' => 'post[/{id}]*']);
222
            expect($route->link(['id' => '123']))->toBe('/post/123');
223
            expect($route->link(['id' => ['123']]))->toBe('/post/123');
224
            expect($route->link(['id' => ['123', '456', '789']]))->toBe('/post/123/456/789');
225
            expect($route->link([]))->toBe('/post');
226
227
            $route = new Route(['pattern' => '/post[/{id}]+']);
228
            expect($route->link(['id' => ['123']]))->toBe('/post/123');
229
            expect($route->link(['id' => ['123', '456', '789']]))->toBe('/post/123/456/789');
230
231
        });
232
233
        it("supports route with multiple optional segments", function() {
234
235
            $route = new Route(['pattern' => '[{relation}/{rid:[^/:][^/]*}/]post[/{id:[^/:][^/]*}][/:{action}]']);
236
237
            $link = $route->link();
238
            expect($link)->toBe('/post');
239
240
            $link = $route->link(['action' => 'add']);
241
            expect($link)->toBe('/post/:add');
242
243
            $link = $route->link(['action' => 'edit', 'id' => 12]);
244
            expect($link)->toBe('/post/12/:edit');
245
246
            $link = $route->link(['relation' => 'user', 'rid' => 5]);
247
            expect($link)->toBe('/user/5/post');
248
249
            $link = $route->link(['relation' => 'user', 'rid' => 5, 'action' => 'edit', 'id' => 12]);
250
            expect($link)->toBe('/user/5/post/12/:edit');
251
252
        });
253
254
        it("supports route with complex repeatable optional segments", function() {
255
256
            $route = new Route(['pattern' => '[{relations:[^/]+/[^/:][^/]*}/]*post[/{id:[^/:][^/]*}][/:{action}]']);
257
258
            $link = $route->link();
259
            expect($link)->toBe('/post');
260
261
            $link = $route->link(['action' => 'add']);
262
            expect($link)->toBe('/post/:add');
263
264
            $link = $route->link(['action' => 'edit', 'id' => 12]);
265
            expect($link)->toBe('/post/12/:edit');
266
267
            $link = $route->link(['relations' => [['user', 5]]]);
268
            expect($link)->toBe('/user/5/post');
269
270
            $link = $route->link(['relations' => [['user', 5]], 'action' => 'edit', 'id' => 12]);
271
            expect($link)->toBe('/user/5/post/12/:edit');
272
273
        });
274
275
        it("throws an exception for missing variables", function() {
276
277
            $closure = function() {
278
                $route = new Route(['pattern' => 'post[/{id}]+']);
279
                echo $route->link([]);
280
            };
281
            expect($closure)->toThrow(new RouterException("Missing parameters `'id'` for route: `'#/post[/{id}]+'`."));
282
283
        });
284
285
        it("throws an exception when a variable doesn't match its capture pattern", function() {
286
287
            $closure = function() {
288
                $route = new Route(['pattern' => 'post/{id:[0-9]{3}}']);
289
                $route->link(['id' => '1234']);
290
            };
291
            expect($closure)->toThrow(new RouterException("Expected `'id'` to match `'[0-9]{3}'`, but received `'1234'`."));
292
293
        });
294
295
        it("throws an exception when a an array is provided for a non repeatable parameter placeholder", function() {
296
297
            $closure = function() {
298
                $route = new Route(['pattern' => 'post/{id}']);
299
                $route->link(['id' => ['123', '456']]);
300
            };
301
            expect($closure)->toThrow(new RouterException("Expected `'id'` to match `'[^/]+'`, but received `'123/456'`."));
302
303
        });
304
305
        it("throws an exception when one element of an array doesn't match the capture pattern", function() {
306
307
            $closure = function() {
308
                $route = new Route(['pattern' => 'post[/{id:[0-9]{3}}]+']);
309
                $route->link(['id' => ['123', '456', '78']]);
310
            };
311
            expect($closure)->toThrow(new RouterException("Expected `'id'` to match `'[0-9]{3}'`, but received `'78'`."));
312
313
        });
314
315
    });
316
317
    describe("->dispatch()", function() {
318
319
        it("passes route as argument of the handler function", function() {
320
321
            $r = new Router();
322
            $r->get('foo/{var1}[/{var2}]',
0 ignored issues
show
The method get() does not exist on Lead\Router\Router. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

322
            $r->/** @scrutinizer ignore-call */ 
323
                get('foo/{var1}[/{var2}]',
Loading history...
323
                ['host' => '{subdomain}.{domain}.bar'],
324
                function($route, $response) {
325
                    return array_merge([$response], array_values($route->params));
326
                }
327
            );
328
329
            $response = new stdClass();
330
331
            $route = $r->route('foo/25', 'GET', 'foo.biz.bar');
332
            $actual = $route->dispatch($response);
333
            expect($actual)->toBe([$response, 'foo', 'biz', '25', null]);
334
335
            $route = $r->route('foo/25/bar', 'GET', 'foo.biz.bar');
336
            $actual = $route->dispatch($response);
337
            expect($actual)->toBe([$response, 'foo', 'biz', '25', 'bar']);
338
339
        });
340
341
        it("throws an exception on non valid routes", function() {
342
343
            $closure = function() {
344
                $r = new Router();
345
                $r->get('foo', function() {});
346
                $route = $r->route('bar');
347
                $route->dispatch();
348
            };
349
350
            expect($closure)->toThrow(new RouteNotFoundException("No route found for `*:*:GET:/bar`.", 404));
351
        });
352
353
    });
354
355
});
356