Completed
Push — master ( 93f6e0...a7ea34 )
by Aydin
27:25 queued 18:55
created

KleinTest::testGetPathFor()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 26
rs 8.8571
cc 2
eloc 15
nc 2
nop 0
1
<?php
2
/**
3
 * Klein (klein.php) - A fast & flexible router for PHP
4
 *
5
 * @author      Chris O'Hara <[email protected]>
6
 * @author      Trevor Suarez (Rican7) (contributor and v2 refactorer)
7
 * @copyright   (c) Chris O'Hara
8
 * @link        https://github.com/chriso/klein.php
9
 * @license     MIT
10
 */
11
12
namespace Klein\Tests;
13
14
use Exception;
15
use Klein\App;
16
use Klein\DataCollection\RouteCollection;
17
use Klein\Exceptions\DispatchHaltedException;
18
use Klein\Exceptions\HttpException;
19
use Klein\Exceptions\HttpExceptionInterface;
20
use Klein\Klein;
21
use Klein\Request;
22
use Klein\Response;
23
use Klein\Route;
24
use Klein\ServiceProvider;
25
use OutOfBoundsException;
26
27
/**
28
 * KleinTest 
29
 */
30
class KleinTest extends AbstractKleinTest
31
{
32
33
    /**
34
     * Constants
35
     */
36
37
    const TEST_CALLBACK_MESSAGE = 'yay';
38
39
40
    /**
41
     * Helpers
42
     */
43
44
    protected function getTestCallable($message = self::TEST_CALLBACK_MESSAGE)
45
    {
46
        return function () use ($message) {
47
            return $message;
48
        };
49
    }
50
51
52
    /**
53
     * Tests
54
     */
55
56
    public function testConstructor()
57
    {
58
        $klein = new Klein();
59
60
        $this->assertNotNull($klein);
61
        $this->assertTrue($klein instanceof Klein);
62
    }
63
64
    public function testService()
65
    {
66
        $service = $this->klein_app->service();
67
68
        $this->assertNotNull($service);
69
        $this->assertTrue($service instanceof ServiceProvider);
70
    }
71
72
    public function testApp()
73
    {
74
        $app = $this->klein_app->app();
75
76
        $this->assertNotNull($app);
77
        $this->assertTrue($app instanceof App);
78
    }
79
80
    public function testRoutes()
81
    {
82
        $routes = $this->klein_app->routes();
83
84
        $this->assertNotNull($routes);
85
        $this->assertTrue($routes instanceof RouteCollection);
86
    }
87
88
    public function testRequest()
89
    {
90
        $this->klein_app->dispatch();
91
92
        $request = $this->klein_app->request();
93
94
        $this->assertNotNull($request);
95
        $this->assertTrue($request instanceof Request);
96
    }
97
98
    public function testResponse()
99
    {
100
        $this->klein_app->dispatch();
101
102
        $response = $this->klein_app->response();
103
104
        $this->assertNotNull($response);
105
        $this->assertTrue($response instanceof Response);
106
    }
107
108
    public function testRespond()
109
    {
110
        $route = $this->klein_app->respond($this->getTestCallable());
111
112
        $object_id = spl_object_hash($route);
113
114
        $this->assertNotNull($route);
115
        $this->assertTrue($route instanceof Route);
116
        $this->assertTrue($this->klein_app->routes()->exists($object_id));
117
        $this->assertSame($route, $this->klein_app->routes()->get($object_id));
118
    }
119
120
    public function testWith()
121
    {
122
        // Test data
123
        $test_namespace = '/test/namespace';
124
        $passed_context = null;
125
126
        $this->klein_app->with(
127
            $test_namespace,
128
            function ($context) use (&$passed_context) {
129
                $passed_context = $context;
130
            }
131
        );
132
133
        $this->assertTrue($passed_context instanceof Klein);
134
    }
135
136
    public function testWithStringCallable()
137
    {
138
        // Test data
139
        $test_namespace = '/test/namespace';
140
141
        $this->klein_app->with(
142
            $test_namespace,
143
            'test_num_args_wrapper'
144
        );
145
146
        $this->expectOutputString('1');
147
    }
148
149
    /**
150
     * Weird PHPUnit bug is causing scope errors for the
151
     * isolated process tests, unless I run this also in an
152
     * isolated process
153
     *
154
     * @runInSeparateProcess
155
     */
156
    public function testWithUsingFileInclude()
157
    {
158
        // Test data
159
        $test_namespace = '/test/namespace';
160
        $test_routes_include = __DIR__ .'/routes/random.php';
161
162
        // Test file include
163
        $this->assertEmpty($this->klein_app->routes()->all());
164
        $this->klein_app->with($test_namespace, $test_routes_include);
165
166
        $this->assertNotEmpty($this->klein_app->routes()->all());
167
168
        $all_routes = array_values($this->klein_app->routes()->all());
169
        $test_route = $all_routes[0];
170
171
        $this->assertTrue($test_route instanceof Route);
172
        $this->assertSame($test_namespace . '/?', $test_route->getPath());
173
    }
174
175
    public function testDispatch()
176
    {
177
        $request = new Request();
178
        $response = new Response();
179
180
        $this->klein_app->dispatch($request, $response);
181
182
        $this->assertSame($request, $this->klein_app->request());
183
        $this->assertSame($response, $this->klein_app->response());
184
    }
185
186
    public function testGetPathFor()
187
    {
188
        // Test data
189
        $test_path = '/test';
190
        $test_name = 'Test Route Thing';
191
192
        $route = new Route($this->getTestCallable());
193
        $route->setPath($test_path);
194
        $route->setName($test_name);
195
196
        $this->klein_app->routes()->addRoute($route);
197
198
        // Make sure it fails if not prepared
199
        try {
200
            $this->klein_app->getPathFor($test_name);
201
        } catch (Exception $e) {
202
            $this->assertTrue($e instanceof OutOfBoundsException);
203
        }
204
205
        $this->klein_app->routes()->prepareNamed();
206
207
        $returned_path = $this->klein_app->getPathFor($test_name);
208
209
        $this->assertNotEmpty($returned_path);
210
        $this->assertSame($test_path, $returned_path);
211
    }
212
213
    public function testOnErrorWithStringCallables()
214
    {
215
        $this->klein_app->onError('test_num_args_wrapper');
216
217
        $this->klein_app->respond(
218
            function ($request, $response, $service) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

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

Loading history...
Unused Code introduced by
The parameter $response is not used and could be removed.

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

Loading history...
Unused Code introduced by
The parameter $service is not used and could be removed.

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

Loading history...
219
                throw new Exception('testing');
220
            }
221
        );
222
223
        $this->assertSame(
224
            '4',
225
            $this->dispatchAndReturnOutput()
226
        );
227
    }
228
229
    public function testOnErrorWithBadCallables()
230
    {
231
        $this->klein_app->onError('this_function_doesnt_exist');
232
233
        $this->klein_app->respond(
234
            function ($request, $response, $service) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

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

Loading history...
Unused Code introduced by
The parameter $response is not used and could be removed.

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

Loading history...
Unused Code introduced by
The parameter $service is not used and could be removed.

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

Loading history...
235
                throw new Exception('testing');
236
            }
237
        );
238
239
        $this->assertEmpty($this->klein_app->service()->flashes());
240
241
        $this->assertSame(
242
            '',
243
            $this->dispatchAndReturnOutput()
244
        );
245
246
        $this->assertNotEmpty($this->klein_app->service()->flashes());
247
248
        // Clean up
249
        session_destroy();
250
    }
251
252
    public function testOnHttpError()
253
    {
254
        // Create expected arguments
255
        $num_of_args = 0;
256
        $expected_arguments = array(
257
            'code'            => null,
258
            'klein'           => null,
259
            'matched'         => null,
260
            'methods_matched' => null,
261
            'exception'       => null,
262
        );
263
264
        $this->klein_app->onHttpError(
265
            function ($code, $klein, $matched, $methods_matched, $exception) use (&$num_of_args, &$expected_arguments) {
266
                // Keep track of our arguments
267
                $num_of_args = func_num_args();
268
                $expected_arguments['code'] = $code;
269
                $expected_arguments['klein'] = $klein;
270
                $expected_arguments['matched'] = $matched;
271
                $expected_arguments['methods_matched'] = $methods_matched;
272
                $expected_arguments['exception'] = $exception;
273
274
                $klein->response()->body($code .' error');
275
            }
276
        );
277
278
        $this->klein_app->dispatch(null, null, false);
279
280
        $this->assertSame(
281
            '404 error',
282
            $this->klein_app->response()->body()
283
        );
284
285
        $this->assertSame(count($expected_arguments), $num_of_args);
286
287
        $this->assertTrue(is_int($expected_arguments['code']));
288
        $this->assertTrue($expected_arguments['klein'] instanceof Klein);
289
        $this->assertTrue($expected_arguments['matched'] instanceof RouteCollection);
290
        $this->assertTrue(is_array($expected_arguments['methods_matched']));
291
        $this->assertTrue($expected_arguments['exception'] instanceof HttpExceptionInterface);
292
293
        $this->assertSame($expected_arguments['klein'], $this->klein_app);
294
    }
295
296
    public function testOnHttpErrorWithStringCallables()
297
    {
298
        $this->klein_app->onHttpError('test_num_args_wrapper');
299
300
        $this->assertSame(
301
            '5',
302
            $this->dispatchAndReturnOutput()
303
        );
304
    }
305
306
    public function testOnHttpErrorWithBadCallables()
307
    {
308
        $this->klein_app->onError('this_function_doesnt_exist');
309
310
        $this->assertSame(
311
            '',
312
            $this->dispatchAndReturnOutput()
313
        );
314
    }
315
316 View Code Duplication
    public function testAfterDispatch()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
317
    {
318
        $this->klein_app->afterDispatch(
319
            function ($klein) {
320
                $klein->response()->body('after callbacks!');
321
            }
322
        );
323
324
        $this->klein_app->dispatch(null, null, false);
325
326
        $this->assertSame(
327
            'after callbacks!',
328
            $this->klein_app->response()->body()
329
        );
330
    }
331
332
    public function testAfterDispatchWithMultipleCallbacks()
333
    {
334
        $this->klein_app->afterDispatch(
335
            function ($klein) {
336
                $klein->response()->body('after callbacks!');
337
            }
338
        );
339
340
        $this->klein_app->afterDispatch(
341
            function ($klein) {
342
                $klein->response()->body('whatever');
343
            }
344
        );
345
346
        $this->klein_app->dispatch(null, null, false);
347
348
        $this->assertSame(
349
            'whatever',
350
            $this->klein_app->response()->body()
351
        );
352
    }
353
354 View Code Duplication
    public function testAfterDispatchWithStringCallables()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
355
    {
356
        $this->klein_app->afterDispatch('test_response_edit_wrapper');
357
358
        $this->klein_app->dispatch(null, null, false);
359
360
        $this->assertSame(
361
            'after callbacks!',
362
            $this->klein_app->response()->body()
363
        );
364
    }
365
366
    public function testAfterDispatchWithBadCallables()
367
    {
368
        $this->klein_app->afterDispatch('this_function_doesnt_exist');
369
370
        $this->klein_app->dispatch();
371
372
        $this->expectOutputString(null);
373
    }
374
375
    /**
376
     * @expectedException Klein\Exceptions\UnhandledException
377
     */
378
    public function testAfterDispatchWithCallableThatThrowsException()
379
    {
380
        $this->klein_app->afterDispatch(
381
            function ($klein) {
0 ignored issues
show
Unused Code introduced by
The parameter $klein is not used and could be removed.

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

Loading history...
382
                throw new Exception('testing');
383
            }
384
        );
385
386
        $this->klein_app->dispatch();
387
388
        $this->assertSame(
389
            500,
390
            $this->klein_app->response()->code()
391
        );
392
    }
393
394
    /**
395
     * @expectedException \Klein\Exceptions\UnhandledException
396
     */
397 View Code Duplication
    public function testErrorsWithNoCallbacks()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
398
    {
399
        $this->klein_app->respond(
400
            function ($request, $response, $service) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

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

Loading history...
Unused Code introduced by
The parameter $response is not used and could be removed.

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

Loading history...
Unused Code introduced by
The parameter $service is not used and could be removed.

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

Loading history...
401
                throw new Exception('testing');
402
            }
403
        );
404
405
        $this->klein_app->dispatch();
406
407
        $this->assertSame(
408
            500,
409
            $this->klein_app->response()->code()
410
        );
411
    }
412
413
    public function testSkipThis()
414
    {
415
        try {
416
            $this->klein_app->skipThis();
417
        } catch (Exception $e) {
418
            $this->assertTrue($e instanceof DispatchHaltedException);
419
            $this->assertSame(DispatchHaltedException::SKIP_THIS, $e->getCode());
420
            $this->assertSame(1, $e->getNumberOfSkips());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getNumberOfSkips() does only exist in the following sub-classes of Exception: Klein\Exceptions\DispatchHaltedException. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
421
        }
422
    }
423
424
    public function testSkipNext()
425
    {
426
        $number_of_skips = 3;
427
428
        try {
429
            $this->klein_app->skipNext($number_of_skips);
430
        } catch (Exception $e) {
431
            $this->assertTrue($e instanceof DispatchHaltedException);
432
            $this->assertSame(DispatchHaltedException::SKIP_NEXT, $e->getCode());
433
            $this->assertSame($number_of_skips, $e->getNumberOfSkips());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getNumberOfSkips() does only exist in the following sub-classes of Exception: Klein\Exceptions\DispatchHaltedException. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
434
        }
435
    }
436
437
    public function testSkipRemaining()
438
    {
439
        try {
440
            $this->klein_app->skipRemaining();
441
        } catch (Exception $e) {
442
            $this->assertTrue($e instanceof DispatchHaltedException);
443
            $this->assertSame(DispatchHaltedException::SKIP_REMAINING, $e->getCode());
444
        }
445
    }
446
447
    public function testAbort()
448
    {
449
        $test_code = 503;
450
451
        $this->klein_app->respond(
452
            function ($a, $b, $c, $d, $klein_app) use ($test_code) {
453
                $klein_app->abort($test_code);
454
            }
455
        );
456
457
        try {
458
            $this->klein_app->dispatch();
459
        } catch (Exception $e) {
460
            $this->assertTrue($e instanceof DispatchHaltedException);
461
            $this->assertSame(DispatchHaltedException::SKIP_REMAINING, $e->getCode());
462
        }
463
464
        $this->assertSame($test_code, $this->klein_app->response()->code());
465
        $this->assertTrue($this->klein_app->response()->isLocked());
466
    }
467
468 View Code Duplication
    public function testOptions()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
469
    {
470
        $route = $this->klein_app->options($this->getTestCallable());
471
472
        $this->assertNotNull($route);
473
        $this->assertTrue($route instanceof Route);
474
        $this->assertSame('OPTIONS', $route->getMethod());
475
    }
476
477 View Code Duplication
    public function testHead()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
478
    {
479
        $route = $this->klein_app->head($this->getTestCallable());
480
481
        $this->assertNotNull($route);
482
        $this->assertTrue($route instanceof Route);
483
        $this->assertSame('HEAD', $route->getMethod());
484
    }
485
486 View Code Duplication
    public function testGet()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
487
    {
488
        $route = $this->klein_app->get($this->getTestCallable());
489
490
        $this->assertNotNull($route);
491
        $this->assertTrue($route instanceof Route);
492
        $this->assertSame('GET', $route->getMethod());
493
    }
494
495 View Code Duplication
    public function testPost()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
496
    {
497
        $route = $this->klein_app->post($this->getTestCallable());
498
499
        $this->assertNotNull($route);
500
        $this->assertTrue($route instanceof Route);
501
        $this->assertSame('POST', $route->getMethod());
502
    }
503
504 View Code Duplication
    public function testPut()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
505
    {
506
        $route = $this->klein_app->put($this->getTestCallable());
507
508
        $this->assertNotNull($route);
509
        $this->assertTrue($route instanceof Route);
510
        $this->assertSame('PUT', $route->getMethod());
511
    }
512
513 View Code Duplication
    public function testDelete()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
514
    {
515
        $route = $this->klein_app->delete($this->getTestCallable());
516
517
        $this->assertNotNull($route);
518
        $this->assertTrue($route instanceof Route);
519
        $this->assertSame('DELETE', $route->getMethod());
520
    }
521
522 View Code Duplication
    public function testPatch()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
523
    {
524
        $route = $this->klein_app->patch($this->getTestCallable());
525
526
        $this->assertNotNull($route);
527
        $this->assertTrue($route instanceof Route);
528
        $this->assertSame('PATCH', $route->getMethod());
529
    }
530
}
531