Version 1
[yaffs-website] / web / core / tests / Drupal / Tests / Core / Routing / UrlGeneratorTest.php
diff --git a/web/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php b/web/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php
new file mode 100644 (file)
index 0000000..6c6d768
--- /dev/null
@@ -0,0 +1,552 @@
+<?php
+
+namespace Drupal\Tests\Core\Routing;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
+use Drupal\Core\PathProcessor\PathProcessorAlias;
+use Drupal\Core\PathProcessor\PathProcessorManager;
+use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\Core\Routing\RequestContext;
+use Drupal\Core\Routing\RouteProviderInterface;
+use Drupal\Core\Routing\UrlGenerator;
+use Drupal\Tests\UnitTestCase;
+use Prophecy\Argument;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Confirm that the UrlGenerator is functioning properly.
+ *
+ * @coversDefaultClass \Drupal\Core\Routing\UrlGenerator
+ * @group Routing
+ */
+class UrlGeneratorTest extends UnitTestCase {
+
+  /**
+   * The route provider.
+   *
+   * @var \Drupal\Core\Routing\RouteProviderInterface
+   */
+  protected $provider;
+
+  /**
+   * The url generator to test.
+   *
+   * @var \Drupal\Core\Routing\UrlGenerator
+   */
+  protected $generator;
+
+  /**
+   * The alias manager.
+   *
+   * @var \Drupal\Core\Path\AliasManager|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $aliasManager;
+
+  /**
+   * The mock route processor manager.
+   *
+   * @var \Drupal\Core\RouteProcessor\RouteProcessorManager|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $routeProcessorManager;
+
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * The request context.
+   *
+   * @var \Drupal\Core\Routing\RequestContext
+   */
+  protected $context;
+
+  /**
+   * The path processor.
+   *
+   * @var \Drupal\Core\PathProcessor\PathProcessorManager
+   */
+  protected $processorManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    $cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE);
+    $container = new ContainerBuilder();
+    $container->set('cache_contexts_manager', $cache_contexts_manager);
+    \Drupal::setContainer($container);
+
+    $routes = new RouteCollection();
+    $first_route = new Route('/test/one');
+    $second_route = new Route('/test/two/{narf}');
+    $third_route = new Route('/test/two/');
+    $fourth_route = new Route('/test/four', [], [], [], '', ['https']);
+    $none_route = new Route('', [], [], ['_no_path' => TRUE]);
+
+    $routes->add('test_1', $first_route);
+    $routes->add('test_2', $second_route);
+    $routes->add('test_3', $third_route);
+    $routes->add('test_4', $fourth_route);
+    $routes->add('<none>', $none_route);
+
+    // Create a route provider stub.
+    $provider = $this->getMockBuilder('Drupal\Core\Routing\RouteProvider')
+      ->disableOriginalConstructor()
+      ->getMock();
+    // We need to set up return value maps for both the getRouteByName() and the
+    // getRoutesByNames() method calls on the route provider. The parameters
+    // are not passed in and default to an empty array.
+    $route_name_return_map = $routes_names_return_map = [];
+    $return_map_values = [
+      [
+        'route_name' => 'test_1',
+        'return' => $first_route,
+      ],
+      [
+        'route_name' => 'test_2',
+        'return' => $second_route,
+      ],
+      [
+        'route_name' => 'test_3',
+        'return' => $third_route,
+      ],
+      [
+        'route_name' => 'test_4',
+        'return' => $fourth_route,
+      ],
+      [
+        'route_name' => '<none>',
+        'return' => $none_route,
+      ],
+    ];
+    foreach ($return_map_values as $values) {
+      $route_name_return_map[] = [$values['route_name'], $values['return']];
+      $routes_names_return_map[] = [[$values['route_name']], $values['return']];
+    }
+    $this->provider = $provider;
+    $this->provider->expects($this->any())
+      ->method('getRouteByName')
+      ->will($this->returnValueMap($route_name_return_map));
+    $provider->expects($this->any())
+      ->method('getRoutesByNames')
+      ->will($this->returnValueMap($routes_names_return_map));
+
+    // Create an alias manager stub.
+    $alias_manager = $this->getMockBuilder('Drupal\Core\Path\AliasManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $alias_manager->expects($this->any())
+      ->method('getAliasByPath')
+      ->will($this->returnCallback([$this, 'aliasManagerCallback']));
+
+    $this->aliasManager = $alias_manager;
+
+    $this->requestStack = new RequestStack();
+    $request = Request::create('/some/path');
+    $this->requestStack->push($request);
+
+    $this->context = new RequestContext();
+    $this->context->fromRequestStack($this->requestStack);
+
+    $processor = new PathProcessorAlias($this->aliasManager);
+    $processor_manager = new PathProcessorManager();
+    $processor_manager->addOutbound($processor, 1000);
+    $this->processorManager = $processor_manager;
+
+    $this->routeProcessorManager = $this->getMockBuilder('Drupal\Core\RouteProcessor\RouteProcessorManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $generator = new UrlGenerator($this->provider, $processor_manager, $this->routeProcessorManager, $this->requestStack, ['http', 'https']);
+    $generator->setContext($this->context);
+    $this->generator = $generator;
+  }
+
+  /**
+   * Return value callback for the getAliasByPath() method on the mock alias
+   * manager.
+   *
+   * Ensures that by default the call to getAliasByPath() will return the first
+   * argument that was passed in. We special-case the paths for which we wish it
+   * to return an actual alias.
+   *
+   * @return string
+   */
+  public function aliasManagerCallback() {
+    $args = func_get_args();
+    switch ($args[0]) {
+      case '/test/one':
+        return '/hello/world';
+      case '/test/two/5':
+        return '/goodbye/cruel/world';
+      case '/<front>':
+        return '/';
+      default:
+        return $args[0];
+    }
+  }
+
+  /**
+   * Confirms that generated routes will have aliased paths.
+   */
+  public function testAliasGeneration() {
+    $url = $this->generator->generate('test_1');
+    $this->assertEquals('/hello/world', $url);
+    // No cacheability to test; UrlGenerator::generate() doesn't support
+    // collecting cacheability metadata.
+
+    $this->routeProcessorManager->expects($this->exactly(3))
+      ->method('processOutbound')
+      ->with($this->anything());
+
+
+    // Check that the two generate methods return the same result.
+    $this->assertGenerateFromRoute('test_1', [], [], $url, (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT));
+
+    $path = $this->generator->getPathFromRoute('test_1');
+    $this->assertEquals('test/one', $path);
+  }
+
+  /**
+   * Confirms that generated routes will have aliased paths using interface constants.
+   */
+  public function testAliasGenerationUsingInterfaceConstants() {
+    $url = $this->generator->generate('test_1', [], UrlGenerator::ABSOLUTE_PATH);
+    $this->assertEquals('/hello/world', $url);
+    // No cacheability to test; UrlGenerator::generate() doesn't support
+    // collecting cacheability metadata.
+
+    $this->routeProcessorManager->expects($this->exactly(3))
+      ->method('processOutbound')
+      ->with($this->anything());
+
+
+    // Check that the two generate methods return the same result.
+    $this->assertGenerateFromRoute('test_1', [], [], $url, (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT));
+
+    $path = $this->generator->getPathFromRoute('test_1');
+    $this->assertEquals('test/one', $path);
+  }
+
+  /**
+   * @covers ::generateFromRoute
+   */
+  public function testUrlGenerationWithDisabledPathProcessing() {
+    $path_processor = $this->prophesize(OutboundPathProcessorInterface::class);
+    $path_processor->processOutbound(Argument::cetera())->shouldNotBeCalled();
+
+    $generator = new UrlGenerator($this->provider, $path_processor->reveal(), $this->routeProcessorManager, $this->requestStack, ['http', 'https']);
+    $generator->setContext($this->context);
+
+    $url = $this->generator->generateFromRoute('test_1', [], ['path_processing' => FALSE]);
+    $this->assertEquals('/test/one', $url);
+  }
+
+  /**
+   * @covers ::generateFromRoute
+   */
+  public function testUrlGenerationWithDisabledPathProcessingByRoute() {
+    $path_processor = $this->prophesize(OutboundPathProcessorInterface::class);
+    $path_processor->processOutbound(Argument::cetera())->shouldNotBeCalled();
+
+    $provider = $this->prophesize(RouteProviderInterface::class);
+    $provider->getRouteByName('test_1')->willReturn(new Route('/test/one', [], [], ['default_url_options' => ['path_processing' => FALSE]]));
+
+    $generator = new UrlGenerator($provider->reveal(), $path_processor->reveal(), $this->routeProcessorManager, $this->requestStack, ['http', 'https']);
+    $generator->setContext($this->context);
+
+    $url = $generator->generateFromRoute('test_1', []);
+    $this->assertEquals('/test/one', $url);
+  }
+
+  /**
+   * @covers ::generateFromRoute
+   */
+  public function testUrlGenerationWithDisabledPathProcessingByRouteAndOptedInPathProcessing() {
+    $path_processor = $this->prophesize(OutboundPathProcessorInterface::class);
+    $path_processor->processOutbound('/test/one', Argument::cetera())->willReturn('/hello/world')->shouldBeCalled();
+
+    $provider = $this->prophesize(RouteProviderInterface::class);
+    $provider->getRouteByName('test_1')->willReturn(new Route('/test/one', [], [], ['default_url_options' => ['path_processing' => FALSE]]));
+
+    $generator = new UrlGenerator($provider->reveal(), $path_processor->reveal(), $this->routeProcessorManager, $this->requestStack, ['http', 'https']);
+    $generator->setContext($this->context);
+
+    $url = $generator->generateFromRoute('test_1', [], ['path_processing' => TRUE]);
+    $this->assertEquals('/hello/world', $url);
+  }
+
+  /**
+   * Tests URL generation in a subdirectory.
+   */
+  public function testGetPathFromRouteWithSubdirectory() {
+    $this->routeProcessorManager->expects($this->once())
+      ->method('processOutbound');
+
+    $path = $this->generator->getPathFromRoute('test_1');
+    $this->assertEquals('test/one', $path);
+  }
+
+  /**
+   * Confirms that generated routes will have aliased paths.
+   */
+  public function testAliasGenerationWithParameters() {
+    $url = $this->generator->generate('test_2', ['narf' => '5']);
+    $this->assertEquals('/goodbye/cruel/world', $url);
+    // No cacheability to test; UrlGenerator::generate() doesn't support
+    // collecting cacheability metadata.
+
+    $this->routeProcessorManager->expects($this->any())
+      ->method('processOutbound')
+      ->with($this->anything());
+
+    $options = ['fragment' => 'top'];
+    // Extra parameters should appear in the query string.
+    $this->assertGenerateFromRoute('test_1', ['zoo' => 5], $options, '/hello/world?zoo=5#top', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT));
+
+    $options = ['query' => ['page' => '1'], 'fragment' => 'bottom'];
+    $this->assertGenerateFromRoute('test_2', ['narf' => 5], $options, '/goodbye/cruel/world?page=1#bottom', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT));
+
+    // Changing the parameters, the route still matches but there is no alias.
+    $this->assertGenerateFromRoute('test_2', ['narf' => 7], $options, '/test/two/7?page=1#bottom', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT));
+
+    $path = $this->generator->getPathFromRoute('test_2', ['narf' => '5']);
+    $this->assertEquals('test/two/5', $path);
+
+    // Specify a query parameter with NULL.
+    $options = ['query' => ['page' => NULL], 'fragment' => 'bottom'];
+    $this->assertGenerateFromRoute('test_2', ['narf' => 5], $options, '/goodbye/cruel/world?page#bottom', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT));
+  }
+
+  /**
+   * Confirms that generated routes will have aliased paths with options.
+   *
+   * @dataProvider providerTestAliasGenerationWithOptions
+   */
+  public function testAliasGenerationWithOptions($route_name, $route_parameters, $options, $expected) {
+    $this->assertGenerateFromRoute($route_name, $route_parameters, $options, $expected, (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT));
+  }
+
+  /**
+   * Provides test data for testAliasGenerationWithOptions.
+   */
+  public function providerTestAliasGenerationWithOptions() {
+    $data = [];
+    // Extra parameters should appear in the query string.
+    $data[] = [
+      'test_1',
+      ['zoo' => '5'],
+      ['fragment' => 'top'],
+      '/hello/world?zoo=5#top',
+    ];
+    $data[] = [
+      'test_2',
+      ['narf' => '5'],
+      ['query' => ['page' => '1'], 'fragment' => 'bottom'],
+      '/goodbye/cruel/world?page=1#bottom',
+    ];
+    // Changing the parameters, the route still matches but there is no alias.
+    $data[] = [
+      'test_2',
+      ['narf' => '7'],
+      ['query' => ['page' => '1'], 'fragment' => 'bottom'],
+      '/test/two/7?page=1#bottom',
+    ];
+    // Query string values containing '/' should be decoded.
+    $data[] = [
+      'test_2',
+      ['narf' => '7'],
+      ['query' => ['page' => '1/2'], 'fragment' => 'bottom'],
+      '/test/two/7?page=1/2#bottom',
+    ];
+    // A NULL query string.
+    $data['query-with-NULL'] = [
+      'test_2',
+      ['narf' => '7'],
+      ['query' => NULL, 'fragment' => 'bottom'],
+      '/test/two/7#bottom',
+    ];
+    return $data;
+  }
+
+  /**
+   * Tests URL generation from route with trailing start and end slashes.
+   */
+  public function testGetPathFromRouteTrailing() {
+    $this->routeProcessorManager->expects($this->once())
+      ->method('processOutbound');
+
+    $path = $this->generator->getPathFromRoute('test_3');
+    $this->assertEquals($path, 'test/two');
+  }
+
+  /**
+   * Confirms that absolute URLs work with generated routes.
+   */
+  public function testAbsoluteURLGeneration() {
+    $url = $this->generator->generate('test_1', [], TRUE);
+    $this->assertEquals('http://localhost/hello/world', $url);
+    // No cacheability to test; UrlGenerator::generate() doesn't support
+    // collecting cacheability metadata.
+
+    $this->routeProcessorManager->expects($this->exactly(2))
+      ->method('processOutbound')
+      ->with($this->anything());
+
+    $options = ['absolute' => TRUE, 'fragment' => 'top'];
+    // Extra parameters should appear in the query string.
+    $this->assertGenerateFromRoute('test_1', ['zoo' => 5], $options, 'http://localhost/hello/world?zoo=5#top', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT)->setCacheContexts(['url.site']));
+  }
+
+  /**
+   * Confirms that absolute URLs work with generated routes using interface constants.
+   */
+  public function testAbsoluteURLGenerationUsingInterfaceConstants() {
+    $url = $this->generator->generate('test_1', [], UrlGenerator::ABSOLUTE_URL);
+    $this->assertEquals('http://localhost/hello/world', $url);
+    // No cacheability to test; UrlGenerator::generate() doesn't support
+    // collecting cacheability metadata.
+
+    $this->routeProcessorManager->expects($this->exactly(2))
+      ->method('processOutbound')
+      ->with($this->anything());
+
+    $options = ['absolute' => TRUE, 'fragment' => 'top'];
+    // Extra parameters should appear in the query string.
+    $this->assertGenerateFromRoute('test_1', ['zoo' => 5], $options, 'http://localhost/hello/world?zoo=5#top', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT)->setCacheContexts(['url.site']));
+  }
+
+  /**
+   * Confirms that explicitly setting the base_url works with generated routes
+   */
+  public function testBaseURLGeneration() {
+    $options = ['base_url' => 'http://www.example.com:8888'];
+    $this->assertGenerateFromRoute('test_1', [], $options, 'http://www.example.com:8888/hello/world', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT));
+
+    $options = ['base_url' => 'http://www.example.com:8888', 'https' => TRUE];
+    $this->assertGenerateFromRoute('test_1', [], $options, 'https://www.example.com:8888/hello/world', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT));
+
+    $options = ['base_url' => 'https://www.example.com:8888', 'https' => FALSE];
+    $this->assertGenerateFromRoute('test_1', [], $options, 'http://www.example.com:8888/hello/world', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT));
+
+    $this->routeProcessorManager->expects($this->exactly(2))
+      ->method('processOutbound')
+      ->with($this->anything());
+
+    $options = ['base_url' => 'http://www.example.com:8888', 'fragment' => 'top'];
+    // Extra parameters should appear in the query string.
+    $this->assertGenerateFromRoute('test_1', ['zoo' => 5], $options, 'http://www.example.com:8888/hello/world?zoo=5#top', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT));
+  }
+
+  /**
+   * Test that the 'scheme' route requirement is respected during url generation.
+   */
+  public function testUrlGenerationWithHttpsRequirement() {
+    $url = $this->generator->generate('test_4', [], TRUE);
+    $this->assertEquals('https://localhost/test/four', $url);
+    // No cacheability to test; UrlGenerator::generate() doesn't support
+    // collecting cacheability metadata.
+
+    $this->routeProcessorManager->expects($this->exactly(2))
+      ->method('processOutbound')
+      ->with($this->anything());
+
+    $options = ['absolute' => TRUE, 'https' => TRUE];
+    $this->assertGenerateFromRoute('test_1', [], $options, 'https://localhost/hello/world', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT)->setCacheContexts(['url.site']));
+  }
+
+  /**
+   * Tests generating a relative URL with no path.
+   *
+   * @param array $options
+   *   An array of URL options.
+   * @param string $expected_url
+   *   The expected relative URL.
+   *
+   * @covers ::generateFromRoute
+   *
+   * @dataProvider providerTestNoPath
+   */
+  public function testNoPath($options, $expected_url) {
+    $url = $this->generator->generateFromRoute('<none>', [], $options);
+    $this->assertEquals($expected_url, $url);
+  }
+
+  /**
+   * Data provider for ::testNoPath().
+   */
+  public function providerTestNoPath() {
+    return [
+      // Empty options.
+      [[], ''],
+      // Query parameters only.
+      [['query' => ['foo' => 'bar']], '?foo=bar'],
+      // Multiple query parameters.
+      [['query' => ['foo' => 'bar', 'baz' => '']], '?foo=bar&baz='],
+      // Fragment only.
+      [['fragment' => 'foo'], '#foo'],
+      // Query parameters and fragment.
+      [['query' => ['bar' => 'baz'], 'fragment' => 'foo'], '?bar=baz#foo'],
+      // Multiple query parameters and fragment.
+      [['query' => ['bar' => 'baz', 'foo' => 'bar'], 'fragment' => 'foo'], '?bar=baz&foo=bar#foo'],
+    ];
+  }
+
+  /**
+   * @covers \Drupal\Core\Routing\UrlGenerator::generateFromRoute
+   *
+   * Note: We use absolute covers to let
+   * \Drupal\Tests\Core\Render\MetadataBubblingUrlGeneratorTest work.
+   */
+  public function testGenerateWithPathProcessorChangingQueryParameter() {
+    $path_processor = $this->getMock(OutboundPathProcessorInterface::CLASS);
+    $path_processor->expects($this->atLeastOnce())
+      ->method('processOutbound')
+      ->willReturnCallback(function ($path, &$options = [], Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
+        $options['query'] = ['zoo' => 5];
+        return $path;
+      });
+    $this->processorManager->addOutbound($path_processor);
+
+    $options = [];
+    $this->assertGenerateFromRoute('test_2', ['narf' => 5], $options, '/goodbye/cruel/world?zoo=5', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT));
+  }
+
+  /**
+   * Asserts \Drupal\Core\Routing\UrlGenerator::generateFromRoute()'s output.
+   *
+   * @param $route_name
+   *   The route name to test.
+   * @param array $route_parameters
+   *   The route parameters to test.
+   * @param array $options
+   *   The options to test.
+   * @param $expected_url
+   *   The expected generated URL string.
+   * @param \Drupal\Core\Render\BubbleableMetadata $expected_bubbleable_metadata
+   *   The expected generated bubbleable metadata.
+   */
+  protected function assertGenerateFromRoute($route_name, array $route_parameters, array $options, $expected_url, BubbleableMetadata $expected_bubbleable_metadata) {
+    // First, test with $collect_cacheability_metadata set to the default value.
+    $url = $this->generator->generateFromRoute($route_name, $route_parameters, $options);
+    $this->assertSame($expected_url, $url);
+
+    // Second, test with it set to TRUE.
+    $generated_url = $this->generator->generateFromRoute($route_name, $route_parameters, $options, TRUE);
+    $this->assertSame($expected_url, $generated_url->getGeneratedUrl());
+    $this->assertEquals($expected_bubbleable_metadata, BubbleableMetadata::createFromObject($generated_url));
+  }
+
+}