1
|
|
|
<?php declare(strict_types = 1); |
2
|
|
|
/* |
3
|
|
|
* This file is part of the KleijnWeb\SwaggerBundle package. |
4
|
|
|
* |
5
|
|
|
* For the full copyright and license information, please view the LICENSE |
6
|
|
|
* file that was distributed with this source code. |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace KleijnWeb\SwaggerBundle\Tests\Routing; |
10
|
|
|
|
11
|
|
|
use KleijnWeb\PhpApi\Descriptions\Description\Description; |
12
|
|
|
use KleijnWeb\PhpApi\Descriptions\Description\Operation; |
13
|
|
|
use KleijnWeb\PhpApi\Descriptions\Description\Parameter; |
14
|
|
|
use KleijnWeb\PhpApi\Descriptions\Description\Path; |
15
|
|
|
use KleijnWeb\PhpApi\Descriptions\Description\Repository; |
16
|
|
|
use KleijnWeb\PhpApi\Descriptions\Description\Schema\ScalarSchema; |
17
|
|
|
use KleijnWeb\PhpApi\Descriptions\Description\Schema\Schema; |
18
|
|
|
use KleijnWeb\SwaggerBundle\Routing\OpenApiRouteLoader; |
19
|
|
|
use Symfony\Component\Routing\Route; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* @author John Kleijn <[email protected]> |
23
|
|
|
*/ |
24
|
|
|
class OpenApiRouteLoaderTest extends \PHPUnit_Framework_TestCase |
25
|
|
|
{ |
26
|
|
|
const DOCUMENT_PATH = '/totally/non-existent/path'; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @var \PHPUnit_Framework_MockObject_MockObject |
30
|
|
|
*/ |
31
|
|
|
private $repositoryMock; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @var \PHPUnit_Framework_MockObject_MockObject |
35
|
|
|
*/ |
36
|
|
|
private $decriptionMock; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var OpenApiRouteLoader |
40
|
|
|
*/ |
41
|
|
|
private $loader; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Create mocks |
45
|
|
|
*/ |
46
|
|
View Code Duplication |
protected function setUp() |
|
|
|
|
47
|
|
|
{ |
48
|
|
|
$this->decriptionMock = $this |
49
|
|
|
->getMockBuilder(Description::class) |
50
|
|
|
->disableOriginalConstructor() |
51
|
|
|
->getMock(); |
52
|
|
|
|
53
|
|
|
/** @var Repository $repository */ |
54
|
|
|
$this->repositoryMock = $repository = $this |
55
|
|
|
->getMockBuilder(Repository::class) |
56
|
|
|
->disableOriginalConstructor() |
57
|
|
|
->getMock(); |
58
|
|
|
|
59
|
|
|
$this->repositoryMock |
60
|
|
|
->expects($this->any()) |
61
|
|
|
->method('get') |
62
|
|
|
->willReturn($this->decriptionMock); |
63
|
|
|
|
64
|
|
|
$this->loader = new OpenApiRouteLoader($repository); |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* @test |
69
|
|
|
*/ |
70
|
|
|
public function supportSwaggerAsRouteTypeOnly() |
71
|
|
|
{ |
72
|
|
|
$this->assertFalse($this->loader->supports('/a/b/c')); |
73
|
|
|
$this->assertTrue($this->loader->supports('/a/b/c', 'swagger')); |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @test |
78
|
|
|
*/ |
79
|
|
View Code Duplication |
public function canLoadMultipleDocuments() |
|
|
|
|
80
|
|
|
{ |
81
|
|
|
$this->decriptionMock |
82
|
|
|
->expects($this->any()) |
83
|
|
|
->method('getPaths') |
84
|
|
|
->willReturn([]); |
85
|
|
|
|
86
|
|
|
$this->loader->load(self::DOCUMENT_PATH); |
87
|
|
|
$this->loader->load(self::DOCUMENT_PATH.'2'); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* @test |
92
|
|
|
*/ |
93
|
|
|
public function loadingMultipleDocumentsWillPreventRouteKeyCollisions() |
94
|
|
|
{ |
95
|
|
|
$this->decriptionMock |
96
|
|
|
->expects($this->any()) |
97
|
|
|
->method('getPaths') |
98
|
|
|
->willReturn( |
99
|
|
|
[ |
100
|
|
|
new Path('/a', [new Operation('', '/a', 'get')]), |
101
|
|
|
] |
102
|
|
|
); |
103
|
|
|
|
104
|
|
|
$routes1 = $this->loader->load(self::DOCUMENT_PATH); |
105
|
|
|
$routes2 = $this->loader->load(self::DOCUMENT_PATH.'2'); |
106
|
|
|
$this->assertSame(count($routes1), count(array_diff_key($routes1->all(), $routes2->all()))); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* @test |
111
|
|
|
* @expectedException \RuntimeException |
112
|
|
|
*/ |
113
|
|
View Code Duplication |
public function cannotLoadSameDocumentMoreThanOnce() |
|
|
|
|
114
|
|
|
{ |
115
|
|
|
$this->decriptionMock |
116
|
|
|
->expects($this->any()) |
117
|
|
|
->method('getPaths') |
118
|
|
|
->willReturn([]); |
119
|
|
|
|
120
|
|
|
$this->loader->load(self::DOCUMENT_PATH); |
121
|
|
|
$this->loader->load(self::DOCUMENT_PATH); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* @test |
126
|
|
|
*/ |
127
|
|
View Code Duplication |
public function willReturnRouteCollection() |
|
|
|
|
128
|
|
|
{ |
129
|
|
|
$this->decriptionMock |
130
|
|
|
->expects($this->any()) |
131
|
|
|
->method('getPaths') |
132
|
|
|
->willReturn([]); |
133
|
|
|
|
134
|
|
|
$routes = $this->loader->load(self::DOCUMENT_PATH); |
135
|
|
|
$this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $routes); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* @test |
140
|
|
|
*/ |
141
|
|
View Code Duplication |
public function routeCollectionWillContainOneRouteForEveryPathAndMethod() |
|
|
|
|
142
|
|
|
{ |
143
|
|
|
$this->decriptionMock |
144
|
|
|
->expects($this->any()) |
145
|
|
|
->method('getPaths') |
146
|
|
|
->willReturn( |
147
|
|
|
[ |
148
|
|
|
new Path('/a', [new Operation(uniqid(), '/a', 'get'), new Operation(uniqid(), '/a', 'post')]), |
149
|
|
|
new Path('/b', [new Operation(uniqid(), '/b', 'get')]), |
150
|
|
|
] |
151
|
|
|
); |
152
|
|
|
|
153
|
|
|
$routes = $this->loader->load(self::DOCUMENT_PATH); |
154
|
|
|
|
155
|
|
|
$this->assertCount(3, $routes); |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* @test |
160
|
|
|
*/ |
161
|
|
|
public function shouldCreateRoutesWithTheCorrectHttpSchemes() |
162
|
|
|
{ |
163
|
|
|
$this->decriptionMock |
164
|
|
|
->expects($this->any()) |
165
|
|
|
->method('getPaths') |
166
|
|
|
->willReturn([ |
167
|
|
|
new Path('/a', [ |
168
|
|
|
new Operation(uniqid(), '/a', 'get'), |
169
|
|
|
new Operation(uniqid(), '/a', 'post'), |
170
|
|
|
]), |
171
|
|
|
]); |
172
|
|
|
|
173
|
|
|
$this->decriptionMock |
174
|
|
|
->expects($this->any()) |
175
|
|
|
->method('getSchemes') |
176
|
|
|
->willReturn(['https', 'http']); |
177
|
|
|
|
178
|
|
|
$routes = $this->loader->load(self::DOCUMENT_PATH); |
179
|
|
|
|
180
|
|
|
$this->assertCount(2, $routes); |
181
|
|
|
|
182
|
|
|
foreach ($routes as $route) { |
183
|
|
|
$this->assertEquals(['https', 'http'], $route->getSchemes()); |
184
|
|
|
} |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* @test |
189
|
|
|
*/ |
190
|
|
View Code Duplication |
public function routeCollectionWillIncludeSeparateRoutesForSubPaths() |
|
|
|
|
191
|
|
|
{ |
192
|
|
|
$this->decriptionMock |
193
|
|
|
->expects($this->any()) |
194
|
|
|
->method('getPaths') |
195
|
|
|
->willReturn( |
196
|
|
|
[ |
197
|
|
|
new Path('/a', [new Operation(uniqid(), '/a', 'get')]), |
198
|
|
|
new Path('/a/b', [new Operation(uniqid(), '/a/b', 'get')]), |
199
|
|
|
new Path('/a/b/c', [new Operation(uniqid(), '/a/b/c', 'get')]), |
200
|
|
|
] |
201
|
|
|
); |
202
|
|
|
|
203
|
|
|
|
204
|
|
|
$routes = $this->loader->load(self::DOCUMENT_PATH); |
205
|
|
|
|
206
|
|
|
$this->assertCount(3, $routes); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* @test |
211
|
|
|
*/ |
212
|
|
|
public function canUseOperationIdAsControllerKey() |
213
|
|
|
{ |
214
|
|
|
$expected = 'my.controller.key:methodName'; |
215
|
|
|
|
216
|
|
|
$this->decriptionMock |
217
|
|
|
->expects($this->any()) |
218
|
|
|
->method('getPaths') |
219
|
|
|
->willReturn([ |
220
|
|
|
new Path( |
221
|
|
|
'/a', |
222
|
|
|
[new Operation('/a:get', '/a', 'get'), new Operation($expected, '/a', 'post'),] |
223
|
|
|
), |
224
|
|
|
new Path('/b', [new Operation('/b:get', '/b', 'get')]), |
225
|
|
|
]); |
226
|
|
|
|
227
|
|
|
$routes = $this->loader->load(self::DOCUMENT_PATH); |
228
|
|
|
|
229
|
|
|
$actual = $routes->get('swagger.path.a.methodName'); |
230
|
|
|
$this->assertNotNull($actual); |
231
|
|
|
$this->assertSame($expected, $actual->getDefault('_controller')); |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* @test |
236
|
|
|
*/ |
237
|
|
|
public function canUseXRouterMethodToOverrideMethod() |
238
|
|
|
{ |
239
|
|
|
$extensions = ['router-controller-method' => 'myMethodName']; |
240
|
|
|
|
241
|
|
|
$this->decriptionMock |
242
|
|
|
->expects($this->any()) |
243
|
|
|
->method('getPaths') |
244
|
|
|
->willReturn([ |
245
|
|
|
new Path( |
246
|
|
|
'/a', |
247
|
|
|
[ |
248
|
|
|
new Operation('/a:get', '/a', 'get'), |
249
|
|
|
new Operation('/a:post', '/a', 'post', [], null, [], $extensions), |
250
|
|
|
] |
251
|
|
|
), |
252
|
|
|
new Path('/b', [new Operation('/b:get', '/b', 'get')]), |
253
|
|
|
]); |
254
|
|
|
|
255
|
|
|
$routes = $this->loader->load(self::DOCUMENT_PATH); |
256
|
|
|
|
257
|
|
|
$actual = $routes->get('swagger.path.a.myMethodName'); |
258
|
|
|
$this->assertNotNull($actual); |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* @test |
263
|
|
|
*/ |
264
|
|
|
public function canUseXRouterControllerForDiKeyInOperation() |
265
|
|
|
{ |
266
|
|
|
$diKey = 'my.x_router.controller'; |
267
|
|
|
$expected = "$diKey:post"; |
268
|
|
|
$extensions = ['router-controller' => $diKey]; |
269
|
|
|
$this->decriptionMock |
270
|
|
|
->expects($this->any()) |
271
|
|
|
->method('getPaths') |
272
|
|
|
->willReturn([ |
273
|
|
|
new Path( |
274
|
|
|
'/a', |
275
|
|
|
[ |
276
|
|
|
new Operation('/a:get', '/a', 'get'), |
277
|
|
|
new Operation('/a:post', '/a', 'post', [], null, [], $extensions), |
278
|
|
|
] |
279
|
|
|
), |
280
|
|
|
new Path('/b', [new Operation('/b:get', '/b', 'get')]), |
281
|
|
|
]); |
282
|
|
|
|
283
|
|
|
$routes = $this->loader->load(self::DOCUMENT_PATH); |
284
|
|
|
|
285
|
|
|
$actual = $routes->get('swagger.path.a.post'); |
286
|
|
|
$this->assertNotNull($actual); |
287
|
|
|
$this->assertSame($expected, $actual->getDefault('_controller')); |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* @test |
292
|
|
|
*/ |
293
|
|
View Code Duplication |
public function canUseXRouterControllerForDiKeyInPath() |
|
|
|
|
294
|
|
|
{ |
295
|
|
|
$diKey = 'my.x_router.controller'; |
296
|
|
|
$expected = "$diKey:post"; |
297
|
|
|
$this->decriptionMock |
298
|
|
|
->expects($this->any()) |
299
|
|
|
->method('getPaths') |
300
|
|
|
->willReturn([new Path('/a', [new Operation('/a:post', '/a', 'post')])]); |
301
|
|
|
|
302
|
|
|
$this->decriptionMock |
303
|
|
|
->expects($this->atLeast(1)) |
304
|
|
|
->method('getExtension') |
305
|
|
|
->willReturnCallback( |
306
|
|
|
function (string $name) use ($diKey) { |
307
|
|
|
return $name == 'router-controller' ? $diKey : null; |
308
|
|
|
} |
309
|
|
|
); |
310
|
|
|
|
311
|
|
|
$routes = $this->loader->load(self::DOCUMENT_PATH); |
312
|
|
|
|
313
|
|
|
$actual = $routes->get('swagger.path.a.post'); |
314
|
|
|
$this->assertNotNull($actual); |
315
|
|
|
$this->assertSame($expected, $actual->getDefault('_controller')); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* @test |
320
|
|
|
*/ |
321
|
|
View Code Duplication |
public function canUseXRouterForDiKeyInPath() |
|
|
|
|
322
|
|
|
{ |
323
|
|
|
$router = 'my.x_router'; |
324
|
|
|
$expected = "$router.a:post"; |
325
|
|
|
$this->decriptionMock |
326
|
|
|
->expects($this->any()) |
327
|
|
|
->method('getPaths') |
328
|
|
|
->willReturn([new Path('/a', [new Operation('/a:post', '/a', 'post')])]); |
329
|
|
|
|
330
|
|
|
$this->decriptionMock |
331
|
|
|
->expects($this->atLeast(1)) |
332
|
|
|
->method('getExtension') |
333
|
|
|
->willReturnCallback( |
334
|
|
|
function (string $name) use ($router) { |
335
|
|
|
return $name == 'router' ? $router : null; |
336
|
|
|
} |
337
|
|
|
); |
338
|
|
|
|
339
|
|
|
$routes = $this->loader->load(self::DOCUMENT_PATH); |
340
|
|
|
|
341
|
|
|
$actual = $routes->get('swagger.path.a.post'); |
342
|
|
|
$this->assertNotNull($actual); |
343
|
|
|
$this->assertSame($expected, $actual->getDefault('_controller')); |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* @test |
348
|
|
|
*/ |
349
|
|
|
public function routeCollectionWillIncludeSeparateRoutesForSubPathMethodCombinations() |
350
|
|
|
{ |
351
|
|
|
$this->decriptionMock |
352
|
|
|
->expects($this->any()) |
353
|
|
|
->method('getPaths') |
354
|
|
|
->willReturn([ |
355
|
|
|
new Path( |
356
|
|
|
'/a', |
357
|
|
|
[new Operation('/a:get', '/a', 'get')] |
358
|
|
|
), |
359
|
|
|
new Path( |
360
|
|
|
'/a/b', |
361
|
|
|
[new Operation('/a/b:get', '/a/b', 'get'), new Operation('/a/b:post', '/a/b', 'post')] |
362
|
|
|
), |
363
|
|
|
new Path('/a/b/c', [new Operation('/a/b/c:get', '/a/b/c', 'get')]), |
364
|
|
|
]); |
365
|
|
|
|
366
|
|
|
$routes = $this->loader->load(self::DOCUMENT_PATH); |
367
|
|
|
|
368
|
|
|
$this->assertCount(4, $routes); |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* @test |
373
|
|
|
*/ |
374
|
|
|
public function routeCollectionWillContainPathFromDescription() |
375
|
|
|
{ |
376
|
|
|
$paths = [ |
377
|
|
|
new Path('/a', [new Operation('/a:get', '/a', 'get'),]), |
378
|
|
|
new Path('/a/b', [new Operation('/a/b:get', '/a/b', 'get'),]), |
379
|
|
|
new Path('/a/b/c', [new Operation('/a/b/c:get', '/a/b/c', 'get')]), |
380
|
|
|
new Path('/d/f/g', [new Operation('/d/f/g:get', '/d/f/g', 'get')]), |
381
|
|
|
new Path('/1/2/3', [new Operation('/1/2/3:get', '/1/2/3', 'get')]), |
382
|
|
|
new Path('/foo/{bar}/{blah}', [new Operation('/foo/{bar}/{blah}:get', '/foo/{bar}/{blah}', 'get')]), |
383
|
|
|
new Path('/z', [new Operation('/z:get', '/z', 'get'),]), |
384
|
|
|
]; |
385
|
|
|
|
386
|
|
|
$this->decriptionMock |
387
|
|
|
->expects($this->any()) |
388
|
|
|
->method('getPaths') |
389
|
|
|
->willReturn($paths); |
390
|
|
|
|
391
|
|
|
$routes = $this->loader->load(self::DOCUMENT_PATH); |
392
|
|
|
|
393
|
|
|
$descriptionPaths = array_map( |
394
|
|
|
function (Path $path) { |
395
|
|
|
return $path->getPath(); |
396
|
|
|
}, |
397
|
|
|
$paths |
398
|
|
|
); |
399
|
|
|
sort($descriptionPaths); |
400
|
|
|
|
401
|
|
|
$routePaths = array_map( |
402
|
|
|
function (Route $route) { |
403
|
|
|
return $route->getPath(); |
404
|
|
|
}, |
405
|
|
|
$routes->getIterator()->getArrayCopy() |
406
|
|
|
); |
407
|
|
|
|
408
|
|
|
sort($routePaths); |
409
|
|
|
$this->assertSame($descriptionPaths, $routePaths); |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
/** |
413
|
|
|
* @test |
414
|
|
|
*/ |
415
|
|
|
public function willAddRequirementsForIntegerPathParams() |
416
|
|
|
{ |
417
|
|
|
$parameter = new Parameter( |
418
|
|
|
'foo', |
419
|
|
|
true, |
420
|
|
|
new ScalarSchema((object)['type' => Schema::TYPE_INT]), |
421
|
|
|
Parameter::IN_PATH |
422
|
|
|
); |
423
|
|
|
|
424
|
|
|
$this->decriptionMock |
425
|
|
|
->expects($this->any()) |
426
|
|
|
->method('getPaths') |
427
|
|
|
->willReturn([new Path('/a', [new Operation('/a:get', '/a', 'get', [$parameter])])]); |
428
|
|
|
|
429
|
|
|
$routes = $this->loader->load(self::DOCUMENT_PATH); |
430
|
|
|
$actual = $routes->get('swagger.path.a.get'); |
431
|
|
|
$this->assertNotNull($actual); |
432
|
|
|
$requirements = $actual->getRequirements(); |
433
|
|
|
$this->assertNotNull($requirements); |
434
|
|
|
|
435
|
|
|
$this->assertSame($requirements['foo'], '\d+'); |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
/** |
439
|
|
|
* @test |
440
|
|
|
*/ |
441
|
|
|
public function willAddRequirementsForStringPatternParams() |
442
|
|
|
{ |
443
|
|
|
$expected = '\d{2}hello'; |
444
|
|
|
$parameter = new Parameter( |
445
|
|
|
'aString', |
446
|
|
|
true, |
447
|
|
|
new ScalarSchema( |
448
|
|
|
(object)[ |
449
|
|
|
'type' => Schema::TYPE_STRING, |
450
|
|
|
'pattern' => $expected, |
451
|
|
|
] |
452
|
|
|
), |
453
|
|
|
Parameter::IN_PATH |
454
|
|
|
); |
455
|
|
|
|
456
|
|
|
$this->decriptionMock |
457
|
|
|
->expects($this->any()) |
458
|
|
|
->method('getPaths') |
459
|
|
|
->willReturn([new Path('/a', [new Operation('/a:get', '/a', 'get', [$parameter])])]); |
460
|
|
|
|
461
|
|
|
$routes = $this->loader->load(self::DOCUMENT_PATH); |
462
|
|
|
$actual = $routes->get('swagger.path.a.get'); |
463
|
|
|
$this->assertNotNull($actual); |
464
|
|
|
$requirements = $actual->getRequirements(); |
465
|
|
|
$this->assertNotNull($requirements); |
466
|
|
|
|
467
|
|
|
$this->assertSame($expected, $requirements['aString']); |
468
|
|
|
} |
469
|
|
|
|
470
|
|
|
/** |
471
|
|
|
* @test |
472
|
|
|
*/ |
473
|
|
|
public function willAddRequirementsForStringEnumParams() |
474
|
|
|
{ |
475
|
|
|
$enum = ['a', 'b', 'c']; |
476
|
|
|
$expected = '(a|b|c)'; |
477
|
|
|
$parameter = new Parameter( |
478
|
|
|
'aString', |
479
|
|
|
true, |
480
|
|
|
new ScalarSchema( |
481
|
|
|
(object)[ |
482
|
|
|
'type' => Schema::TYPE_STRING, |
483
|
|
|
'enum' => $enum, |
484
|
|
|
] |
485
|
|
|
), |
486
|
|
|
Parameter::IN_PATH |
487
|
|
|
); |
488
|
|
|
|
489
|
|
|
$this->decriptionMock |
490
|
|
|
->expects($this->any()) |
491
|
|
|
->method('getPaths') |
492
|
|
|
->willReturn([new Path('/a', [new Operation('/a:get', '/a', 'get', [$parameter])])]); |
493
|
|
|
|
494
|
|
|
$routes = $this->loader->load(self::DOCUMENT_PATH); |
495
|
|
|
$actual = $routes->get('swagger.path.a.get'); |
496
|
|
|
$this->assertNotNull($actual); |
497
|
|
|
$requirements = $actual->getRequirements(); |
498
|
|
|
$this->assertNotNull($requirements); |
499
|
|
|
|
500
|
|
|
$this->assertSame($expected, $requirements['aString']); |
501
|
|
|
} |
502
|
|
|
} |
503
|
|
|
|
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.