RouterTest.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. <?php
  2. namespace Drupal\system\Tests\Routing;
  3. use Drupal\Core\Cache\Cache;
  4. use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
  5. use Drupal\Core\Language\LanguageInterface;
  6. use Drupal\simpletest\WebTestBase;
  7. use Symfony\Component\Routing\Exception\RouteNotFoundException;
  8. use Drupal\Core\Url;
  9. /**
  10. * Functional class for the full integrated routing system.
  11. *
  12. * @group Routing
  13. */
  14. class RouterTest extends WebTestBase {
  15. /**
  16. * Modules to enable.
  17. *
  18. * @var array
  19. */
  20. public static $modules = ['router_test'];
  21. /**
  22. * Confirms that our FinishResponseSubscriber logic works properly.
  23. */
  24. public function testFinishResponseSubscriber() {
  25. $renderer_required_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
  26. $expected_cache_contexts = Cache::mergeContexts($renderer_required_cache_contexts, ['url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT]);
  27. // Confirm that the router can get to a controller.
  28. $this->drupalGet('router_test/test1');
  29. $this->assertRaw('test1', 'The correct string was returned because the route was successful.');
  30. // Check expected headers from FinishResponseSubscriber.
  31. $headers = $this->drupalGetHeaders();
  32. $this->assertEqual($headers['x-ua-compatible'], 'IE=edge');
  33. $this->assertEqual($headers['content-language'], 'en');
  34. $this->assertEqual($headers['x-content-type-options'], 'nosniff');
  35. $this->assertEqual($headers['x-frame-options'], 'SAMEORIGIN');
  36. $this->drupalGet('router_test/test2');
  37. $this->assertRaw('test2', 'The correct string was returned because the route was successful.');
  38. // Check expected headers from FinishResponseSubscriber.
  39. $headers = $this->drupalGetHeaders();
  40. $this->assertEqual($headers['x-drupal-cache-contexts'], implode(' ', $expected_cache_contexts));
  41. $this->assertEqual($headers['x-drupal-cache-tags'], 'config:user.role.anonymous http_response rendered');
  42. // Confirm that the page wrapping is being added, so we're not getting a
  43. // raw body returned.
  44. $this->assertRaw('</html>', 'Page markup was found.');
  45. // In some instances, the subrequest handling may get confused and render
  46. // a page inception style. This test verifies that is not happening.
  47. $this->assertNoPattern('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
  48. // Confirm that route-level access check's cacheability is applied to the
  49. // X-Drupal-Cache-Contexts and X-Drupal-Cache-Tags headers.
  50. // 1. controller result: render array, globally cacheable route access.
  51. $this->drupalGet('router_test/test18');
  52. $headers = $this->drupalGetHeaders();
  53. $this->assertEqual($headers['x-drupal-cache-contexts'], implode(' ', Cache::mergeContexts($renderer_required_cache_contexts, ['url'])));
  54. $this->assertEqual($headers['x-drupal-cache-tags'], 'config:user.role.anonymous foo http_response rendered');
  55. // 2. controller result: render array, per-role cacheable route access.
  56. $this->drupalGet('router_test/test19');
  57. $headers = $this->drupalGetHeaders();
  58. $this->assertEqual($headers['x-drupal-cache-contexts'], implode(' ', Cache::mergeContexts($renderer_required_cache_contexts, ['url', 'user.roles'])));
  59. $this->assertEqual($headers['x-drupal-cache-tags'], 'config:user.role.anonymous foo http_response rendered');
  60. // 3. controller result: Response object, globally cacheable route access.
  61. $this->drupalGet('router_test/test1');
  62. $headers = $this->drupalGetHeaders();
  63. $this->assertFalse(isset($headers['x-drupal-cache-contexts']));
  64. $this->assertFalse(isset($headers['x-drupal-cache-tags']));
  65. // 4. controller result: Response object, per-role cacheable route access.
  66. $this->drupalGet('router_test/test20');
  67. $headers = $this->drupalGetHeaders();
  68. $this->assertFalse(isset($headers['x-drupal-cache-contexts']));
  69. $this->assertFalse(isset($headers['x-drupal-cache-tags']));
  70. // 5. controller result: CacheableResponse object, globally cacheable route access.
  71. $this->drupalGet('router_test/test21');
  72. $headers = $this->drupalGetHeaders();
  73. $this->assertEqual($headers['x-drupal-cache-contexts'], '');
  74. $this->assertEqual($headers['x-drupal-cache-tags'], 'http_response');
  75. // 6. controller result: CacheableResponse object, per-role cacheable route access.
  76. $this->drupalGet('router_test/test22');
  77. $headers = $this->drupalGetHeaders();
  78. $this->assertEqual($headers['x-drupal-cache-contexts'], 'user.roles');
  79. $this->assertEqual($headers['x-drupal-cache-tags'], 'http_response');
  80. // Finally, verify that the X-Drupal-Cache-Contexts and X-Drupal-Cache-Tags
  81. // headers are not sent when their container parameter is set to FALSE.
  82. $this->drupalGet('router_test/test18');
  83. $headers = $this->drupalGetHeaders();
  84. $this->assertTrue(isset($headers['x-drupal-cache-contexts']));
  85. $this->assertTrue(isset($headers['x-drupal-cache-tags']));
  86. $this->setHttpResponseDebugCacheabilityHeaders(FALSE);
  87. $this->drupalGet('router_test/test18');
  88. $headers = $this->drupalGetHeaders();
  89. $this->assertFalse(isset($headers['x-drupal-cache-contexts']));
  90. $this->assertFalse(isset($headers['x-drupal-cache-tags']));
  91. }
  92. /**
  93. * Confirms that multiple routes with the same path do not cause an error.
  94. */
  95. public function testDuplicateRoutePaths() {
  96. // Tests two routes with exactly the same path. The route with the maximum
  97. // fit and lowest sorting route name will match, regardless of the order the
  98. // routes are declared.
  99. // @see \Drupal\Core\Routing\RouteProvider::getRoutesByPath()
  100. $this->drupalGet('router-test/duplicate-path2');
  101. $this->assertResponse(200);
  102. $this->assertRaw('router_test.two_duplicate1');
  103. // Tests three routes with same the path. One of the routes the path has a
  104. // different case.
  105. $this->drupalGet('router-test/case-sensitive-duplicate-path3');
  106. $this->assertResponse(200);
  107. $this->assertRaw('router_test.case_sensitive_duplicate1');
  108. // While case-insensitive matching works, exact matches are preferred.
  109. $this->drupalGet('router-test/case-sensitive-Duplicate-PATH3');
  110. $this->assertResponse(200);
  111. $this->assertRaw('router_test.case_sensitive_duplicate2');
  112. // Test that case-insensitive matching works, falling back to the first
  113. // route defined.
  114. $this->drupalGet('router-test/case-sensitive-Duplicate-Path3');
  115. $this->assertResponse(200);
  116. $this->assertRaw('router_test.case_sensitive_duplicate1');
  117. }
  118. /**
  119. * Confirms that placeholders in paths work correctly.
  120. */
  121. public function testControllerPlaceholders() {
  122. // Test with 0 and a random value.
  123. $values = ["0", $this->randomMachineName()];
  124. foreach ($values as $value) {
  125. $this->drupalGet('router_test/test3/' . $value);
  126. $this->assertResponse(200);
  127. $this->assertRaw($value, 'The correct string was returned because the route was successful.');
  128. }
  129. // Confirm that the page wrapping is being added, so we're not getting a
  130. // raw body returned.
  131. $this->assertRaw('</html>', 'Page markup was found.');
  132. // In some instances, the subrequest handling may get confused and render
  133. // a page inception style. This test verifies that is not happening.
  134. $this->assertNoPattern('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
  135. }
  136. /**
  137. * Confirms that default placeholders in paths work correctly.
  138. */
  139. public function testControllerPlaceholdersDefaultValues() {
  140. $this->drupalGet('router_test/test4');
  141. $this->assertResponse(200);
  142. $this->assertRaw('narf', 'The correct string was returned because the route was successful.');
  143. // Confirm that the page wrapping is being added, so we're not getting a
  144. // raw body returned.
  145. $this->assertRaw('</html>', 'Page markup was found.');
  146. // In some instances, the subrequest handling may get confused and render
  147. // a page inception style. This test verifies that is not happening.
  148. $this->assertNoPattern('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
  149. }
  150. /**
  151. * Confirms that default placeholders in paths work correctly.
  152. */
  153. public function testControllerPlaceholdersDefaultValuesProvided() {
  154. $this->drupalGet('router_test/test4/barf');
  155. $this->assertResponse(200);
  156. $this->assertRaw('barf', 'The correct string was returned because the route was successful.');
  157. // Confirm that the page wrapping is being added, so we're not getting a
  158. // raw body returned.
  159. $this->assertRaw('</html>', 'Page markup was found.');
  160. // In some instances, the subrequest handling may get confused and render
  161. // a page inception style. This test verifies that is not happening.
  162. $this->assertNoPattern('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
  163. }
  164. /**
  165. * Checks that dynamically defined and altered routes work correctly.
  166. *
  167. * @see \Drupal\router_test\RouteSubscriber
  168. */
  169. public function testDynamicRoutes() {
  170. // Test the altered route.
  171. $this->drupalGet('router_test/test6');
  172. $this->assertResponse(200);
  173. $this->assertRaw('test5', 'The correct string was returned because the route was successful.');
  174. }
  175. /**
  176. * Checks that a request with text/html response gets rendered as a page.
  177. */
  178. public function testControllerResolutionPage() {
  179. $this->drupalGet('/router_test/test10');
  180. $this->assertRaw('abcde', 'Correct body was found.');
  181. // Confirm that the page wrapping is being added, so we're not getting a
  182. // raw body returned.
  183. $this->assertRaw('</html>', 'Page markup was found.');
  184. // In some instances, the subrequest handling may get confused and render
  185. // a page inception style. This test verifies that is not happening.
  186. $this->assertNoPattern('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
  187. }
  188. /**
  189. * Checks the generate method on the url generator using the front router.
  190. */
  191. public function testUrlGeneratorFront() {
  192. $front_url = Url::fromRoute('<front>', [], ['absolute' => TRUE]);
  193. // Compare to the site base URL.
  194. $base_url = Url::fromUri('base:/', ['absolute' => TRUE]);
  195. $this->assertIdentical($base_url->toString(), $front_url->toString());
  196. }
  197. /**
  198. * Tests that a page trying to match a path will succeed.
  199. */
  200. public function testRouterMatching() {
  201. $this->drupalGet('router_test/test14/1');
  202. $this->assertResponse(200);
  203. $this->assertText('User route "entity.user.canonical" was matched.');
  204. // Try to match a route for a non-existent user.
  205. $this->drupalGet('router_test/test14/2');
  206. $this->assertResponse(200);
  207. $this->assertText('Route not matched.');
  208. // Check that very long paths don't cause an error.
  209. $path = 'router_test/test1';
  210. $suffix = '/d/r/u/p/a/l';
  211. for ($i = 0; $i < 10; $i++) {
  212. $path .= $suffix;
  213. $this->drupalGet($path);
  214. $this->assertResponse(404);
  215. }
  216. }
  217. /**
  218. * Tests that a PSR-7 response works.
  219. */
  220. public function testRouterResponsePsr7() {
  221. $this->drupalGet('/router_test/test23');
  222. $this->assertResponse(200);
  223. $this->assertText('test23');
  224. }
  225. /**
  226. * Tests the user account on the DIC.
  227. */
  228. public function testUserAccount() {
  229. $account = $this->drupalCreateUser();
  230. $this->drupalLogin($account);
  231. $second_account = $this->drupalCreateUser();
  232. $this->drupalGet('router_test/test12/' . $second_account->id());
  233. $this->assertText($account->getUsername() . ':' . $second_account->getUsername());
  234. $this->assertEqual($account->id(), $this->loggedInUser->id(), 'Ensure that the user was not changed.');
  235. $this->drupalGet('router_test/test13/' . $second_account->id());
  236. $this->assertText($account->getUsername() . ':' . $second_account->getUsername());
  237. $this->assertEqual($account->id(), $this->loggedInUser->id(), 'Ensure that the user was not changed.');
  238. }
  239. /**
  240. * Checks that an ajax request gets rendered as an Ajax response, by mime.
  241. */
  242. public function testControllerResolutionAjax() {
  243. // This will fail with a JSON parse error if the request is not routed to
  244. // The correct controller.
  245. $this->drupalGetAjax('/router_test/test10');
  246. $this->assertEqual($this->drupalGetHeader('Content-Type'), 'application/json', 'Correct mime content type was returned');
  247. $this->assertRaw('abcde', 'Correct body was found.');
  248. }
  249. /**
  250. * Tests that routes no longer exist for a module that has been uninstalled.
  251. */
  252. public function testRouterUninstallInstall() {
  253. \Drupal::service('module_installer')->uninstall(['router_test']);
  254. \Drupal::service('router.builder')->rebuild();
  255. try {
  256. \Drupal::service('router.route_provider')->getRouteByName('router_test.1');
  257. $this->fail('Route was delete on uninstall.');
  258. }
  259. catch (RouteNotFoundException $e) {
  260. $this->pass('Route was delete on uninstall.');
  261. }
  262. // Install the module again.
  263. \Drupal::service('module_installer')->install(['router_test']);
  264. \Drupal::service('router.builder')->rebuild();
  265. $route = \Drupal::service('router.route_provider')->getRouteByName('router_test.1');
  266. $this->assertNotNull($route, 'Route exists after module installation');
  267. }
  268. /**
  269. * Ensure that multiple leading slashes are redirected.
  270. */
  271. public function testLeadingSlashes() {
  272. $request = $this->container->get('request_stack')->getCurrentRequest();
  273. $url = $request->getUriForPath('//router_test/test1');
  274. $this->drupalGet($url);
  275. $this->assertEqual(1, $this->redirectCount, $url . " redirected to " . $this->url);
  276. $this->assertUrl($request->getUriForPath('/router_test/test1'));
  277. // It should not matter how many leading slashes are used and query strings
  278. // should be preserved.
  279. $url = $request->getUriForPath('/////////////////////////////////////////////////router_test/test1') . '?qs=test';
  280. $this->drupalGet($url);
  281. $this->assertEqual(1, $this->redirectCount, $url . " redirected to " . $this->url);
  282. $this->assertUrl($request->getUriForPath('/router_test/test1') . '?qs=test');
  283. // Ensure that external URLs in destination query params are not redirected
  284. // to.
  285. $url = $request->getUriForPath('/////////////////////////////////////////////////router_test/test1') . '?qs=test&destination=http://www.example.com%5c@drupal8alt.test';
  286. $this->drupalGet($url);
  287. $this->assertEqual(1, $this->redirectCount, $url . " redirected to " . $this->url);
  288. $this->assertUrl($request->getUriForPath('/router_test/test1') . '?qs=test');
  289. }
  290. }