Passed
Pull Request — master (#9)
by Divine
12:53 queued 03:00
created

testWithImplicitRouteMatch()   C

Complexity

Conditions 9
Paths 256

Size

Total Lines 61
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 32
c 1
b 0
f 0
nc 256
nop 6
dl 0
loc 61
rs 6.5222

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Flight Routing.
7
 *
8
 * PHP version 7.2 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 Biurad Group (https://biurad.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Flight\Routing\Tests;
19
20
use Flight\Routing\Concerns\HttpMethods;
21
use Flight\Routing\Exceptions\MethodNotAllowedException;
22
use Flight\Routing\Exceptions\RouteNotFoundException;
23
use Flight\Routing\Interfaces\RouteCollectorInterface;
24
use Flight\Routing\Interfaces\RouteGroupInterface;
25
use Flight\Routing\Interfaces\RouteInterface;
26
use Flight\Routing\Interfaces\RouterInterface;
27
use Flight\Routing\Interfaces\RouterProxyInterface;
28
use Flight\Routing\Route;
29
use Flight\Routing\RouteCollector;
30
use Flight\Routing\RouteResults;
31
use Flight\Routing\Tests\Fixtures\SampleController;
32
use Flight\Routing\Tests\Fixtures\SampleMiddleware;
33
use Generator;
34
use Laminas\Stratigility\Next;
35
use PHPUnit\Framework\Assert;
36
use PHPUnit\Framework\TestCase;
37
use Prophecy\Argument;
38
use Psr\Container\ContainerInterface;
0 ignored issues
show
Bug introduced by
The type Psr\Container\ContainerInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
39
use Psr\Http\Message\ResponseInterface;
40
use Psr\Http\Message\ServerRequestInterface;
41
use Psr\Http\Server\MiddlewareInterface;
42
use Psr\Http\Server\RequestHandlerInterface;
43
44
/**
45
 * Base class for testing adapter integrations.
46
 *
47
 * Implementers of adapters should extend this class in their test suite,
48
 * implementing the `getRouter()` method.
49
 *
50
 * This test class tests that the router correctly marshals the allowed methods
51
 * for a match that matches the path, but not the request method.
52
 */
53
abstract class RouterIntegrationTest extends TestCase
54
{
55
    /**
56
     * Delegate dispatcher selection to routing functionality
57
     * Based on implemented Router.
58
     *
59
     * @return RouterInterface
60
     */
61
    abstract public function getRouter(): RouterInterface;
62
63
    /**
64
     * Delegate psr factories on selected router.
65
     *
66
     * @see getRouter() method.
67
     *
68
     * @return array
69
     */
70
    abstract public function psrServerResponseFactory(): array;
71
72
    /**
73
     * The Delegate RouteCollector.
74
     *
75
     * @param ContainerInterface $container
76
     *
77
     * @return RouteCollectorInterface
78
     */
79
    public function getRouteCollection(ContainerInterface $container = null): RouteCollectorInterface
80
    {
81
        [, $responseFactory] = $this->psrServerResponseFactory();
82
83
        return new RouteCollector($responseFactory, $this->getRouter(), null, $container);
84
    }
85
86
    public function createInvalidResponseFactory(): callable
87
    {
88
        return function (): void {
89
            Assert::fail('Response generated when it should not have been');
90
        };
91
    }
92
93
    public function method(): Generator
94
    {
95
        yield 'HEAD: head, post' => [
96
            HttpMethods::METHOD_HEAD,
97
            [HttpMethods::METHOD_HEAD, HttpMethods::METHOD_POST],
98
        ];
99
100
        yield 'HEAD: head, get' => [
101
            HttpMethods::METHOD_HEAD,
102
            [HttpMethods::METHOD_HEAD, HttpMethods::METHOD_GET],
103
        ];
104
105
        yield 'HEAD: post, head' => [
106
            HttpMethods::METHOD_HEAD,
107
            [HttpMethods::METHOD_POST, HttpMethods::METHOD_HEAD],
108
        ];
109
110
        yield 'HEAD: get, head' => [
111
            HttpMethods::METHOD_HEAD,
112
            [HttpMethods::METHOD_GET, HttpMethods::METHOD_HEAD],
113
        ];
114
115
        yield 'PUT: put, patch' => [
116
            HttpMethods::METHOD_PATCH,
117
            [HttpMethods::METHOD_PUT, HttpMethods::METHOD_PATCH],
118
        ];
119
120
        yield 'PATCH: patch, put' => [
121
            HttpMethods::METHOD_PUT,
122
            [HttpMethods::METHOD_PUT, HttpMethods::METHOD_PATCH],
123
        ];
124
125
        yield 'DELETE: patch, delete' => [
126
            HttpMethods::METHOD_DELETE,
127
            [HttpMethods::METHOD_DELETE, HttpMethods::METHOD_PATCH],
128
        ];
129
130
        yield 'OPTIONS: options, post' => [
131
            HttpMethods::METHOD_OPTIONS,
132
            [HttpMethods::METHOD_OPTIONS, HttpMethods::METHOD_POST],
133
        ];
134
135
        yield 'OPTIONS: options, get' => [
136
            HttpMethods::METHOD_OPTIONS,
137
            [HttpMethods::METHOD_OPTIONS, HttpMethods::METHOD_GET],
138
        ];
139
140
        yield 'OPTIONS: post, options' => [
141
            HttpMethods::METHOD_OPTIONS,
142
            [HttpMethods::METHOD_POST, HttpMethods::METHOD_OPTIONS],
143
        ];
144
145
        yield 'OPTIONS: get, options' => [
146
            HttpMethods::METHOD_OPTIONS,
147
            [HttpMethods::METHOD_GET, HttpMethods::METHOD_OPTIONS],
148
        ];
149
    }
150
151
    /**
152
     * @dataProvider method
153
     */
154
    public function testExplicitWithRouteCollector(string $method, array $routes): void
155
    {
156
        $router                            = $this->getRouteCollection();
157
        [$serverRequest, $responseFactory] = $this->psrServerResponseFactory();
158
159
        $finalResponse = $responseFactory->createResponse();
160
        $finalResponse = $finalResponse->withHeader('foo-bar', 'baz');
161
        $finalResponse->getBody()->write('FOO BAR BODY');
162
163
        $finalHandler = $this->prophesize(RequestHandlerInterface::class);
164
        $finalHandler
165
            ->handle(Argument::that(function (ServerRequestInterface $request) use ($method) {
166
                Assert::assertSame($method, $request->getMethod());
167
168
                $routeResult = $request->getAttribute(RouteResults::class);
169
                Assert::assertInstanceOf(RouteResults::class, $routeResult);
170
                Assert::assertTrue(RouteResults::FOUND === $routeResult->getROuteStatus());
171
172
                $matchedRoute = $routeResult->getMatchedRoute();
173
                Assert::assertNotFalse($matchedRoute);
174
175
                return true;
176
            }))
177
            ->willReturn($finalResponse)
178
            ->shouldBeCalledTimes(1);
179
180
        foreach ($routes as $routeMethod) {
181
            $route = new Route([$routeMethod], '/api/v1/me', $finalHandler->reveal());
182
183
            if ($routeMethod === $method) {
184
                $router->setRoute($route);
185
            }
186
        }
187
188
        $path     = $serverRequest->getUri()->withPath('/api/v1/me');
189
        $response = $router->handle($serverRequest->withMethod($method)->withUri($path));
190
191
        $this->assertEquals(200, $response->getStatusCode());
192
        $this->assertSame('FOO BAR BODY', (string) $response->getBody());
193
        $this->assertTrue($response->hasHeader('foo-bar'));
194
        $this->assertSame('baz', $response->getHeaderLine('foo-bar'));
195
    }
196
197
    /**
198
     * @dataProvider method
199
     */
200
    public function testExplicitWithoutCollector(string $method, array $routes): void
201
    {
202
        $router           = $this->getRouter();
203
        [$serverRequest,] = $this->psrServerResponseFactory();
204
205
        $finalHandler = $this->prophesize(RequestHandlerInterface::class);
206
        $finalHandler->handle(Argument::any())->shouldNotBeCalled();
207
208
        foreach ($routes as $routeMethod) {
209
            $route = new Route([$routeMethod], '/api/v1/me', $finalHandler->reveal());
210
211
            if ($routeMethod === $method) {
212
                $router->addRoute($route);
213
            }
214
        }
215
216
        $path    = $serverRequest->getUri()->withPath('/api/v1/me');
217
        $results = $router->match($serverRequest->withMethod($method)->withUri($path));
218
219
        $this->assertTrue($results::FOUND === $results->getRouteStatus());
220
        $this->assertNotFalse($results->getMatchedRoute());
221
    }
222
223
    public function withoutImplicitMiddleware()
224
    {
225
        // @codingStandardsIgnoreStart
226
        // request method, array of allowed methods for a route.
227
        yield 'HEAD: get'          => [HttpMethods::METHOD_HEAD, [HttpMethods::METHOD_GET]];
228
229
        yield 'HEAD: post'         => [HttpMethods::METHOD_HEAD, [HttpMethods::METHOD_POST]];
230
231
        yield 'HEAD: get, post'    => [HttpMethods::METHOD_HEAD, [HttpMethods::METHOD_GET, HttpMethods::METHOD_POST]];
232
233
        yield 'OPTIONS: get'       => [HttpMethods::METHOD_OPTIONS, [HttpMethods::METHOD_GET]];
234
235
        yield 'OPTIONS: post'      => [HttpMethods::METHOD_OPTIONS, [HttpMethods::METHOD_POST]];
236
237
        yield 'OPTIONS: get, post' => [HttpMethods::METHOD_OPTIONS, [HttpMethods::METHOD_GET, HttpMethods::METHOD_POST]];
238
        // @codingStandardsIgnoreEnd
239
    }
240
241
    /**
242
     * In case we are not using Implicit*Middlewares and we don't have any route with explicit method
243
     * returned response should be 405: Method Not Allowed - handled by MethodNotAllowedMiddleware.
244
     *
245
     * @dataProvider withoutImplicitMiddleware
246
     */
247
    public function testWithoutImplicitMiddleware(string $requestMethod, array $allowedMethods): void
248
    {
249
        $router           = $this->getRouteCollection();
250
        [$serverRequest,] = $this->psrServerResponseFactory();
251
252
        $finalResponse = $this->prophesize(ResponseInterface::class);
253
        $finalResponse->withStatus(405)->will([$finalResponse, 'reveal']);
254
        $finalResponse->withHeader('Allow', \implode(',', $allowedMethods))->will([$finalResponse, 'reveal']);
255
256
        $finalHandler = $this->prophesize(RequestHandlerInterface::class);
257
        $finalHandler->handle(Argument::any())->shouldNotBeCalled();
258
259
        foreach ($allowedMethods as $routeMethod) {
260
            $route = (new Route([$routeMethod], '/api/v1/me', $finalHandler->reveal()))
261
                ->addMiddleware(
262
                    function (ServerRequestInterface $request, RequestHandlerInterface $handler) {
263
                        return $handler->handle($request);
264
                    }
265
                );
266
267
            $router->setRoute($route);
268
        }
269
270
        $this->expectException(MethodNotAllowedException::class);
271
272
        $path = $serverRequest->getUri()->withPath('/api/v1/me');
273
        $router->handle($serverRequest->withMethod($requestMethod)->withUri($path));
274
    }
275
276
    /**
277
     * Provider for the testImplicitHeadRequest method.
278
     *
279
     * Implementations must provide this method. Each test case returned
280
     * must consist of the following three elements, in order:
281
     *
282
     * - string route path (the match string)
283
     * - string request path (the path in the ServerRequest instance)
284
     * - string request method (for ServerRequest instance)
285
     * - callable|object|string|null, returning route's response
286
     * - array route options (if any/required): selected keys:
287
     *   - [
288
     *        name => {subject},
289
     *        generate => [$params, $query]
290
     *        domain => {host}, (for ServerRequest instance)
291
     *        scheme => {scheme}, (for ServerRequest instance)
292
     *        scheme => {scheme},
293
     *        middlewares => [...],
294
     *        regex => [{param} => {pattern}...],
295
     *        defaults => [{$key} => {$value}...]
296
     *     ]
297
     * - array of asserts options (if any/required): selected keys:
298
     *   - [
299
     *        body => {contents},
300
     *        generate => {$path}
301
     *        scheme => {scheme}
302
     *        status => {code},
303
     *        content-type => {content-type},
304
     *        header => {header}
305
     *     ]
306
     */
307
    abstract public function implicitRoutesAndRequests(): Generator;
308
309
    /**
310
     * @dataProvider implicitRoutesAndRequests
311
     */
312
    public function testWithImplicitMiddleware(
313
        string $routePath,
314
        string $requestPath,
315
        string $requestMethod,
316
        $controller,
0 ignored issues
show
Unused Code introduced by
The parameter $controller 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

316
        /** @scrutinizer ignore-unused */ $controller,

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...
317
        array $routeOptions = [],
318
        array $asserts = []
0 ignored issues
show
Unused Code introduced by
The parameter $asserts 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

318
        /** @scrutinizer ignore-unused */ array $asserts = []

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...
319
    ): void {
320
        $router                            = $this->getRouteCollection();
321
        [$serverRequest, $responseFactory] = $this->psrServerResponseFactory();
322
323
        $finalResponse = (new $responseFactory())->createResponse();
324
        $finalResponse = $finalResponse->withHeader('foo-bar', 'baz');
325
326
        $middleware = $this->prophesize(MiddlewareInterface::class);
327
        $middleware
328
            ->process(Argument::type(ServerRequestInterface::class), Argument::type(RequestHandlerInterface::class))
329
            ->willReturn($finalResponse)
330
            ->shouldBeCalledTimes(1);
331
332
        $router->map([$requestMethod], $routePath, function () use ($finalResponse) {
333
            return $finalResponse;
334
        })
335
        ->addMiddleware(
336
            function (ServerRequestInterface $req, RequestHandlerInterface $handle) use ($middleware, $requestMethod) {
337
                Assert::assertEquals($requestMethod, $req->getMethod());
338
                Assert::assertInstanceOf(Next::class, $handle);
339
340
                return $middleware->reveal()->process($req, $handle);
341
            }
342
        )
343
        ->addDomain($routeOptions['domain'] ?? '')
344
        ->setName($routeOptions['name'] ?? null)
345
        ->whereArray($routeOptions['regex'] ?? [])
346
        ->addDefaults($routeOptions['defaults'] ?? [])
347
        ->addSchemes($routeOptions['scheme'] ?? null);
348
349
        $path = $serverRequest->getUri()->withPath($requestPath);
350
351
        if (isset($routeOptions['domain'])) {
352
            $path = $path->withHost($routeOptions['domain']);
353
        }
354
355
        if (isset($routeOptions['scheme'])) {
356
            $path = $path->withScheme($routeOptions['scheme']);
357
        }
358
359
        $response = $router->handle($serverRequest->withMethod($requestMethod)->withUri($path));
360
361
        $this->assertSame($finalResponse, $response);
362
        $this->assertEquals('baz', $response->getHeaderLine('foo-bar'));
363
    }
364
365
    /**
366
     * @dataProvider implicitRoutesAndRequests
367
     */
368
    public function testWithImplicitRouteMatch(
369
        string $routePath,
370
        string $requestPath,
371
        string $requestMethod,
372
        $controller,
373
        array $routeOptions = [],
374
        array $asserts = []
375
    ): void {
376
        $router           = $this->getRouteCollection();
377
        [$serverRequest,] = $this->psrServerResponseFactory();
378
379
        $router->map([$requestMethod], $routePath, $controller)
380
        ->setName($routeOptions['name'] ?? null)
381
        ->addMiddleware($routeOptions['middlewares'] ?? [])
382
        ->whereArray($routeOptions['regex'] ?? [])
383
        ->addDefaults($routeOptions['defaults'] ?? [])
384
        ->addSchemes($routeOptions['scheme'] ?? null);
385
386
        $path = $serverRequest->getUri()->withPath($requestPath);
387
388
        if (isset($routeOptions['domain'])) {
389
            $path = $path->withHost($routeOptions['domain']);
390
        }
391
392
        if (isset($routeOptions['scheme'])) {
393
            $path = $path->withScheme($routeOptions['scheme']);
394
        }
395
396
        $response = $router->handle($serverRequest->withMethod($requestMethod)->withUri($path));
397
398
        if (isset($asserts['status'])) {
399
            $this->assertSame($asserts['status'], $response->getStatusCode());
400
        }
401
402
        if (isset($asserts['body'])) {
403
            $this->assertEquals($asserts['body'], (string) $response->getBody());
404
        }
405
406
        if (isset($asserts['content-type'])) {
407
            $this->assertEquals($asserts['content-type'], $response->getHeaderLine('Content-Type'));
408
        }
409
410
        if (isset($asserts['header'])) {
411
            $this->assertTrue($response->hasHeader($asserts['header']));
412
        }
413
414
        $this->assertInstanceOf(ResponseInterface::class, $response);
415
        $this->assertInstanceOf(RouteInterface::class, $route = $router->currentRoute());
416
417
        if (isset($asserts['scheme'])) {
418
            $this->assertTrue(\in_array($asserts['scheme'], $route->getSchemes()));
0 ignored issues
show
Bug introduced by
It seems like $route->getSchemes() can also be of type null; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

418
            $this->assertTrue(\in_array($asserts['scheme'], /** @scrutinizer ignore-type */ $route->getSchemes()));
Loading history...
419
        }
420
421
        if (isset($asserts['generate'], $routeOptions['generate'], $routeOptions['name'])) {
422
            $generated = \call_user_func(
423
                [$router, 'generateUri'],
424
                $routeOptions['name'],
425
                ...$routeOptions['generate']
426
            );
427
428
            $this->assertEquals($asserts['generate'], $generated);
429
        }
430
    }
431
432
    public function groups(): Generator
433
    {
434
        yield 'Group: Simple Grouping'          => [
435
            [
436
                RouteGroupInterface::NAME           => 'group',
437
                RouteGroupInterface::PREFIX         => 'group',
438
                RouteGroupInterface::REQUIREMENTS   => [],
439
                RouteGroupInterface::DOMAIN         => '',
440
                RouteGroupInterface::DEFAULTS       => ['how' => 'What to do?'],
441
                RouteGroupInterface::MIDDLEWARES    => [SampleMiddleware::class],
442
                RouteGroupInterface::SCHEMES        => null,
443
            ],
444
            [
445
                'path'       => 'group/test',
446
                'default'    => 'how',
447
                'middleware' => SampleMiddleware::class,
448
            ],
449
        ];
450
451
        yield 'Group: Prefix Grouping'          => [
452
            [
453
                RouteGroupInterface::PREFIX         => 'group/',
454
            ],
455
            [
456
                'path' => 'group/test',
457
            ],
458
        ];
459
460
        yield 'Group: Namespace Grouping'       => [
461
            [
462
                RouteGroupInterface::PREFIX            => 'group_',
463
                RouteGroupInterface::DEFAULTS          => ['how' => 'What to do?'],
464
                RouteGroupInterface::NAMESPACE         => '\\Fixtures',
465
            ],
466
            [
467
                'path'      => 'group_test',
468
                'namespace' => true,
469
            ],
470
        ];
471
    }
472
473
    /**
474
     * @dataProvider groups
475
     */
476
    public function testWithImplicitRouteGroup(array $groupAttributes, array $asserts = []): void
477
    {
478
        $router = $this->getRouteCollection();
479
        $router->setNamespace('Flight\\Routing\\Tests\\');
480
481
        [$serverRequest,] = $this->psrServerResponseFactory();
482
        $path             = $serverRequest->getUri()->withPath('/' . ($asserts['path'] ?? 'group/test'));
483
484
        $router->group($groupAttributes, function (RouterProxyInterface $route) use ($asserts): void {
485
            $route->get(
486
                'test*<homePageRequestResponse>',
487
                isset($asserts['namespace']) ? 'SampleController' : SampleController::class
488
            )
489
            ->setName('_hello');
490
        });
491
492
        $router->handle($serverRequest->withMethod(HttpMethods::METHOD_GET)->withUri($path));
493
494
        $this->assertInstanceOf(RouteInterface::class, $route = $router->currentRoute());
495
496
        if (isset($asserts['default'])) {
497
            $this->assertTrue($route->hasDefault($asserts['default']));
498
        }
499
500
        if (isset($asserts['path'])) {
501
            $this->assertEquals($asserts['path'], $route->getPath());
502
        }
503
504
        if (isset($asserts['middleware'])) {
505
            $this->assertTrue(\in_array($asserts['middleware'], $route->getMiddlewares(), true));
506
        }
507
508
        if ('_hello' !== $route->getName()) {
509
            $this->assertEquals('group_hello', $route->getName());
510
        }
511
512
        $this->assertTrue($route->hasGroup());
513
    }
514
515
    public function testImplicitRouteNotFound(): void
516
    {
517
        $router           = $this->getRouteCollection();
518
        [$serverRequest,] = $this->psrServerResponseFactory();
519
520
        $router->map([HttpMethods::METHOD_GET], '/error', function () {
521
            return 'This is an error page';
522
        })->setName('error');
523
524
        $path = $serverRequest->getUri()->withPath('/error');
525
        $router->handle($serverRequest->withMethod(HttpMethods::METHOD_GET)->withUri($path));
526
527
        $this->assertInstanceOf(RouteInterface::class, $router->getNamedRoute('error'));
528
        $this->assertNotEmpty($router->getRoutes());
529
        $this->assertCount(1, $router->getRoutes());
530
531
        $this->expectException(RouteNotFoundException::class);
532
533
        $path = $serverRequest->getUri()->withPath('/not-found');
534
        $router->handle($serverRequest->withMethod(HttpMethods::METHOD_GET)->withUri($path));
535
    }
536
}
537