Passed
Pull Request — master (#34)
by Anatoly
02:04
created

RouterTest::testAddRoutesFromSeveralRouteCollections()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 15
nc 1
nop 0
dl 0
loc 20
rs 9.7666
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Sunrise\Http\Router\Tests;
4
5
/**
6
 * Import classes
7
 */
8
use PHPUnit\Framework\TestCase;
9
use Psr\Http\Server\MiddlewareInterface;
10
use Psr\Http\Server\RequestHandlerInterface;
11
use Sunrise\Http\Router\Exception\MethodNotAllowedException;
12
use Sunrise\Http\Router\Exception\MiddlewareAlreadyExistsException;
13
use Sunrise\Http\Router\Exception\RouteAlreadyExistsException;
14
use Sunrise\Http\Router\Exception\RouteNotFoundException;
15
use Sunrise\Http\Router\Loader\LoaderInterface;
16
use Sunrise\Http\Router\RouteCollection;
17
use Sunrise\Http\Router\RouteCollectionGroupActionInterface;
18
use Sunrise\Http\Router\Router;
19
use Sunrise\Http\ServerRequest\ServerRequestFactory;
20
21
/**
22
 * Import functions
23
 */
24
use function array_merge;
25
26
/**
27
 * RouterTest
28
 */
29
class RouterTest extends TestCase
30
{
31
32
    /**
33
     * @return void
34
     */
35
    public function testConstructor() : void
36
    {
37
        $router = new Router();
38
39
        $this->assertInstanceOf(MiddlewareInterface::class, $router);
40
        $this->assertInstanceOf(RequestHandlerInterface::class, $router);
41
    }
42
43
    /**
44
     * @return void
45
     */
46
    public function testAddRoute() : void
47
    {
48
        $routes = [
49
            new Fixture\TestRoute(),
50
            new Fixture\TestRoute(),
51
            new Fixture\TestRoute(),
52
        ];
53
54
        $router = new Router();
55
        $router->addRoute(...$routes);
56
57
        $this->assertSame($routes, $router->getRoutes());
58
    }
59
60
    /**
61
     * @return void
62
     */
63
    public function testAddMiddleware() : void
64
    {
65
        $middlewares = [
66
            new Fixture\BlankMiddleware(),
67
            new Fixture\BlankMiddleware(),
68
            new Fixture\BlankMiddleware(),
69
        ];
70
71
        $router = new Router();
72
        $router->addMiddleware(...$middlewares);
73
74
        $this->assertSame($middlewares, $router->getMiddlewares());
75
    }
76
77
    /**
78
     * @return void
79
     */
80
    public function testAddExistingRoute() : void
81
    {
82
        $route = new Fixture\TestRoute();
83
84
        $router = new Router();
85
        $router->addRoute($route);
86
87
        $this->expectException(RouteAlreadyExistsException::class);
88
        $this->expectExceptionMessage('A route with the name "' . $route->getName() . '" already exists.');
89
90
        try {
91
            $router->addRoute($route);
92
        } catch (RouteAlreadyExistsException $e) {
93
            $this->assertSame($route, $e->fromContext('route'));
94
95
            throw $e;
96
        }
97
    }
98
99
    /**
100
     * @return void
101
     */
102
    public function testAddExistingMiddleware() : void
103
    {
104
        $middleware = new Fixture\BlankMiddleware();
105
106
        $router = new Router();
107
        $router->addMiddleware($middleware);
108
109
        $this->expectException(MiddlewareAlreadyExistsException::class);
110
        $this->expectExceptionMessage('A middleware with the hash "' . $middleware->getHash() . '" already exists.');
111
112
        try {
113
            $router->addMiddleware($middleware);
114
        } catch (MiddlewareAlreadyExistsException $e) {
115
            $this->assertSame($middleware, $e->fromContext('middleware'));
116
117
            throw $e;
118
        }
119
    }
120
121
    /**
122
     * @return void
123
     */
124
    public function testGetAllowedMethods() : void
125
    {
126
        $routes = [
127
            new Fixture\TestRoute(),
128
            new Fixture\TestRoute(),
129
            new Fixture\TestRoute(),
130
        ];
131
132
        $expectedMethods = array_merge(
133
            $routes[0]->getMethods(),
134
            $routes[1]->getMethods(),
135
            $routes[2]->getMethods()
136
        );
137
138
        $router = new Router();
139
140
        $this->assertSame([], $router->getAllowedMethods());
141
142
        $router->addRoute(...$routes);
143
144
        $this->assertSame($expectedMethods, $router->getAllowedMethods());
145
    }
146
147
    /**
148
     * @return void
149
     */
150
    public function testGetRoute() : void
151
    {
152
        $routes = [
153
            new Fixture\TestRoute(),
154
            new Fixture\TestRoute(),
155
            new Fixture\TestRoute(),
156
        ];
157
158
        $router = new Router();
159
        $router->addRoute(...$routes);
160
161
        $this->assertSame($routes[1], $router->getRoute($routes[1]->getName()));
162
    }
163
164
    /**
165
     * @return void
166
     */
167
    public function testGetUndefinedRoute() : void
168
    {
169
        $routes = [
170
            new Fixture\TestRoute(),
171
            new Fixture\TestRoute(),
172
            new Fixture\TestRoute(),
173
        ];
174
175
        $router = new Router();
176
        $router->addRoute(...$routes);
177
178
        $this->expectException(RouteNotFoundException::class);
179
        $this->expectExceptionMessage('No route found for the name "foo".');
180
181
        try {
182
            $router->getRoute('foo');
183
        } catch (RouteNotFoundException $e) {
184
            $this->assertSame('foo', $e->fromContext('name'));
185
186
            throw $e;
187
        }
188
    }
189
190
    /**
191
     * The test method only proxies the function `path_build`,
192
     * the function should be tested separately.
193
     *
194
     * @return void
195
     */
196
    public function testGenerateUri() : void
197
    {
198
        $route = new Fixture\TestRoute();
199
200
        $router = new Router();
201
        $router->addRoute($route);
202
203
        $this->assertSame($route->getPath(), $router->generateUri($route->getName()));
204
    }
205
206
    /**
207
     * @return void
208
     */
209
    public function testMatch() : void
210
    {
211
        $routes = [
212
            new Fixture\TestRoute(),
213
            new Fixture\TestRoute(),
214
            new Fixture\TestRoute(),
215
            new Fixture\TestRoute(),
216
            new Fixture\TestRoute(),
217
        ];
218
219
        $router = new Router();
220
        $router->addRoute(...$routes);
221
222
        $foundRoute = $router->match((new ServerRequestFactory)
223
            ->createServerRequest(
224
                $routes[2]->getMethods()[1],
225
                $routes[2]->getPath()
226
            ));
227
228
        $this->assertSame($routes[2]->getName(), $foundRoute->getName());
229
    }
230
231
    /**
232
     * @return void
233
     */
234
    public function testMatchForUnallowedMethod() : void
235
    {
236
        $routes = [
237
            new Fixture\TestRoute(),
238
            new Fixture\TestRoute(),
239
            new Fixture\TestRoute(),
240
        ];
241
242
        $router = new Router();
243
        $router->addRoute(...$routes);
244
245
        $request = (new ServerRequestFactory)
246
            ->createServerRequest('GET', $routes[0]->getPath());
247
248
        $this->expectException(MethodNotAllowedException::class);
249
        $this->expectExceptionMessage('The method "GET" is not allowed.');
250
251
        try {
252
            $router->match($request);
253
        } catch (MethodNotAllowedException $e) {
254
            $allowedMethods = array_merge(
255
                $routes[0]->getMethods(),
256
                $routes[1]->getMethods(),
257
                $routes[2]->getMethods()
258
            );
259
260
            $this->assertSame('GET', $e->fromContext('method'));
261
            $this->assertSame($allowedMethods, $e->fromContext('allowed'));
262
            $this->assertSame($allowedMethods, $e->getAllowedMethods());
263
264
            throw $e;
265
        }
266
    }
267
268
    /**
269
     * @return void
270
     */
271
    public function testMatchForUndefinedRoute() : void
272
    {
273
        $routes = [
274
            new Fixture\TestRoute(),
275
            new Fixture\TestRoute(),
276
            new Fixture\TestRoute(),
277
        ];
278
279
        $router = new Router();
280
        $router->addRoute(...$routes);
281
282
        $request = (new ServerRequestFactory)
283
            ->createServerRequest($routes[0]->getMethods()[0], '/');
284
285
        $this->expectException(RouteNotFoundException::class);
286
        $this->expectExceptionMessage('No route found for the URI "/".');
287
288
        try {
289
            $router->match($request);
290
        } catch (RouteNotFoundException $e) {
291
            $this->assertSame('/', $e->fromContext('uri'));
292
293
            throw $e;
294
        }
295
    }
296
297
    /**
298
     * @return void
299
     */
300
    public function testHandle() : void
301
    {
302
        $routes = [
303
            new Fixture\TestRoute(),
304
            new Fixture\TestRoute(),
305
            new Fixture\TestRoute(),
306
            new Fixture\TestRoute(),
307
            new Fixture\TestRoute(),
308
        ];
309
310
        $router = new Router();
311
        $router->addRoute(...$routes);
312
313
        $router->handle((new ServerRequestFactory)
314
            ->createServerRequest(
315
                $routes[2]->getMethods()[1],
316
                $routes[2]->getPath()
317
            ));
318
319
        $this->assertTrue($routes[2]->getRequestHandler()->isRunned());
320
    }
321
322
    /**
323
     * @return void
324
     */
325
    public function testHandleWithMiddlewares() : void
326
    {
327
        $route = new Fixture\TestRoute();
328
329
        $middlewares = [
330
            new Fixture\BlankMiddleware(),
331
            new Fixture\BlankMiddleware(),
332
            new Fixture\BlankMiddleware(),
333
        ];
334
335
        $router = new Router();
336
        $router->addRoute($route);
337
        $router->addMiddleware(...$middlewares);
338
        $router->handle((new ServerRequestFactory)
339
            ->createServerRequest(
340
                $route->getMethods()[0],
341
                $route->getPath()
342
            ));
343
344
        $this->assertTrue($middlewares[0]->isRunned());
345
        $this->assertTrue($middlewares[1]->isRunned());
346
        $this->assertTrue($middlewares[2]->isRunned());
347
        $this->assertTrue($route->getRequestHandler()->isRunned());
348
    }
349
350
    /**
351
     * @return void
352
     */
353
    public function testHandleWithBrokenMiddleware() : void
354
    {
355
        $route = new Fixture\TestRoute();
356
357
        $middlewares = [
358
            new Fixture\BlankMiddleware(),
359
            new Fixture\BlankMiddleware(true),
360
            new Fixture\BlankMiddleware(),
361
        ];
362
363
        $router = new Router();
364
        $router->addRoute($route);
365
        $router->addMiddleware(...$middlewares);
366
        $router->handle((new ServerRequestFactory)
367
            ->createServerRequest(
368
                $route->getMethods()[0],
369
                $route->getPath()
370
            ));
371
372
        $this->assertTrue($middlewares[0]->isRunned());
373
        $this->assertTrue($middlewares[1]->isRunned());
374
        $this->assertFalse($middlewares[2]->isRunned());
375
        $this->assertFalse($route->getRequestHandler()->isRunned());
376
    }
377
378
    /**
379
     * @return void
380
     */
381
    public function testHandleForUnallowedMethod() : void
382
    {
383
        $routes = [
384
            new Fixture\TestRoute(),
385
            new Fixture\TestRoute(),
386
            new Fixture\TestRoute(),
387
        ];
388
389
        $router = new Router();
390
        $router->addRoute(...$routes);
391
392
        $request = (new ServerRequestFactory)
393
            ->createServerRequest('GET', '/');
394
395
        $this->expectException(MethodNotAllowedException::class);
396
        $this->expectExceptionMessage('The method "GET" is not allowed.');
397
398
        try {
399
            $router->handle($request);
400
        } catch (MethodNotAllowedException $e) {
401
            $allowedMethods = array_merge(
402
                $routes[0]->getMethods(),
403
                $routes[1]->getMethods(),
404
                $routes[2]->getMethods()
405
            );
406
407
            $this->assertSame('GET', $e->fromContext('method'));
408
            $this->assertSame($allowedMethods, $e->fromContext('allowed'));
409
            $this->assertSame($allowedMethods, $e->getAllowedMethods());
410
411
            throw $e;
412
        }
413
    }
414
415
    /**
416
     * @return void
417
     */
418
    public function testHandleForUndefinedRoute() : void
419
    {
420
        $routes = [
421
            new Fixture\TestRoute(),
422
            new Fixture\TestRoute(),
423
            new Fixture\TestRoute(),
424
        ];
425
426
        $router = new Router();
427
        $router->addRoute(...$routes);
428
429
        $request = (new ServerRequestFactory)
430
            ->createServerRequest($routes[0]->getMethods()[0], '/');
431
432
        $this->expectException(RouteNotFoundException::class);
433
        $this->expectExceptionMessage('No route found for the URI "/".');
434
435
        try {
436
            $router->handle($request);
437
        } catch (RouteNotFoundException $e) {
438
            $this->assertSame('/', $e->fromContext('uri'));
439
440
            throw $e;
441
        }
442
    }
443
444
    /**
445
     * @return void
446
     */
447
    public function testProcess() : void
448
    {
449
        $routes = [
450
            new Fixture\TestRoute(),
451
            new Fixture\TestRoute(),
452
            new Fixture\TestRoute(),
453
            new Fixture\TestRoute(),
454
            new Fixture\TestRoute(),
455
        ];
456
457
        $router = new Router();
458
        $router->addRoute(...$routes);
459
460
        $fallback = new Fixture\BlankRequestHandler();
461
462
        $router->process((new ServerRequestFactory)
463
            ->createServerRequest(
464
                $routes[2]->getMethods()[1],
465
                $routes[2]->getPath()
466
            ), $fallback);
467
468
        $this->assertTrue($routes[2]->getRequestHandler()->isRunned());
469
        $this->assertFalse($fallback->isRunned());
470
    }
471
472
    /**
473
     * @return void
474
     */
475
    public function testProcessForUnallowedMethod() : void
476
    {
477
        $routes = [
478
            new Fixture\TestRoute(),
479
            new Fixture\TestRoute(),
480
            new Fixture\TestRoute(),
481
        ];
482
483
        $router = new Router();
484
        $router->addRoute(...$routes);
485
486
        $request = (new ServerRequestFactory)
487
            ->createServerRequest('GET', '/');
488
489
        $fallback = new Fixture\BlankRequestHandler();
490
491
        $router->process($request, $fallback);
492
493
        $this->assertInstanceOf(
494
            MethodNotAllowedException::class,
495
            $fallback->getAttribute(Router::ATTR_NAME_FOR_ROUTING_ERROR)
496
        );
497
498
        $this->assertTrue($fallback->isRunned());
499
    }
500
501
    /**
502
     * @return void
503
     */
504
    public function testProcessForUndefinedRoute() : void
505
    {
506
        $routes = [
507
            new Fixture\TestRoute(),
508
            new Fixture\TestRoute(),
509
            new Fixture\TestRoute(),
510
        ];
511
512
        $router = new Router();
513
        $router->addRoute(...$routes);
514
515
        $request = (new ServerRequestFactory)
516
            ->createServerRequest($routes[0]->getMethods()[0], '/');
517
518
        $fallback = new Fixture\BlankRequestHandler();
519
520
        $router->process($request, $fallback);
521
522
        $this->assertInstanceOf(
523
            RouteNotFoundException::class,
524
            $fallback->getAttribute(Router::ATTR_NAME_FOR_ROUTING_ERROR)
525
        );
526
527
        $this->assertTrue($fallback->isRunned());
528
    }
529
530
    /**
531
     * @return void
532
     */
533
    public function testGroup() : void
534
    {
535
        $router = new Router();
536
537
        $this->assertInstanceOf(
538
            RouteCollectionGroupActionInterface::class,
539
            $router->group(function ($group) {
540
                $group->get('api.ping', '/ping', new Fixture\BlankRequestHandler());
541
542
                $this->assertInstanceOf(
543
                    RouteCollectionGroupActionInterface::class,
544
                    $group->group(function ($group) {
545
546
                        $this->assertInstanceOf(
547
                            RouteCollectionGroupActionInterface::class,
548
                            $group->group(function ($group) {
549
                                $group->post('api.section.create', '/create', new Fixture\BlankRequestHandler());
550
                                $group->patch('api.section.update', '/update/{id}', new Fixture\BlankRequestHandler());
551
                            })->addPrefix('/section')
552
                        );
553
554
                        $this->assertInstanceOf(
555
                            RouteCollectionGroupActionInterface::class,
556
                            $group->group(function ($group) {
557
                                $group->post('api.product.create', '/create', new Fixture\BlankRequestHandler());
558
                                $group->patch('api.product.update', '/update/{id}', new Fixture\BlankRequestHandler());
559
                            })->addPrefix('/product')
560
                        );
561
                    })->addPrefix('/v1')
562
                );
563
            })->addPrefix('/api')
564
        );
565
566
        $this->assertContains([
567
            'name' => 'api.ping',
568
            'path' => '/api/ping',
569
            'methods' => [Router::METHOD_GET],
570
            'requestHandler' => 'Sunrise\Http\Router\Tests\Fixture\BlankRequestHandler',
571
            'middlewares' => [],
572
            'attributes' => [],
573
        ], Fixture\Helper::routesToArray($router->getRoutes()));
574
575
        $this->assertContains([
576
            'name' => 'api.section.create',
577
            'path' => '/api/v1/section/create',
578
            'methods' => [Router::METHOD_POST],
579
            'requestHandler' => 'Sunrise\Http\Router\Tests\Fixture\BlankRequestHandler',
580
            'middlewares' => [],
581
            'attributes' => [],
582
        ], Fixture\Helper::routesToArray($router->getRoutes()));
583
584
        $this->assertContains([
585
            'name' => 'api.section.update',
586
            'path' => '/api/v1/section/update/{id}',
587
            'methods' => [Router::METHOD_PATCH],
588
            'requestHandler' => 'Sunrise\Http\Router\Tests\Fixture\BlankRequestHandler',
589
            'middlewares' => [],
590
            'attributes' => [],
591
        ], Fixture\Helper::routesToArray($router->getRoutes()));
592
593
        $this->assertContains([
594
            'name' => 'api.product.create',
595
            'path' => '/api/v1/product/create',
596
            'methods' => [Router::METHOD_POST],
597
            'requestHandler' => 'Sunrise\Http\Router\Tests\Fixture\BlankRequestHandler',
598
            'middlewares' => [],
599
            'attributes' => [],
600
        ], Fixture\Helper::routesToArray($router->getRoutes()));
601
602
        $this->assertContains([
603
            'name' => 'api.product.update',
604
            'path' => '/api/v1/product/update/{id}',
605
            'methods' => [Router::METHOD_PATCH],
606
            'requestHandler' => 'Sunrise\Http\Router\Tests\Fixture\BlankRequestHandler',
607
            'middlewares' => [],
608
            'attributes' => [],
609
        ], Fixture\Helper::routesToArray($router->getRoutes()));
610
    }
611
612
    /**
613
     * @return void
614
     */
615
    public function testLoad() : void
616
    {
617
        $router = new Router();
618
619
        $expectedRoutes = [
620
            new Fixture\TestRoute(),
621
            new Fixture\TestRoute(),
622
            new Fixture\TestRoute(),
623
        ];
624
625
        $loader = $this->createMock(LoaderInterface::class);
626
627
        $loader->method('load')->willReturn(
628
            new RouteCollection(...$expectedRoutes)
629
        );
630
631
        $router->load($loader);
632
633
        $this->assertSame($expectedRoutes, $router->getRoutes());
634
    }
635
636
    /**
637
     * @return void
638
     */
639
    public function testMultipleLoad() : void
640
    {
641
        $router = new Router();
642
643
        $expectedRoutes = [
644
            new Fixture\TestRoute(),
645
            new Fixture\TestRoute(),
646
            new Fixture\TestRoute(),
647
        ];
648
649
        $loaders = [
650
            $this->createMock(LoaderInterface::class),
651
            $this->createMock(LoaderInterface::class),
652
            $this->createMock(LoaderInterface::class),
653
        ];
654
655
        $loaders[0]->method('load')->willReturn(
656
            new RouteCollection($expectedRoutes[0])
657
        );
658
659
        $loaders[1]->method('load')->willReturn(
660
            new RouteCollection($expectedRoutes[1])
661
        );
662
663
        $loaders[2]->method('load')->willReturn(
664
            new RouteCollection($expectedRoutes[2])
665
        );
666
667
        $router->load(...$loaders);
668
669
        $this->assertSame($expectedRoutes, $router->getRoutes());
670
    }
671
}
672