3 namespace Drupal\Tests\system\Functional\Routing;
5 use Drupal\Core\Cache\Cache;
6 use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
7 use Drupal\Core\Language\LanguageInterface;
8 use Drupal\Tests\BrowserTestBase;
9 use Symfony\Component\Routing\Exception\RouteNotFoundException;
13 * Functional class for the full integrated routing system.
17 class RouterTest extends BrowserTestBase {
24 public static $modules = ['router_test'];
27 * Confirms that our FinishResponseSubscriber logic works properly.
29 public function testFinishResponseSubscriber() {
30 $renderer_required_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
31 $expected_cache_contexts = Cache::mergeContexts($renderer_required_cache_contexts, ['url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT]);
33 // Confirm that the router can get to a controller.
34 $this->drupalGet('router_test/test1');
35 $this->assertRaw('test1', 'The correct string was returned because the route was successful.');
36 // Check expected headers from FinishResponseSubscriber.
37 $headers = $this->getSession()->getResponseHeaders();
39 $this->assertEquals($headers['X-UA-Compatible'], ['IE=edge']);
40 $this->assertEquals($headers['Content-language'], ['en']);
41 $this->assertEquals($headers['X-Content-Type-Options'], ['nosniff']);
42 $this->assertEquals($headers['X-Frame-Options'], ['SAMEORIGIN']);
44 $this->drupalGet('router_test/test2');
45 $this->assertRaw('test2', 'The correct string was returned because the route was successful.');
46 // Check expected headers from FinishResponseSubscriber.
47 $headers = $this->drupalGetHeaders();
48 $this->assertEqual($headers['X-Drupal-Cache-Contexts'], [implode(' ', $expected_cache_contexts)]);
49 $this->assertEqual($headers['X-Drupal-Cache-Tags'], ['config:user.role.anonymous http_response rendered']);
50 // Confirm that the page wrapping is being added, so we're not getting a
52 $this->assertRaw('</html>', 'Page markup was found.');
53 // In some instances, the subrequest handling may get confused and render
54 // a page inception style. This test verifies that is not happening.
55 $this->assertSession()->responseNotMatches('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
57 // Confirm that route-level access check's cacheability is applied to the
58 // X-Drupal-Cache-Contexts and X-Drupal-Cache-Tags headers.
59 // 1. controller result: render array, globally cacheable route access.
60 $this->drupalGet('router_test/test18');
61 $headers = $this->drupalGetHeaders();
62 $this->assertEqual($headers['X-Drupal-Cache-Contexts'], [implode(' ', Cache::mergeContexts($renderer_required_cache_contexts, ['url']))]);
63 $this->assertEqual($headers['X-Drupal-Cache-Tags'], ['config:user.role.anonymous foo http_response rendered']);
64 // 2. controller result: render array, per-role cacheable route access.
65 $this->drupalGet('router_test/test19');
66 $headers = $this->drupalGetHeaders();
67 $this->assertEqual($headers['X-Drupal-Cache-Contexts'], [implode(' ', Cache::mergeContexts($renderer_required_cache_contexts, ['url', 'user.roles']))]);
68 $this->assertEqual($headers['X-Drupal-Cache-Tags'], ['config:user.role.anonymous foo http_response rendered']);
69 // 3. controller result: Response object, globally cacheable route access.
70 $this->drupalGet('router_test/test1');
71 $headers = $this->drupalGetHeaders();
72 $this->assertFalse(isset($headers['X-Drupal-Cache-Contexts']));
73 $this->assertFalse(isset($headers['X-Drupal-Cache-Tags']));
74 // 4. controller result: Response object, per-role cacheable route access.
75 $this->drupalGet('router_test/test20');
76 $headers = $this->drupalGetHeaders();
77 $this->assertFalse(isset($headers['X-Drupal-Cache-Contexts']));
78 $this->assertFalse(isset($headers['X-Drupal-Cache-Tags']));
79 // 5. controller result: CacheableResponse object, globally cacheable route access.
80 $this->drupalGet('router_test/test21');
81 $headers = $this->drupalGetHeaders();
82 $this->assertEqual($headers['X-Drupal-Cache-Contexts'], ['']);
83 $this->assertEqual($headers['X-Drupal-Cache-Tags'], ['http_response']);
84 // 6. controller result: CacheableResponse object, per-role cacheable route access.
85 $this->drupalGet('router_test/test22');
86 $headers = $this->drupalGetHeaders();
87 $this->assertEqual($headers['X-Drupal-Cache-Contexts'], ['user.roles']);
88 $this->assertEqual($headers['X-Drupal-Cache-Tags'], ['http_response']);
90 // Finally, verify that the X-Drupal-Cache-Contexts and X-Drupal-Cache-Tags
91 // headers are not sent when their container parameter is set to FALSE.
92 $this->drupalGet('router_test/test18');
93 $headers = $this->drupalGetHeaders();
94 $this->assertTrue(isset($headers['X-Drupal-Cache-Contexts']));
95 $this->assertTrue(isset($headers['X-Drupal-Cache-Tags']));
96 $this->setContainerParameter('http.response.debug_cacheability_headers', FALSE);
97 $this->rebuildContainer();
99 $this->drupalGet('router_test/test18');
100 $headers = $this->drupalGetHeaders();
101 $this->assertFalse(isset($headers['X-Drupal-Cache-Contexts']));
102 $this->assertFalse(isset($headers['X-Drupal-Cache-Tags']));
106 * Confirms that multiple routes with the same path do not cause an error.
108 public function testDuplicateRoutePaths() {
109 // Tests two routes with exactly the same path. The route with the maximum
110 // fit and lowest sorting route name will match, regardless of the order the
111 // routes are declared.
112 // @see \Drupal\Core\Routing\RouteProvider::getRoutesByPath()
113 $this->drupalGet('router-test/duplicate-path2');
114 $this->assertResponse(200);
115 $this->assertRaw('router_test.two_duplicate1');
117 // Tests three routes with same the path. One of the routes the path has a
119 $this->drupalGet('router-test/case-sensitive-duplicate-path3');
120 $this->assertResponse(200);
121 $this->assertRaw('router_test.case_sensitive_duplicate1');
122 // While case-insensitive matching works, exact matches are preferred.
123 $this->drupalGet('router-test/case-sensitive-Duplicate-PATH3');
124 $this->assertResponse(200);
125 $this->assertRaw('router_test.case_sensitive_duplicate2');
126 // Test that case-insensitive matching works, falling back to the first
128 $this->drupalGet('router-test/case-sensitive-Duplicate-Path3');
129 $this->assertResponse(200);
130 $this->assertRaw('router_test.case_sensitive_duplicate1');
134 * Confirms that placeholders in paths work correctly.
136 public function testControllerPlaceholders() {
137 // Test with 0 and a random value.
138 $values = ["0", $this->randomMachineName()];
139 foreach ($values as $value) {
140 $this->drupalGet('router_test/test3/' . $value);
141 $this->assertResponse(200);
142 $this->assertRaw($value, 'The correct string was returned because the route was successful.');
145 // Confirm that the page wrapping is being added, so we're not getting a
146 // raw body returned.
147 $this->assertRaw('</html>', 'Page markup was found.');
149 // In some instances, the subrequest handling may get confused and render
150 // a page inception style. This test verifies that is not happening.
151 $this->assertSession()->responseNotMatches('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
155 * Confirms that default placeholders in paths work correctly.
157 public function testControllerPlaceholdersDefaultValues() {
158 $this->drupalGet('router_test/test4');
159 $this->assertResponse(200);
160 $this->assertRaw('narf', 'The correct string was returned because the route was successful.');
162 // Confirm that the page wrapping is being added, so we're not getting a
163 // raw body returned.
164 $this->assertRaw('</html>', 'Page markup was found.');
166 // In some instances, the subrequest handling may get confused and render
167 // a page inception style. This test verifies that is not happening.
168 $this->assertSession()->responseNotMatches('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
172 * Confirms that default placeholders in paths work correctly.
174 public function testControllerPlaceholdersDefaultValuesProvided() {
175 $this->drupalGet('router_test/test4/barf');
176 $this->assertResponse(200);
177 $this->assertRaw('barf', 'The correct string was returned because the route was successful.');
179 // Confirm that the page wrapping is being added, so we're not getting a
180 // raw body returned.
181 $this->assertRaw('</html>', 'Page markup was found.');
183 // In some instances, the subrequest handling may get confused and render
184 // a page inception style. This test verifies that is not happening.
185 $this->assertSession()->responseNotMatches('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
189 * Checks that dynamically defined and altered routes work correctly.
191 * @see \Drupal\router_test\RouteSubscriber
193 public function testDynamicRoutes() {
194 // Test the altered route.
195 $this->drupalGet('router_test/test6');
196 $this->assertResponse(200);
197 $this->assertRaw('test5', 'The correct string was returned because the route was successful.');
201 * Checks that a request with text/html response gets rendered as a page.
203 public function testControllerResolutionPage() {
204 $this->drupalGet('/router_test/test10');
206 $this->assertRaw('abcde', 'Correct body was found.');
208 // Confirm that the page wrapping is being added, so we're not getting a
209 // raw body returned.
210 $this->assertRaw('</html>', 'Page markup was found.');
212 // In some instances, the subrequest handling may get confused and render
213 // a page inception style. This test verifies that is not happening.
214 $this->assertSession()->responseNotMatches('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
218 * Checks the generate method on the url generator using the front router.
220 public function testUrlGeneratorFront() {
221 $front_url = Url::fromRoute('<front>', [], ['absolute' => TRUE]);
222 // Compare to the site base URL.
223 $base_url = Url::fromUri('base:/', ['absolute' => TRUE]);
224 $this->assertIdentical($base_url->toString(), $front_url->toString());
228 * Tests that a page trying to match a path will succeed.
230 public function testRouterMatching() {
231 $this->drupalGet('router_test/test14/1');
232 $this->assertResponse(200);
233 $this->assertText('User route "entity.user.canonical" was matched.');
235 // Try to match a route for a non-existent user.
236 $this->drupalGet('router_test/test14/2');
237 $this->assertResponse(200);
238 $this->assertText('Route not matched.');
240 // Check that very long paths don't cause an error.
241 $path = 'router_test/test1';
242 $suffix = '/d/r/u/p/a/l';
243 for ($i = 0; $i < 10; $i++) {
245 $this->drupalGet($path);
246 $this->assertResponse(404);
251 * Tests that a PSR-7 response works.
253 public function testRouterResponsePsr7() {
254 $this->drupalGet('/router_test/test23');
255 $this->assertResponse(200);
256 $this->assertText('test23');
260 * Tests the user account on the DIC.
262 public function testUserAccount() {
263 $account = $this->drupalCreateUser();
264 $this->drupalLogin($account);
266 $second_account = $this->drupalCreateUser();
268 $this->drupalGet('router_test/test12/' . $second_account->id());
269 $this->assertText($account->getUsername() . ':' . $second_account->getUsername());
270 $this->assertEqual($account->id(), $this->loggedInUser->id(), 'Ensure that the user was not changed.');
272 $this->drupalGet('router_test/test13/' . $second_account->id());
273 $this->assertText($account->getUsername() . ':' . $second_account->getUsername());
274 $this->assertEqual($account->id(), $this->loggedInUser->id(), 'Ensure that the user was not changed.');
278 * Checks that an ajax request gets rendered as an Ajax response, by mime.
280 public function testControllerResolutionAjax() {
281 // This will fail with a JSON parse error if the request is not routed to
282 // The correct controller.
283 $options['query'][MainContentViewSubscriber::WRAPPER_FORMAT] = 'drupal_ajax';
284 $headers[] = 'X-Requested-With: XMLHttpRequest';
285 $this->drupalGet('/router_test/test10', $options, $headers);
287 $this->assertEqual($this->drupalGetHeader('Content-Type'), 'application/json', 'Correct mime content type was returned');
289 $this->assertRaw('abcde', 'Correct body was found.');
293 * Tests that routes no longer exist for a module that has been uninstalled.
295 public function testRouterUninstallInstall() {
296 \Drupal::service('module_installer')->uninstall(['router_test']);
297 \Drupal::service('router.builder')->rebuild();
299 \Drupal::service('router.route_provider')->getRouteByName('router_test.1');
300 $this->fail('Route was delete on uninstall.');
302 catch (RouteNotFoundException $e) {
303 $this->pass('Route was delete on uninstall.');
305 // Install the module again.
306 \Drupal::service('module_installer')->install(['router_test']);
307 \Drupal::service('router.builder')->rebuild();
308 $route = \Drupal::service('router.route_provider')->getRouteByName('router_test.1');
309 $this->assertNotNull($route, 'Route exists after module installation');
313 * Ensure that multiple leading slashes are redirected.
315 public function testLeadingSlashes() {
316 $request = $this->container->get('request_stack')->getCurrentRequest();
317 $url = $request->getUriForPath('//router_test/test1');
318 $this->drupalGet($url);
319 $this->assertUrl($request->getUriForPath('/router_test/test1'));
321 // It should not matter how many leading slashes are used and query strings
322 // should be preserved.
323 $url = $request->getUriForPath('/////////////////////////////////////////////////router_test/test1') . '?qs=test';
324 $this->drupalGet($url);
325 $this->assertUrl($request->getUriForPath('/router_test/test1') . '?qs=test');
327 // Ensure that external URLs in destination query params are not redirected
329 $url = $request->getUriForPath('/////////////////////////////////////////////////router_test/test1') . '?qs=test&destination=http://www.example.com%5c@drupal8alt.test';
330 $this->drupalGet($url);
331 $this->assertUrl($request->getUriForPath('/router_test/test1') . '?qs=test');