Upgraded drupal core with security updates
[yaffs-website] / web / core / tests / Drupal / Tests / Core / UrlTest.php
1 <?php
2
3 /**
4  * @file
5  * Contains \Drupal\Tests\Core\UrlTest.
6  */
7
8 namespace Drupal\Tests\Core;
9
10 use Drupal\Component\Utility\UrlHelper;
11 use Drupal\Core\Access\AccessManagerInterface;
12 use Drupal\Core\DependencyInjection\ContainerBuilder;
13 use Drupal\Core\GeneratedUrl;
14 use Drupal\Core\Routing\RouteMatch;
15 use Drupal\Core\Url;
16 use Drupal\Tests\UnitTestCase;
17 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
18 use Symfony\Component\HttpFoundation\ParameterBag;
19 use Symfony\Component\HttpFoundation\Request;
20 use Symfony\Component\Routing\Exception\InvalidParameterException;
21 use Symfony\Component\Routing\Exception\ResourceNotFoundException;
22 use Symfony\Component\Routing\Route;
23
24 /**
25  * @coversDefaultClass \Drupal\Core\Url
26  * @group UrlTest
27  */
28 class UrlTest extends UnitTestCase {
29
30   /**
31    * @var \Symfony\Component\DependencyInjection\ContainerInterface
32    */
33   protected $container;
34
35   /**
36    * The URL generator
37    *
38    * @var \Drupal\Core\Routing\UrlGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject
39    */
40   protected $urlGenerator;
41
42   /**
43    * The path alias manager.
44    *
45    * @var \Drupal\Core\Path\AliasManagerInterface|\PHPUnit_Framework_MockObject_MockObject
46    */
47   protected $pathAliasManager;
48
49   /**
50    * The router.
51    *
52    * @var \Drupal\Tests\Core\Routing\TestRouterInterface|\PHPUnit_Framework_MockObject_MockObject
53    */
54   protected $router;
55
56   /**
57    * An array of values to use for the test.
58    *
59    * @var array
60    */
61   protected $map;
62
63   /**
64    * The mocked path validator.
65    *
66    * @var \Drupal\Core\Path\PathValidatorInterface|\PHPUnit_Framework_MockObject_MockObject
67    */
68   protected $pathValidator;
69
70   /**
71    * {@inheritdoc}
72    */
73   protected function setUp() {
74     parent::setUp();
75
76     $map = [];
77     $map[] = ['view.frontpage.page_1', [], [], FALSE, '/node'];
78     $map[] = ['node_view', ['node' => '1'], [], FALSE, '/node/1'];
79     $map[] = ['node_edit', ['node' => '2'], [], FALSE, '/node/2/edit'];
80     $this->map = $map;
81
82     $alias_map = [
83       // Set up one proper alias that can be resolved to a system path.
84       ['node-alias-test', NULL, FALSE, 'node'],
85       // Passing in anything else should return the same string.
86       ['node', NULL, FALSE, 'node'],
87       ['node/1', NULL, FALSE, 'node/1'],
88       ['node/2/edit', NULL, FALSE, 'node/2/edit'],
89       ['non-existent', NULL, FALSE, 'non-existent'],
90     ];
91
92     // $this->map has $collect_bubbleable_metadata = FALSE; also generate the
93     // $collect_bubbleable_metadata = TRUE case for ::generateFromRoute().
94     $generate_from_route_map = [];
95     foreach ($this->map as $values) {
96       $generate_from_route_map[] = $values;
97       $generate_from_route_map[] = [$values[0], $values[1], $values[2], TRUE, (new GeneratedUrl())->setGeneratedUrl($values[4])];
98     }
99     $this->urlGenerator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface');
100     $this->urlGenerator->expects($this->any())
101       ->method('generateFromRoute')
102       ->will($this->returnValueMap($generate_from_route_map));
103
104     $this->pathAliasManager = $this->getMock('Drupal\Core\Path\AliasManagerInterface');
105     $this->pathAliasManager->expects($this->any())
106       ->method('getPathByAlias')
107       ->will($this->returnValueMap($alias_map));
108
109     $this->router = $this->getMock('Drupal\Tests\Core\Routing\TestRouterInterface');
110     $this->pathValidator = $this->getMock('Drupal\Core\Path\PathValidatorInterface');
111
112     $this->container = new ContainerBuilder();
113     $this->container->set('router.no_access_checks', $this->router);
114     $this->container->set('url_generator', $this->urlGenerator);
115     $this->container->set('path.alias_manager', $this->pathAliasManager);
116     $this->container->set('path.validator', $this->pathValidator);
117     \Drupal::setContainer($this->container);
118   }
119
120   /**
121    * Tests creating a Url from a request.
122    */
123   public function testUrlFromRequest() {
124     $this->router->expects($this->at(0))
125       ->method('matchRequest')
126       ->with($this->getRequestConstraint('/node'))
127       ->willReturn([
128           RouteObjectInterface::ROUTE_NAME => 'view.frontpage.page_1',
129           '_raw_variables' => new ParameterBag(),
130         ]);
131     $this->router->expects($this->at(1))
132       ->method('matchRequest')
133       ->with($this->getRequestConstraint('/node/1'))
134       ->willReturn([
135         RouteObjectInterface::ROUTE_NAME => 'node_view',
136         '_raw_variables' => new ParameterBag(['node' => '1']),
137       ]);
138     $this->router->expects($this->at(2))
139       ->method('matchRequest')
140       ->with($this->getRequestConstraint('/node/2/edit'))
141       ->willReturn([
142         RouteObjectInterface::ROUTE_NAME => 'node_edit',
143         '_raw_variables' => new ParameterBag(['node' => '2']),
144       ]);
145
146     $urls = [];
147     foreach ($this->map as $index => $values) {
148       $path = array_pop($values);
149       $url = Url::createFromRequest(Request::create("$path"));
150       $expected = Url::fromRoute($values[0], $values[1], $values[2]);
151       $this->assertEquals($expected, $url);
152       $urls[$index] = $url;
153     }
154     return $urls;
155   }
156
157   /**
158    * This constraint checks whether a Request object has the right path.
159    *
160    * @param string $path
161    *   The path.
162    *
163    * @return \PHPUnit_Framework_Constraint_Callback
164    *   The constraint checks whether a Request object has the right path.
165    */
166   protected function getRequestConstraint($path) {
167     return $this->callback(function (Request $request) use ($path) {
168       return $request->getPathInfo() == $path;
169     });
170   }
171
172   /**
173    * Tests the fromRoute() method with the special <front> path.
174    *
175    * @covers ::fromRoute
176    */
177   public function testFromRouteFront() {
178     $url = Url::fromRoute('<front>');
179     $this->assertSame('<front>', $url->getRouteName());
180   }
181
182   /**
183    * Tests the fromUserInput method with valid paths.
184    *
185    * @covers ::fromUserInput
186    * @dataProvider providerFromValidInternalUri
187    */
188   public function testFromUserInput($path) {
189     $url = Url::fromUserInput($path);
190     $uri = $url->getUri();
191
192     $this->assertInstanceOf('Drupal\Core\Url', $url);
193     $this->assertFalse($url->isRouted());
194     $this->assertEquals(0, strpos($uri, 'base:'));
195
196     $parts = UrlHelper::parse($path);
197     $options = $url->getOptions();
198
199     if (!empty($parts['fragment'])) {
200       $this->assertSame($parts['fragment'], $options['fragment']);
201     }
202     else {
203       $this->assertArrayNotHasKey('fragment', $options);
204     }
205
206     if (!empty($parts['query'])) {
207       $this->assertEquals($parts['query'], $options['query']);
208     }
209     else {
210       $this->assertArrayNotHasKey('query', $options);
211     }
212   }
213
214   /**
215    * Tests the fromUserInput method with invalid paths.
216    *
217    * @covers ::fromUserInput
218    * @dataProvider providerFromInvalidInternalUri
219    */
220   public function testFromInvalidUserInput($path) {
221     $this->setExpectedException(\InvalidArgumentException::class);
222     $url = Url::fromUserInput($path);
223   }
224
225   /**
226    * Tests fromUri() method with a user-entered path not matching any route.
227    *
228    * @covers ::fromUri
229    */
230   public function testFromRoutedPathWithInvalidRoute() {
231     $this->pathValidator->expects($this->once())
232       ->method('getUrlIfValidWithoutAccessCheck')
233       ->with('invalid-path')
234       ->willReturn(FALSE);
235     $url = Url::fromUri('internal:/invalid-path');
236     $this->assertSame(FALSE, $url->isRouted());
237     $this->assertSame('base:invalid-path', $url->getUri());
238   }
239
240   /**
241    * Tests fromUri() method with user-entered path matching a valid route.
242    *
243    * @covers ::fromUri
244    */
245   public function testFromRoutedPathWithValidRoute() {
246     $url = Url::fromRoute('test_route');
247     $this->pathValidator->expects($this->once())
248       ->method('getUrlIfValidWithoutAccessCheck')
249       ->with('valid-path')
250       ->willReturn($url);
251     $result_url = Url::fromUri('internal:/valid-path');
252     $this->assertSame($url, $result_url);
253   }
254
255   /**
256    * Tests the createFromRequest method.
257    *
258    * @covers ::createFromRequest
259    */
260   public function testCreateFromRequest() {
261     $attributes = [
262       '_raw_variables' => new ParameterBag([
263         'color' => 'chartreuse',
264       ]),
265       RouteObjectInterface::ROUTE_NAME => 'the_route_name',
266     ];
267     $request = new Request([], [], $attributes);
268
269     $this->router->expects($this->once())
270       ->method('matchRequest')
271       ->with($request)
272       ->will($this->returnValue($attributes));
273
274     $url = Url::createFromRequest($request);
275     $expected = new Url('the_route_name', ['color' => 'chartreuse']);
276     $this->assertEquals($expected, $url);
277   }
278
279   /**
280    * Tests that an invalid request will thrown an exception.
281    *
282    * @covers ::createFromRequest
283    */
284   public function testUrlFromRequestInvalid() {
285     $request = Request::create('/test-path');
286
287     $this->router->expects($this->once())
288       ->method('matchRequest')
289       ->with($request)
290       ->will($this->throwException(new ResourceNotFoundException()));
291
292     $this->setExpectedException(ResourceNotFoundException::class);
293     Url::createFromRequest($request);
294   }
295
296   /**
297    * Tests the isExternal() method.
298    *
299    * @depends testUrlFromRequest
300    *
301    * @covers ::isExternal
302    */
303   public function testIsExternal($urls) {
304     foreach ($urls as $url) {
305       $this->assertFalse($url->isExternal());
306     }
307   }
308
309   /**
310    * Tests the getUri() method for internal URLs.
311    *
312    * @param \Drupal\Core\Url[] $urls
313    *   Array of URL objects.
314    *
315    * @depends testUrlFromRequest
316    *
317    * @covers ::getUri
318    */
319   public function testGetUriForInternalUrl($urls) {
320     $this->setExpectedException(\UnexpectedValueException::class);
321     foreach ($urls as $url) {
322       $url->getUri();
323     }
324   }
325
326   /**
327    * Tests the getUri() method for external URLs.
328    *
329    * @covers ::getUri
330    */
331   public function testGetUriForExternalUrl() {
332     $url = Url::fromUri('http://example.com/test');
333     $this->assertEquals('http://example.com/test', $url->getUri());
334   }
335
336   /**
337    * Tests the getUri() and isExternal() methods for protocol-relative URLs.
338    *
339    * @covers ::getUri
340    * @covers ::isExternal
341    */
342   public function testGetUriForProtocolRelativeUrl() {
343     $url = Url::fromUri('//example.com/test');
344     $this->assertEquals('//example.com/test', $url->getUri());
345     $this->assertTrue($url->isExternal());
346   }
347
348   /**
349    * Tests the getInternalPath method().
350    *
351    * @param \Drupal\Core\Url[] $urls
352    *   Array of URL objects.
353    *
354    * @covers ::getInternalPath
355    *
356    * @depends testUrlFromRequest
357    */
358   public function testGetInternalPath($urls) {
359     $map = [];
360     $map[] = ['view.frontpage.page_1', [], '/node'];
361     $map[] = ['node_view', ['node' => '1'], '/node/1'];
362     $map[] = ['node_edit', ['node' => '2'], '/node/2/edit'];
363
364     foreach ($urls as $index => $url) {
365       // Clone the url so that there is no leak of internal state into the
366       // other ones.
367       $url = clone $url;
368       $url_generator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface');
369       $url_generator->expects($this->once())
370         ->method('getPathFromRoute')
371         ->will($this->returnValueMap($map, $index));
372       $url->setUrlGenerator($url_generator);
373
374       $url->getInternalPath();
375       $url->getInternalPath();
376     }
377   }
378
379   /**
380    * Tests the toString() method.
381    *
382    * @param \Drupal\Core\Url[] $urls
383    *   An array of Url objects.
384    *
385    * @depends testUrlFromRequest
386    *
387    * @covers ::toString
388    */
389   public function testToString($urls) {
390     foreach ($urls as $index => $url) {
391       $path = array_pop($this->map[$index]);
392       $this->assertSame($path, $url->toString());
393       $generated_url = $url->toString(TRUE);
394       $this->assertSame($path, $generated_url->getGeneratedUrl());
395       $this->assertInstanceOf('\Drupal\Core\Render\BubbleableMetadata', $generated_url);
396     }
397   }
398
399   /**
400    * Tests the getRouteName() method.
401    *
402    * @param \Drupal\Core\Url[] $urls
403    *   An array of Url objects.
404    *
405    * @depends testUrlFromRequest
406    *
407    * @covers ::getRouteName
408    */
409   public function testGetRouteName($urls) {
410     foreach ($urls as $index => $url) {
411       $this->assertSame($this->map[$index][0], $url->getRouteName());
412     }
413   }
414
415   /**
416    * Tests the getRouteName() with an external URL.
417    *
418    * @covers ::getRouteName
419    */
420   public function testGetRouteNameWithExternalUrl() {
421     $url = Url::fromUri('http://example.com');
422     $this->setExpectedException(\UnexpectedValueException::class);
423     $url->getRouteName();
424   }
425
426   /**
427    * Tests the getRouteParameters() method.
428    *
429    * @param \Drupal\Core\Url[] $urls
430    *   An array of Url objects.
431    *
432    * @depends testUrlFromRequest
433    *
434    * @covers ::getRouteParameters
435    */
436   public function testGetRouteParameters($urls) {
437     foreach ($urls as $index => $url) {
438       $this->assertSame($this->map[$index][1], $url->getRouteParameters());
439     }
440   }
441
442   /**
443    * Tests the getRouteParameters() with an external URL.
444    *
445    * @covers ::getRouteParameters
446    */
447   public function testGetRouteParametersWithExternalUrl() {
448     $url = Url::fromUri('http://example.com');
449     $this->setExpectedException(\UnexpectedValueException::class);
450     $url->getRouteParameters();
451   }
452
453   /**
454    * Tests the getOptions() method.
455    *
456    * @param \Drupal\Core\Url[] $urls
457    *   An array of Url objects.
458    *
459    * @depends testUrlFromRequest
460    *
461    * @covers ::getOptions
462    */
463   public function testGetOptions($urls) {
464     foreach ($urls as $index => $url) {
465       $this->assertSame($this->map[$index][2], $url->getOptions());
466     }
467   }
468
469   /**
470    * Tests the setOptions() method.
471    *
472    * @covers ::setOptions
473    */
474   public function testSetOptions() {
475     $url = Url::fromRoute('test_route', []);
476     $this->assertEquals([], $url->getOptions());
477     $url->setOptions(['foo' => 'bar']);
478     $this->assertEquals(['foo' => 'bar'], $url->getOptions());
479     $url->setOptions([]);
480     $this->assertEquals([], $url->getOptions());
481   }
482
483   /**
484    * Tests the mergeOptions() method.
485    *
486    * @covers ::mergeOptions
487    */
488   public function testMergeOptions() {
489     $url = Url::fromRoute('test_route', [], ['foo' => 'bar', 'bar' => ['key' => 'value']]);
490     $url->mergeOptions(['bar' => ['key' => 'value1', 'key2' => 'value2']]);
491     $this->assertEquals(['foo' => 'bar', 'bar' => ['key' => 'value1', 'key2' => 'value2']], $url->getOptions());
492   }
493
494   /**
495    * Tests the access() method for routed URLs.
496    *
497    * @param bool $access
498    *
499    * @covers ::access
500    * @covers ::accessManager
501    * @dataProvider accessProvider
502    */
503   public function testAccessRouted($access) {
504     $account = $this->getMock('Drupal\Core\Session\AccountInterface');
505     $url = new TestUrl('entity.node.canonical', ['node' => 3]);
506     $url->setAccessManager($this->getMockAccessManager($access, $account));
507     $this->assertEquals($access, $url->access($account));
508   }
509
510   /**
511    * Tests the access() method for unrouted URLs (they always have access).
512    *
513    * @covers ::access
514    */
515   public function testAccessUnrouted() {
516     $account = $this->getMock('Drupal\Core\Session\AccountInterface');
517     $url = TestUrl::fromUri('base:kittens');
518     $access_manager = $this->getMock('Drupal\Core\Access\AccessManagerInterface');
519     $access_manager->expects($this->never())
520       ->method('checkNamedRoute');
521     $url->setAccessManager($access_manager);
522     $this->assertTrue($url->access($account));
523   }
524
525   /**
526    * Tests the renderAccess() method.
527    *
528    * @param bool $access
529    *
530    * @covers ::renderAccess
531    * @dataProvider accessProvider
532    */
533   public function testRenderAccess($access) {
534     $element = [
535       '#url' => Url::fromRoute('entity.node.canonical', ['node' => 3]),
536     ];
537     $this->container->set('current_user', $this->getMock('Drupal\Core\Session\AccountInterface'));
538     $this->container->set('access_manager', $this->getMockAccessManager($access));
539     $this->assertEquals($access, TestUrl::renderAccess($element));
540   }
541
542   /**
543    * Tests the fromRouteMatch() method.
544    */
545   public function testFromRouteMatch() {
546     $route = new Route('/test-route/{foo}');
547     $route_match = new RouteMatch('test_route', $route, ['foo' => (object) [1]], ['foo' => 1]);
548     $url = Url::fromRouteMatch($route_match);
549     $this->assertSame('test_route', $url->getRouteName());
550     $this->assertEquals(['foo' => '1'], $url->getRouteParameters());
551   }
552
553   /**
554    * Data provider for testing entity URIs
555    */
556   public function providerTestEntityUris() {
557     return [
558       [
559         'entity:test_entity/1',
560         [],
561         'entity.test_entity.canonical',
562         ['test_entity' => '1'],
563         NULL,
564         NULL,
565       ],
566       [
567         // Ensure a fragment of #0 is handled correctly.
568         'entity:test_entity/1#0',
569         [],
570         'entity.test_entity.canonical',
571         ['test_entity' => '1'],
572         NULL,
573         '0',
574       ],
575       // Ensure an empty fragment of # is in options discarded as expected.
576       [
577         'entity:test_entity/1',
578         ['fragment' => ''],
579         'entity.test_entity.canonical',
580         ['test_entity' => '1'],
581         NULL,
582         NULL,
583       ],
584       // Ensure an empty fragment of # in the URI is discarded as expected.
585       [
586         'entity:test_entity/1#',
587         [],
588         'entity.test_entity.canonical',
589         ['test_entity' => '1'],
590         NULL,
591         NULL,
592       ],
593       [
594         'entity:test_entity/2?page=1&foo=bar#bottom',
595         [], 'entity.test_entity.canonical',
596         ['test_entity' => '2'],
597         ['page' => '1', 'foo' => 'bar'],
598         'bottom',
599       ],
600       [
601         'entity:test_entity/2?page=1&foo=bar#bottom',
602         ['fragment' => 'top', 'query' => ['foo' => 'yes', 'focus' => 'no']],
603         'entity.test_entity.canonical',
604         ['test_entity' => '2'],
605         ['page' => '1', 'foo' => 'yes', 'focus' => 'no'],
606         'top',
607       ],
608
609     ];
610   }
611
612   /**
613    * Tests the fromUri() method with an entity: URI.
614    *
615    * @covers ::fromUri
616    *
617    * @dataProvider providerTestEntityUris
618    */
619   public function testEntityUris($uri, $options, $route_name, $route_parameters, $query, $fragment) {
620     $url = Url::fromUri($uri, $options);
621     $this->assertSame($route_name, $url->getRouteName());
622     $this->assertEquals($route_parameters, $url->getRouteParameters());
623     $this->assertEquals($url->getOption('query'), $query);
624     $this->assertSame($url->getOption('fragment'), $fragment);
625   }
626
627   /**
628    * Tests the fromUri() method with an invalid entity: URI.
629    *
630    * @covers ::fromUri
631    */
632   public function testInvalidEntityUriParameter() {
633     // Make the mocked URL generator behave like the actual one.
634     $this->urlGenerator->expects($this->once())
635       ->method('generateFromRoute')
636       ->with('entity.test_entity.canonical', ['test_entity' => '1/blah'])
637       ->willThrowException(new InvalidParameterException('Parameter "test_entity" for route "/test_entity/{test_entity}" must match "[^/]++" ("1/blah" given) to generate a corresponding URL..'));
638
639     $this->setExpectedException(InvalidParameterException::class);
640     Url::fromUri('entity:test_entity/1/blah')->toString();
641   }
642
643   /**
644    * Tests the toUriString() method with entity: URIs.
645    *
646    * @covers ::toUriString
647    *
648    * @dataProvider providerTestToUriStringForEntity
649    */
650   public function testToUriStringForEntity($uri, $options, $uri_string) {
651     $url = Url::fromUri($uri, $options);
652     $this->assertSame($url->toUriString(), $uri_string);
653   }
654
655   /**
656    * Data provider for testing string entity URIs
657    */
658   public function providerTestToUriStringForEntity() {
659     return [
660       ['entity:test_entity/1', [], 'route:entity.test_entity.canonical;test_entity=1'],
661       ['entity:test_entity/1', ['fragment' => 'top', 'query' => ['page' => '2']], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'],
662       ['entity:test_entity/1?page=2#top', [], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'],
663     ];
664   }
665
666   /**
667    * Tests the toUriString() method with internal: URIs.
668    *
669    * @covers ::toUriString
670    *
671    * @dataProvider providerTestToUriStringForInternal
672    */
673   public function testToUriStringForInternal($uri, $options, $uri_string) {
674     $url = Url::fromRoute('entity.test_entity.canonical', ['test_entity' => '1']);
675     $this->pathValidator->expects($this->any())
676       ->method('getUrlIfValidWithoutAccessCheck')
677       ->willReturnMap([
678         ['test-entity/1', $url],
679         ['<front>', Url::fromRoute('<front>')],
680         ['<none>', Url::fromRoute('<none>')],
681       ]);
682     $url = Url::fromUri($uri, $options);
683     $this->assertSame($url->toUriString(), $uri_string);
684   }
685
686   /**
687    * Data provider for testing internal URIs.
688    */
689   public function providerTestToUriStringForInternal() {
690     return [
691       // The four permutations of a regular path.
692       ['internal:/test-entity/1', [], 'route:entity.test_entity.canonical;test_entity=1'],
693       ['internal:/test-entity/1', ['fragment' => 'top'], 'route:entity.test_entity.canonical;test_entity=1#top'],
694       ['internal:/test-entity/1', ['fragment' => 'top', 'query' => ['page' => '2']], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'],
695       ['internal:/test-entity/1?page=2#top', [], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'],
696
697       // The four permutations of the special '<front>' path.
698       ['internal:/', [], 'route:<front>'],
699       ['internal:/', ['fragment' => 'top'], 'route:<front>#top'],
700       ['internal:/', ['fragment' => 'top', 'query' => ['page' => '2']], 'route:<front>?page=2#top'],
701       ['internal:/?page=2#top', [], 'route:<front>?page=2#top'],
702
703       // The four permutations of the special '<none>' path.
704       ['internal:', [], 'route:<none>'],
705       ['internal:', ['fragment' => 'top'], 'route:<none>#top'],
706       ['internal:', ['fragment' => 'top', 'query' => ['page' => '2']], 'route:<none>?page=2#top'],
707       ['internal:?page=2#top', [], 'route:<none>?page=2#top'],
708     ];
709   }
710
711   /**
712    * Tests the fromUri() method with a valid internal: URI.
713    *
714    * @covers ::fromUri
715    * @dataProvider providerFromValidInternalUri
716    */
717   public function testFromValidInternalUri($path) {
718     $url = Url::fromUri('internal:' . $path);
719     $this->assertInstanceOf('Drupal\Core\Url', $url);
720   }
721
722   /**
723    * Data provider for testFromValidInternalUri().
724    */
725   public function providerFromValidInternalUri() {
726     return [
727       // Normal paths with a leading slash.
728       ['/kittens'],
729       ['/kittens/bengal'],
730       // Fragments with and without leading slashes.
731       ['/#about-our-kittens'],
732       ['/kittens#feeding'],
733       ['#feeding'],
734       // Query strings with and without leading slashes.
735       ['/kittens?page=1000'],
736       ['/?page=1000'],
737       ['?page=1000'],
738       ['?breed=bengal&page=1000'],
739       ['?referrer=https://kittenfacts'],
740       // Paths with various token formats but no leading slash.
741       ['/[duckies]'],
742       ['/%bunnies'],
743       ['/{{ puppies }}'],
744       // Disallowed characters in the authority (host name) that are valid
745       // elsewhere in the path.
746       ['/(:;2&+h^'],
747       ['/AKI@&hO@'],
748     ];
749   }
750
751   /**
752    * Tests the fromUri() method with an invalid internal: URI.
753    *
754    * @covers ::fromUri
755    * @dataProvider providerFromInvalidInternalUri
756    */
757   public function testFromInvalidInternalUri($path) {
758     $this->setExpectedException(\InvalidArgumentException::class);
759     Url::fromUri('internal:' . $path);
760   }
761
762   /**
763    * Data provider for testFromInvalidInternalUri().
764    */
765   public function providerFromInvalidInternalUri() {
766     return [
767       // Normal paths without a leading slash.
768       'normal_path0' => ['kittens'],
769       'normal_path1' => ['kittens/bengal'],
770       // Path without a leading slash containing a fragment.
771       'fragment' => ['kittens#feeding'],
772        // Path without a leading slash containing a query string.
773       'without_leading_slash_query' => ['kittens?page=1000'],
774       // Paths with various token formats but no leading slash.
775       'path_with_tokens0' => ['[duckies]'],
776       'path_with_tokens1' => ['%bunnies'],
777       'path_with_tokens2' => ['{{ puppies }}'],
778       // Disallowed characters in the authority (host name) that are valid
779       // elsewhere in the path.
780       'disallowed_hostname_chars0' => ['(:;2&+h^'],
781       'disallowed_hostname_chars1' => ['AKI@&hO@'],
782       // Leading slash with a domain.
783       'leading_slash_with_domain' => ['/http://example.com'],
784     ];
785   }
786
787   /**
788    * Tests the fromUri() method with a base: URI starting with a number.
789    *
790    * @covers ::fromUri
791    */
792   public function testFromUriNumber() {
793     $url = Url::fromUri('base:2015/10/06');
794     $this->assertSame($url->toUriString(), 'base:/2015/10/06');
795   }
796
797   /**
798    * Tests the toUriString() method with route: URIs.
799    *
800    * @covers ::toUriString
801    *
802    * @dataProvider providerTestToUriStringForRoute
803    */
804   public function testToUriStringForRoute($uri, $options, $uri_string) {
805     $url = Url::fromUri($uri, $options);
806     $this->assertSame($url->toUriString(), $uri_string);
807   }
808
809   /**
810    * Data provider for testing route: URIs.
811    */
812   public function providerTestToUriStringForRoute() {
813     return [
814       ['route:entity.test_entity.canonical;test_entity=1', [], 'route:entity.test_entity.canonical;test_entity=1'],
815       ['route:entity.test_entity.canonical;test_entity=1', ['fragment' => 'top', 'query' => ['page' => '2']], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'],
816       ['route:entity.test_entity.canonical;test_entity=1?page=2#top', [], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'],
817       // Check that an empty fragment is discarded.
818       ['route:entity.test_entity.canonical;test_entity=1?page=2#', [], 'route:entity.test_entity.canonical;test_entity=1?page=2'],
819       // Check that an empty fragment is discarded.
820       ['route:entity.test_entity.canonical;test_entity=1?page=2', ['fragment' => ''], 'route:entity.test_entity.canonical;test_entity=1?page=2'],
821       // Check that a fragment of #0 is preserved.
822       ['route:entity.test_entity.canonical;test_entity=1?page=2#0', [], 'route:entity.test_entity.canonical;test_entity=1?page=2#0'],
823     ];
824   }
825
826   /**
827    * @covers ::fromUri
828    */
829   public function testFromRouteUriWithMissingRouteName() {
830     $this->setExpectedException(\InvalidArgumentException::class, "The route URI 'route:' is invalid.");
831     Url::fromUri('route:');
832   }
833
834   /**
835    * Creates a mock access manager for the access tests.
836    *
837    * @param bool $access
838    * @param \Drupal\Core\Session\AccountInterface|null $account
839    *
840    * @return \Drupal\Core\Access\AccessManagerInterface|\PHPUnit_Framework_MockObject_MockObject
841    */
842   protected function getMockAccessManager($access, $account = NULL) {
843     $access_manager = $this->getMock('Drupal\Core\Access\AccessManagerInterface');
844     $access_manager->expects($this->once())
845       ->method('checkNamedRoute')
846       ->with('entity.node.canonical', ['node' => 3], $account)
847       ->willReturn($access);
848     return $access_manager;
849   }
850
851   /**
852    * Data provider for the access test methods.
853    */
854   public function accessProvider() {
855     return [
856       [TRUE],
857       [FALSE],
858     ];
859   }
860
861 }
862
863 class TestUrl extends Url {
864
865   /**
866    * Sets the access manager.
867    *
868    * @param \Drupal\Core\Access\AccessManagerInterface $access_manager
869    */
870   public function setAccessManager(AccessManagerInterface $access_manager) {
871     $this->accessManager = $access_manager;
872   }
873
874 }