c11b23d20df2960d13cfbf498619882988f4ce83
[yaffs-website] / web / core / tests / Drupal / KernelTests / Core / Routing / RouteProviderTest.php
1 <?php
2
3 /**
4  * @file
5  * Contains \Drupal\KernelTests\Core\Routing\RouteProviderTest.
6  */
7
8 namespace Drupal\KernelTests\Core\Routing;
9
10 use Drupal\Component\Utility\Unicode;
11 use Drupal\Core\Cache\MemoryBackend;
12 use Drupal\Core\Database\Database;
13 use Drupal\Core\DependencyInjection\ContainerBuilder;
14 use Drupal\Core\KeyValueStore\KeyValueMemoryFactory;
15 use Drupal\Core\Lock\NullLockBackend;
16 use Drupal\Core\Path\CurrentPathStack;
17 use Drupal\Core\Routing\MatcherDumper;
18 use Drupal\Core\Routing\RouteProvider;
19 use Drupal\Core\State\State;
20 use Drupal\KernelTests\KernelTestBase;
21 use Drupal\language\Entity\ConfigurableLanguage;
22 use Drupal\Tests\Core\Routing\RoutingFixtures;
23 use Symfony\Component\HttpFoundation\Request;
24 use Symfony\Component\HttpFoundation\RequestStack;
25 use Symfony\Component\Routing\Exception\ResourceNotFoundException;
26 use Symfony\Component\Routing\Exception\RouteNotFoundException;
27 use Symfony\Component\Routing\Route;
28 use Symfony\Component\Routing\RouteCollection;
29
30 /**
31  * Confirm that the default route provider is working correctly.
32  *
33  * @group Routing
34  */
35 class RouteProviderTest extends KernelTestBase {
36
37   /**
38    * Modules to enable.
39    */
40   public static $modules = ['url_alter_test', 'system', 'language'];
41
42   /**
43    * A collection of shared fixture data for tests.
44    *
45    * @var \Drupal\Tests\Core\Routing\RoutingFixtures
46    */
47   protected $fixtures;
48
49   /**
50    * The state.
51    *
52    * @var \Drupal\Core\State\StateInterface
53    */
54   protected $state;
55
56   /**
57    * The current path.
58    *
59    * @var \Drupal\Core\Path\CurrentPathStack
60    */
61   protected $currentPath;
62
63   /**
64    * The cache backend.
65    *
66    * @var \Drupal\Core\Cache\MemoryBackend
67    */
68   protected $cache;
69
70   /**
71    * The inbound path processor.
72    *
73    * @var \Drupal\Core\PathProcessor\InboundPathProcessorInterface
74    */
75   protected $pathProcessor;
76
77   /**
78    * The cache tags invalidator.
79    *
80    * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface
81    */
82   protected $cacheTagsInvalidator;
83
84   protected function setUp() {
85     parent::setUp();
86     $this->fixtures = new RoutingFixtures();
87     $this->state = new State(new KeyValueMemoryFactory(), new MemoryBackend('test'), new NullLockBackend());
88     $this->currentPath = new CurrentPathStack(new RequestStack());
89     $this->cache = new MemoryBackend();
90     $this->pathProcessor = \Drupal::service('path_processor_manager');
91     $this->cacheTagsInvalidator = \Drupal::service('cache_tags.invalidator');
92   }
93
94   /**
95    * {@inheritdoc}
96    */
97   public function register(ContainerBuilder $container) {
98     parent::register($container);
99
100     // Readd the incoming path alias for these tests.
101     if ($container->hasDefinition('path_processor_alias')) {
102       $definition = $container->getDefinition('path_processor_alias');
103       $definition->addTag('path_processor_inbound');
104     }
105   }
106
107   protected function tearDown() {
108     $this->fixtures->dropTables(Database::getConnection());
109
110     parent::tearDown();
111   }
112
113   /**
114    * Confirms that the correct candidate outlines are generated.
115    */
116   public function testCandidateOutlines() {
117
118     $connection = Database::getConnection();
119     $provider = new TestRouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->pathProcessor, $this->cacheTagsInvalidator, 'test_routes');
120
121     $parts = ['node', '5', 'edit'];
122
123     $candidates = $provider->getCandidateOutlines($parts);
124
125     $candidates = array_flip($candidates);
126
127     $this->assertTrue(count($candidates) == 7, 'Correct number of candidates found');
128     $this->assertTrue(array_key_exists('/node/5/edit', $candidates), 'First candidate found.');
129     $this->assertTrue(array_key_exists('/node/5/%', $candidates), 'Second candidate found.');
130     $this->assertTrue(array_key_exists('/node/%/edit', $candidates), 'Third candidate found.');
131     $this->assertTrue(array_key_exists('/node/%/%', $candidates), 'Fourth candidate found.');
132     $this->assertTrue(array_key_exists('/node/5', $candidates), 'Fifth candidate found.');
133     $this->assertTrue(array_key_exists('/node/%', $candidates), 'Sixth candidate found.');
134     $this->assertTrue(array_key_exists('/node', $candidates), 'Seventh candidate found.');
135   }
136
137   /**
138    * Don't fail when given an empty path.
139    */
140   public function testEmptyPathCandidatesOutlines() {
141     $provider = new TestRouteProvider(Database::getConnection(), $this->state, $this->currentPath, $this->cache, $this->pathProcessor, $this->cacheTagsInvalidator, 'test_routes');
142     $candidates = $provider->getCandidateOutlines([]);
143     $this->assertEqual(count($candidates), 0, 'Empty parts should return no candidates.');
144   }
145
146   /**
147    * Confirms that we can find routes with the exact incoming path.
148    */
149   public function testExactPathMatch() {
150     $connection = Database::getConnection();
151     $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->pathProcessor, $this->cacheTagsInvalidator, 'test_routes');
152
153     $this->fixtures->createTables($connection);
154
155     $dumper = new MatcherDumper($connection, $this->state, 'test_routes');
156     $dumper->addRoutes($this->fixtures->sampleRouteCollection());
157     $dumper->dump();
158
159     $path = '/path/one';
160
161     $request = Request::create($path, 'GET');
162
163     $routes = $provider->getRouteCollectionForRequest($request);
164
165     foreach ($routes as $route) {
166       $this->assertEqual($route->getPath(), $path, 'Found path has correct pattern');
167     }
168   }
169
170   /**
171    * Confirms that we can find routes whose pattern would match the request.
172    */
173   public function testOutlinePathMatch() {
174     $connection = Database::getConnection();
175     $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->pathProcessor, $this->cacheTagsInvalidator, 'test_routes');
176
177     $this->fixtures->createTables($connection);
178
179     $dumper = new MatcherDumper($connection, $this->state, 'test_routes');
180     $dumper->addRoutes($this->fixtures->complexRouteCollection());
181     $dumper->dump();
182
183     $path = '/path/1/one';
184
185     $request = Request::create($path, 'GET');
186
187     $routes = $provider->getRouteCollectionForRequest($request);
188
189     // All of the matching paths have the correct pattern.
190     foreach ($routes as $route) {
191       $this->assertEqual($route->compile()->getPatternOutline(), '/path/%/one', 'Found path has correct pattern');
192     }
193
194     $this->assertEqual(count($routes), 2, 'The correct number of routes was found.');
195     $this->assertNotNull($routes->get('route_a'), 'The first matching route was found.');
196     $this->assertNotNull($routes->get('route_b'), 'The second matching route was not found.');
197   }
198
199   /**
200    * Data provider for testMixedCasePaths()
201    */
202   public function providerMixedCaseRoutePaths() {
203     return [
204       ['/path/one', 'route_a'],
205       ['/path/two', NULL],
206       ['/PATH/one', 'route_a'],
207       ['/path/2/one', 'route_b', 'PUT'],
208       ['/paTH/3/one', 'route_b', 'PUT'],
209       // There should be no lower case of a Hebrew letter.
210       ['/somewhere/4/over/the/קainbow', 'route_c'],
211       ['/Somewhere/5/over/the/קainboW', 'route_c'],
212       ['/another/llama/aboUT/22', 'route_d'],
213       ['/another/llama/about/22', 'route_d'],
214       ['/place/meΦω', 'route_e', 'HEAD'],
215       ['/place/meφΩ', 'route_e', 'HEAD'],
216     ];
217   }
218
219   /**
220    * Confirms that we find routes using a case-insensitive path match.
221    *
222    * @dataProvider providerMixedCaseRoutePaths
223    */
224   public function testMixedCasePaths($path, $expected_route_name, $method = 'GET') {
225     // The case-insensitive behavior for higher UTF-8 characters depends on
226     // \Drupal\Component\Utility\Unicode::strtolower() using mb_strtolower()
227     // but kernel tests do not currently run the check that enables it.
228     // @todo remove this when https://www.drupal.org/node/2849669 is fixed.
229     Unicode::check();
230
231     $connection = Database::getConnection();
232     $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->pathProcessor, $this->cacheTagsInvalidator, 'test_routes');
233
234     $this->fixtures->createTables($connection);
235
236     $dumper = new MatcherDumper($connection, $this->state, 'test_routes');
237     $dumper->addRoutes($this->fixtures->mixedCaseRouteCollection());
238     $dumper->dump();
239
240     $request = Request::create($path, $method);
241
242     $routes = $provider->getRouteCollectionForRequest($request);
243
244     if ($expected_route_name) {
245       $this->assertEquals(1, count($routes), 'The correct number of routes was found.');
246       $this->assertNotNull($routes->get($expected_route_name), 'The first matching route was found.');
247     }
248     else {
249       $this->assertEquals(0, count($routes), 'No routes matched.');
250     }
251   }
252
253   /**
254    * Data provider for testMixedCasePaths()
255    */
256   public function providerDuplicateRoutePaths() {
257     // When matching routes with the same fit the route with the lowest-sorting
258     // name should end up first in the resulting route collection.
259     return [
260       ['/path/one', 3, 'route_a'],
261       ['/PATH/one', 3, 'route_a'],
262       ['/path/two', 1, 'route_d'],
263       ['/PATH/three', 0],
264       ['/place/meΦω', 2, 'route_e'],
265       ['/placE/meφΩ', 2, 'route_e'],
266     ];
267   }
268
269   /**
270    * Confirms that we find all routes with the same path.
271    *
272    * @dataProvider providerDuplicateRoutePaths
273    */
274   public function testDuplicateRoutePaths($path, $number, $expected_route_name = NULL) {
275
276     // The case-insensitive behavior for higher UTF-8 characters depends on
277     // \Drupal\Component\Utility\Unicode::strtolower() using mb_strtolower()
278     // but kernel tests do not currently run the check that enables it.
279     // @todo remove this when https://www.drupal.org/node/2849669 is fixed.
280     Unicode::check();
281
282     $connection = Database::getConnection();
283     $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->pathProcessor, $this->cacheTagsInvalidator, 'test_routes');
284
285     $this->fixtures->createTables($connection);
286
287     $dumper = new MatcherDumper($connection, $this->state, 'test_routes');
288     $dumper->addRoutes($this->fixtures->duplicatePathsRouteCollection());
289     $dumper->dump();
290
291     $request = Request::create($path);
292     $routes = $provider->getRouteCollectionForRequest($request);
293     $this->assertEquals($number, count($routes), 'The correct number of routes was found.');
294     if ($expected_route_name) {
295       $route_name = key(current($routes));
296       $this->assertEquals($expected_route_name, $route_name, 'The expected route name was found.');
297     }
298   }
299
300   /**
301    * Confirms that a trailing slash on the request does not result in a 404.
302    */
303   public function testOutlinePathMatchTrailingSlash() {
304     $connection = Database::getConnection();
305     $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->pathProcessor, $this->cacheTagsInvalidator, 'test_routes');
306
307     $this->fixtures->createTables($connection);
308
309     $dumper = new MatcherDumper($connection, $this->state, 'test_routes');
310     $dumper->addRoutes($this->fixtures->complexRouteCollection());
311     $dumper->dump();
312
313     $path = '/path/1/one/';
314
315     $request = Request::create($path, 'GET');
316
317     $routes = $provider->getRouteCollectionForRequest($request);
318
319     // All of the matching paths have the correct pattern.
320     foreach ($routes as $route) {
321       $this->assertEqual($route->compile()->getPatternOutline(), '/path/%/one', 'Found path has correct pattern');
322     }
323
324     $this->assertEqual(count($routes), 2, 'The correct number of routes was found.');
325     $this->assertNotNull($routes->get('route_a'), 'The first matching route was found.');
326     $this->assertNotNull($routes->get('route_b'), 'The second matching route was not found.');
327   }
328
329   /**
330    * Confirms that we can find routes whose pattern would match the request.
331    */
332   public function testOutlinePathMatchDefaults() {
333     $connection = Database::getConnection();
334     $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->pathProcessor, $this->cacheTagsInvalidator, 'test_routes');
335
336     $this->fixtures->createTables($connection);
337
338     $collection = new RouteCollection();
339     $collection->add('poink', new Route('/some/path/{value}', [
340       'value' => 'poink',
341     ]));
342
343     $dumper = new MatcherDumper($connection, $this->state, 'test_routes');
344     $dumper->addRoutes($collection);
345     $dumper->dump();
346
347     $path = '/some/path';
348
349     $request = Request::create($path, 'GET');
350
351     try {
352       $routes = $provider->getRouteCollectionForRequest($request);
353
354       // All of the matching paths have the correct pattern.
355       foreach ($routes as $route) {
356         $this->assertEqual($route->compile()->getPatternOutline(), '/some/path', 'Found path has correct pattern');
357       }
358
359       $this->assertEqual(count($routes), 1, 'The correct number of routes was found.');
360       $this->assertNotNull($routes->get('poink'), 'The first matching route was found.');
361     }
362     catch (ResourceNotFoundException $e) {
363       $this->fail('No matching route found with default argument value.');
364     }
365   }
366
367   /**
368    * Confirms that we can find routes whose pattern would match the request.
369    */
370   public function testOutlinePathMatchDefaultsCollision() {
371     $connection = Database::getConnection();
372     $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->pathProcessor, $this->cacheTagsInvalidator, 'test_routes');
373
374     $this->fixtures->createTables($connection);
375
376     $collection = new RouteCollection();
377     $collection->add('poink', new Route('/some/path/{value}', [
378       'value' => 'poink',
379     ]));
380     $collection->add('narf', new Route('/some/path/here'));
381
382     $dumper = new MatcherDumper($connection, $this->state, 'test_routes');
383     $dumper->addRoutes($collection);
384     $dumper->dump();
385
386     $path = '/some/path';
387
388     $request = Request::create($path, 'GET');
389
390     try {
391       $routes = $provider->getRouteCollectionForRequest($request);
392
393       // All of the matching paths have the correct pattern.
394       foreach ($routes as $route) {
395         $this->assertEqual($route->compile()->getPatternOutline(), '/some/path', 'Found path has correct pattern');
396       }
397
398       $this->assertEqual(count($routes), 1, 'The correct number of routes was found.');
399       $this->assertNotNull($routes->get('poink'), 'The first matching route was found.');
400     }
401     catch (ResourceNotFoundException $e) {
402       $this->fail('No matching route found with default argument value.');
403     }
404   }
405
406   /**
407    * Confirms that we can find routes whose pattern would match the request.
408    */
409   public function testOutlinePathMatchDefaultsCollision2() {
410     $connection = Database::getConnection();
411     $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->pathProcessor, $this->cacheTagsInvalidator, 'test_routes');
412
413     $this->fixtures->createTables($connection);
414
415     $collection = new RouteCollection();
416     $collection->add('poink', new Route('/some/path/{value}', [
417       'value' => 'poink',
418     ]));
419     $collection->add('narf', new Route('/some/path/here'));
420     $collection->add('eep', new Route('/something/completely/different'));
421
422     $dumper = new MatcherDumper($connection, $this->state, 'test_routes');
423     $dumper->addRoutes($collection);
424     $dumper->dump();
425
426     $path = '/some/path/here';
427
428     $request = Request::create($path, 'GET');
429
430     try {
431       $routes = $provider->getRouteCollectionForRequest($request);
432       $routes_array = $routes->all();
433
434       $this->assertEqual(count($routes), 2, 'The correct number of routes was found.');
435       $this->assertEqual(['narf', 'poink'], array_keys($routes_array), 'Ensure the fitness was taken into account.');
436       $this->assertNotNull($routes->get('narf'), 'The first matching route was found.');
437       $this->assertNotNull($routes->get('poink'), 'The second matching route was found.');
438       $this->assertNull($routes->get('eep'), 'Non-matching route was not found.');
439     }
440     catch (ResourceNotFoundException $e) {
441       $this->fail('No matching route found with default argument value.');
442     }
443   }
444
445   /**
446    * Confirms that we can find multiple routes that match the request equally.
447    */
448   public function testOutlinePathMatchDefaultsCollision3() {
449     $connection = Database::getConnection();
450     $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->pathProcessor, $this->cacheTagsInvalidator, 'test_routes');
451
452     $this->fixtures->createTables($connection);
453
454     $collection = new RouteCollection();
455     $collection->add('poink', new Route('/some/{value}/path'));
456     // Add a second route matching the same path pattern.
457     $collection->add('poink2', new Route('/some/{object}/path'));
458     $collection->add('narf', new Route('/some/here/path'));
459     $collection->add('eep', new Route('/something/completely/different'));
460
461     $dumper = new MatcherDumper($connection, $this->state, 'test_routes');
462     $dumper->addRoutes($collection);
463     $dumper->dump();
464
465     $path = '/some/over-there/path';
466
467     $request = Request::create($path, 'GET');
468
469     try {
470       $routes = $provider->getRouteCollectionForRequest($request);
471       $routes_array = $routes->all();
472
473       $this->assertEqual(count($routes), 2, 'The correct number of routes was found.');
474       $this->assertEqual(['poink', 'poink2'], array_keys($routes_array), 'Ensure the fitness and name were taken into account in the sort.');
475       $this->assertNotNull($routes->get('poink'), 'The first matching route was found.');
476       $this->assertNotNull($routes->get('poink2'), 'The second matching route was found.');
477       $this->assertNull($routes->get('eep'), 'Non-matching route was not found.');
478     }
479     catch (ResourceNotFoundException $e) {
480       $this->fail('No matching route found with default argument value.');
481     }
482   }
483
484   /**
485    * Tests a route with a 0 as value.
486    */
487   public function testOutlinePathMatchZero() {
488     $connection = Database::getConnection();
489     $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->pathProcessor, $this->cacheTagsInvalidator, 'test_routes');
490
491     $this->fixtures->createTables($connection);
492
493     $collection = new RouteCollection();
494     $collection->add('poink', new Route('/some/path/{value}'));
495
496     $dumper = new MatcherDumper($connection, $this->state, 'test_routes');
497     $dumper->addRoutes($collection);
498     $dumper->dump();
499
500     $path = '/some/path/0';
501
502     $request = Request::create($path, 'GET');
503
504     try {
505       $routes = $provider->getRouteCollectionForRequest($request);
506
507       // All of the matching paths have the correct pattern.
508       foreach ($routes as $route) {
509         $this->assertEqual($route->compile()->getPatternOutline(), '/some/path/%', 'Found path has correct pattern');
510       }
511
512       $this->assertEqual(count($routes), 1, 'The correct number of routes was found.');
513     }
514     catch (ResourceNotFoundException $e) {
515       $this->fail('No matchout route found with 0 as argument value');
516     }
517   }
518
519   /**
520    * Confirms that an exception is thrown when no matching path is found.
521    */
522   public function testOutlinePathNoMatch() {
523     $connection = Database::getConnection();
524     $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->pathProcessor, $this->cacheTagsInvalidator, 'test_routes');
525
526     $this->fixtures->createTables($connection);
527
528     $dumper = new MatcherDumper($connection, $this->state, 'test_routes');
529     $dumper->addRoutes($this->fixtures->complexRouteCollection());
530     $dumper->dump();
531
532     $path = '/no/such/path';
533
534     $request = Request::create($path, 'GET');
535
536     $routes = $provider->getRoutesByPattern($path);
537     $this->assertFalse(count($routes), 'No path found with this pattern.');
538
539     $collection = $provider->getRouteCollectionForRequest($request);
540     $this->assertTrue(count($collection) == 0, 'Empty route collection found with this pattern.');
541   }
542
543   /**
544    * Tests that route caching works.
545    */
546   public function testRouteCaching() {
547     $connection = Database::getConnection();
548     $language_manager = \Drupal::languageManager();
549     $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->pathProcessor, $this->cacheTagsInvalidator, 'test_routes', $language_manager);
550
551     $this->fixtures->createTables($connection);
552
553     $dumper = new MatcherDumper($connection, $this->state, 'test_routes');
554     $dumper->addRoutes($this->fixtures->sampleRouteCollection());
555     $dumper->addRoutes($this->fixtures->complexRouteCollection());
556     $dumper->dump();
557
558     // A simple path.
559     $path = '/path/add/one';
560     $request = Request::create($path, 'GET');
561     $provider->getRouteCollectionForRequest($request);
562
563     $cache = $this->cache->get('route:en:/path/add/one:');
564     $this->assertEqual('/path/add/one', $cache->data['path']);
565     $this->assertEqual([], $cache->data['query']);
566     $this->assertEqual(3, count($cache->data['routes']));
567
568     // A path with query parameters.
569     $path = '/path/add/one?foo=bar';
570     $request = Request::create($path, 'GET');
571     $provider->getRouteCollectionForRequest($request);
572
573     $cache = $this->cache->get('route:en:/path/add/one:foo=bar');
574     $this->assertEqual('/path/add/one', $cache->data['path']);
575     $this->assertEqual(['foo' => 'bar'], $cache->data['query']);
576     $this->assertEqual(3, count($cache->data['routes']));
577
578     // A path with placeholders.
579     $path = '/path/1/one';
580     $request = Request::create($path, 'GET');
581     $provider->getRouteCollectionForRequest($request);
582
583     $cache = $this->cache->get('route:en:/path/1/one:');
584     $this->assertEqual('/path/1/one', $cache->data['path']);
585     $this->assertEqual([], $cache->data['query']);
586     $this->assertEqual(2, count($cache->data['routes']));
587
588     // A path with a path alias.
589     /** @var \Drupal\Core\Path\AliasStorageInterface $path_storage */
590     $path_storage = \Drupal::service('path.alias_storage');
591     $path_storage->save('/path/add/one', '/path/add-one');
592     /** @var \Drupal\Core\Path\AliasManagerInterface $alias_manager */
593     $alias_manager = \Drupal::service('path.alias_manager');
594     $alias_manager->cacheClear();
595
596     $path = '/path/add-one';
597     $request = Request::create($path, 'GET');
598     $provider->getRouteCollectionForRequest($request);
599
600     $cache = $this->cache->get('route:en:/path/add-one:');
601     $this->assertEqual('/path/add/one', $cache->data['path']);
602     $this->assertEqual([], $cache->data['query']);
603     $this->assertEqual(3, count($cache->data['routes']));
604
605     // Test with a different current language by switching out the default
606     // language.
607     $swiss = ConfigurableLanguage::createFromLangcode('gsw-berne');
608     $language_manager->reset();
609     \Drupal::service('language.default')->set($swiss);
610
611     $path = '/path/add-one';
612     $request = Request::create($path, 'GET');
613     $provider->getRouteCollectionForRequest($request);
614
615     $cache = $this->cache->get('route:gsw-berne:/path/add-one:');
616     $this->assertEquals('/path/add/one', $cache->data['path']);
617     $this->assertEquals([], $cache->data['query']);
618     $this->assertEquals(3, count($cache->data['routes']));
619   }
620
621   /**
622    * Test RouteProvider::getRouteByName() and RouteProvider::getRoutesByNames().
623    */
624   public function testRouteByName() {
625     $connection = Database::getConnection();
626     $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->pathProcessor, $this->cacheTagsInvalidator, 'test_routes');
627
628     $this->fixtures->createTables($connection);
629
630     $dumper = new MatcherDumper($connection, $this->state, 'test_routes');
631     $dumper->addRoutes($this->fixtures->sampleRouteCollection());
632     $dumper->dump();
633
634     $route = $provider->getRouteByName('route_a');
635     $this->assertEqual($route->getPath(), '/path/one', 'The right route pattern was found.');
636     $this->assertEqual($route->getMethods(), ['GET'], 'The right route method was found.');
637     $route = $provider->getRouteByName('route_b');
638     $this->assertEqual($route->getPath(), '/path/one', 'The right route pattern was found.');
639     $this->assertEqual($route->getMethods(), ['PUT'], 'The right route method was found.');
640
641     $exception_thrown = FALSE;
642     try {
643       $provider->getRouteByName('invalid_name');
644     }
645     catch (RouteNotFoundException $e) {
646       $exception_thrown = TRUE;
647     }
648     $this->assertTrue($exception_thrown, 'Random route was not found.');
649
650     $routes = $provider->getRoutesByNames(['route_c', 'route_d', $this->randomMachineName()]);
651     $this->assertEqual(count($routes), 2, 'Only two valid routes found.');
652     $this->assertEqual($routes['route_c']->getPath(), '/path/two');
653     $this->assertEqual($routes['route_d']->getPath(), '/path/three');
654   }
655
656   /**
657    * Ensures that the routing system is capable of extreme long patterns.
658    */
659   public function testGetRoutesByPatternWithLongPatterns() {
660     $connection = Database::getConnection();
661     $provider = new TestRouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->pathProcessor, $this->cacheTagsInvalidator, 'test_routes');
662
663     $this->fixtures->createTables($connection);
664     // This pattern has only 3 parts, so we will get candidates, but no routes,
665     // even though we have not dumped the routes yet.
666     $shortest = '/test/1/test2';
667     $result = $provider->getRoutesByPattern($shortest);
668     $this->assertEqual($result->count(), 0);
669     $candidates = $provider->getCandidateOutlines(explode('/', trim($shortest, '/')));
670     $this->assertEqual(count($candidates), 7);
671     // A longer patten is not found and returns no candidates
672     $path_to_test = '/test/1/test2/2/test3/3/4/5/6/test4';
673     $result = $provider->getRoutesByPattern($path_to_test);
674     $this->assertEqual($result->count(), 0);
675     $candidates = $provider->getCandidateOutlines(explode('/', trim($path_to_test, '/')));
676     $this->assertEqual(count($candidates), 0);
677
678     // Add a matching route and dump it.
679     $dumper = new MatcherDumper($connection, $this->state, 'test_routes');
680     $collection = new RouteCollection();
681     $collection->add('long_pattern', new Route('/test/{v1}/test2/{v2}/test3/{v3}/{v4}/{v5}/{v6}/test4'));
682     $dumper->addRoutes($collection);
683     $dumper->dump();
684
685     $result = $provider->getRoutesByPattern($path_to_test);
686     $this->assertEqual($result->count(), 1);
687     // We can't compare the values of the routes directly, nor use
688     // spl_object_hash() because they are separate instances.
689     $this->assertEqual(serialize($result->get('long_pattern')), serialize($collection->get('long_pattern')), 'The right route was found.');
690     // We now have a single candidate outline.
691     $candidates = $provider->getCandidateOutlines(explode('/', trim($path_to_test, '/')));
692     $this->assertEqual(count($candidates), 1);
693     // Longer and shorter patterns are not found. Both are longer than 3, so
694     // we should not have any candidates either. The fact that we do not
695     // get any candidates for a longer path is a security feature.
696     $longer = '/test/1/test2/2/test3/3/4/5/6/test4/trailing/more/parts';
697     $result = $provider->getRoutesByPattern($longer);
698     $this->assertEqual($result->count(), 0);
699     $candidates = $provider->getCandidateOutlines(explode('/', trim($longer, '/')));
700     $this->assertEqual(count($candidates), 1);
701     $shorter = '/test/1/test2/2/test3';
702     $result = $provider->getRoutesByPattern($shorter);
703     $this->assertEqual($result->count(), 0);
704     $candidates = $provider->getCandidateOutlines(explode('/', trim($shorter, '/')));
705     $this->assertEqual(count($candidates), 0);
706     // This pattern has only 3 parts, so we will get candidates, but no routes.
707     // This result is unchanged by running the dumper.
708     $result = $provider->getRoutesByPattern($shortest);
709     $this->assertEqual($result->count(), 0);
710     $candidates = $provider->getCandidateOutlines(explode('/', trim($shortest, '/')));
711     $this->assertEqual(count($candidates), 7);
712   }
713
714   /**
715    * Tests getRoutesPaged().
716    */
717   public function testGetRoutesPaged() {
718     $connection = Database::getConnection();
719     $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->pathProcessor, $this->cacheTagsInvalidator, 'test_routes');
720
721     $this->fixtures->createTables($connection);
722     $dumper = new MatcherDumper($connection, $this->state, 'test_routes');
723     $dumper->addRoutes($this->fixtures->sampleRouteCollection());
724     $dumper->dump();
725
726     $fixture_routes = $this->fixtures->staticSampleRouteCollection();
727
728     // Query all the routes.
729     $routes = $provider->getRoutesPaged(0);
730     $this->assertEqual(array_keys($routes), array_keys($fixture_routes));
731
732     // Query non routes.
733     $routes = $provider->getRoutesPaged(0, 0);
734     $this->assertEqual(array_keys($routes), []);
735
736     // Query a limited sets of routes.
737     $routes = $provider->getRoutesPaged(1, 2);
738     $this->assertEqual(array_keys($routes), array_slice(array_keys($fixture_routes), 1, 2));
739   }
740
741 }
742
743 class TestRouteProvider extends RouteProvider {
744
745   public function getCandidateOutlines(array $parts) {
746     return parent::getCandidateOutlines($parts);
747   }
748
749 }