RouteCollectionTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\Routing;
  16. use Cake\Http\ServerRequest;
  17. use Cake\Routing\RouteBuilder;
  18. use Cake\Routing\RouteCollection;
  19. use Cake\Routing\Route\Route;
  20. use Cake\TestSuite\TestCase;
  21. class RouteCollectionTest extends TestCase
  22. {
  23. /**
  24. * Setup method
  25. *
  26. * @return void
  27. */
  28. public function setUp()
  29. {
  30. parent::setUp();
  31. $this->collection = new RouteCollection();
  32. }
  33. /**
  34. * Test parse() throws an error on unknown routes.
  35. *
  36. * @expectedException \Cake\Routing\Exception\MissingRouteException
  37. * @expectedExceptionMessage A route matching "/" could not be found
  38. */
  39. public function testParseMissingRoute()
  40. {
  41. $routes = new RouteBuilder($this->collection, '/b', ['key' => 'value']);
  42. $routes->connect('/', ['controller' => 'Articles']);
  43. $routes->connect('/:id', ['controller' => 'Articles', 'action' => 'view']);
  44. $result = $this->collection->parse('/');
  45. $this->assertEquals([], $result, 'Should not match, missing /b');
  46. }
  47. /**
  48. * Test parsing routes.
  49. *
  50. * @return void
  51. */
  52. public function testParse()
  53. {
  54. $routes = new RouteBuilder($this->collection, '/b', ['key' => 'value']);
  55. $routes->connect('/', ['controller' => 'Articles']);
  56. $routes->connect('/:id', ['controller' => 'Articles', 'action' => 'view']);
  57. $routes->connect('/media/search/*', ['controller' => 'Media', 'action' => 'search']);
  58. $result = $this->collection->parse('/b/');
  59. $expected = [
  60. 'controller' => 'Articles',
  61. 'action' => 'index',
  62. 'pass' => [],
  63. 'plugin' => null,
  64. 'key' => 'value',
  65. '_matchedRoute' => '/b',
  66. ];
  67. $this->assertEquals($expected, $result);
  68. $result = $this->collection->parse('/b/the-thing?one=two');
  69. $expected = [
  70. 'controller' => 'Articles',
  71. 'action' => 'view',
  72. 'id' => 'the-thing',
  73. 'pass' => [],
  74. 'plugin' => null,
  75. 'key' => 'value',
  76. '?' => ['one' => 'two'],
  77. '_matchedRoute' => '/b/:id',
  78. ];
  79. $this->assertEquals($expected, $result);
  80. $result = $this->collection->parse('/b/media/search');
  81. $expected = [
  82. 'key' => 'value',
  83. 'pass' => [],
  84. 'plugin' => null,
  85. 'controller' => 'Media',
  86. 'action' => 'search',
  87. '_matchedRoute' => '/b/media/search/*',
  88. ];
  89. $this->assertEquals($expected, $result);
  90. $result = $this->collection->parse('/b/media/search/thing');
  91. $expected = [
  92. 'key' => 'value',
  93. 'pass' => ['thing'],
  94. 'plugin' => null,
  95. 'controller' => 'Media',
  96. 'action' => 'search',
  97. '_matchedRoute' => '/b/media/search/*',
  98. ];
  99. $this->assertEquals($expected, $result);
  100. }
  101. /**
  102. * Test that parse decodes URL data before matching.
  103. * This is important for multibyte URLs that pass through URL rewriting.
  104. *
  105. * @return void
  106. */
  107. public function testParseEncodedBytesInFixedSegment()
  108. {
  109. $routes = new RouteBuilder($this->collection, '/');
  110. $routes->connect('/ден/:day-:month', ['controller' => 'Events', 'action' => 'index']);
  111. $url = '/%D0%B4%D0%B5%D0%BD/15-%D0%BE%D0%BA%D1%82%D0%BE%D0%BC%D0%B2%D1%80%D0%B8?test=foo';
  112. $result = $this->collection->parse($url);
  113. $expected = [
  114. 'pass' => [],
  115. 'plugin' => null,
  116. 'controller' => 'Events',
  117. 'action' => 'index',
  118. 'day' => '15',
  119. 'month' => 'октомври',
  120. '?' => ['test' => 'foo'],
  121. '_matchedRoute' => '/ден/:day-:month',
  122. ];
  123. $this->assertEquals($expected, $result);
  124. }
  125. /**
  126. * Test that parsing checks all the related path scopes.
  127. *
  128. * @return void
  129. */
  130. public function testParseFallback()
  131. {
  132. $routes = new RouteBuilder($this->collection, '/', []);
  133. $routes->resources('Articles');
  134. $routes->connect('/:controller', ['action' => 'index'], ['routeClass' => 'InflectedRoute']);
  135. $routes->connect('/:controller/:action', [], ['routeClass' => 'InflectedRoute']);
  136. $result = $this->collection->parse('/articles/add');
  137. $expected = [
  138. 'controller' => 'Articles',
  139. 'action' => 'add',
  140. 'plugin' => null,
  141. 'pass' => [],
  142. '_matchedRoute' => '/:controller/:action',
  143. ];
  144. $this->assertEquals($expected, $result);
  145. }
  146. /**
  147. * Test parseRequest() throws an error on unknown routes.
  148. *
  149. * @expectedException \Cake\Routing\Exception\MissingRouteException
  150. * @expectedExceptionMessage A route matching "/" could not be found
  151. */
  152. public function testParseRequestMissingRoute()
  153. {
  154. $routes = new RouteBuilder($this->collection, '/b', ['key' => 'value']);
  155. $routes->connect('/', ['controller' => 'Articles']);
  156. $routes->connect('/:id', ['controller' => 'Articles', 'action' => 'view']);
  157. $request = new ServerRequest(['url' => '/']);
  158. $result = $this->collection->parseRequest($request);
  159. $this->assertEquals([], $result, 'Should not match, missing /b');
  160. }
  161. /**
  162. * Test parsing routes.
  163. *
  164. * @return void
  165. */
  166. public function testParseRequest()
  167. {
  168. $routes = new RouteBuilder($this->collection, '/b', ['key' => 'value']);
  169. $routes->connect('/', ['controller' => 'Articles']);
  170. $routes->connect('/:id', ['controller' => 'Articles', 'action' => 'view']);
  171. $routes->connect('/media/search/*', ['controller' => 'Media', 'action' => 'search']);
  172. $request = new ServerRequest(['url' => '/b/']);
  173. $result = $this->collection->parseRequest($request);
  174. $expected = [
  175. 'controller' => 'Articles',
  176. 'action' => 'index',
  177. 'pass' => [],
  178. 'plugin' => null,
  179. 'key' => 'value',
  180. '_matchedRoute' => '/b',
  181. ];
  182. $this->assertEquals($expected, $result);
  183. $request = new ServerRequest(['url' => '/b/the-thing?one=two']);
  184. $result = $this->collection->parseRequest($request);
  185. $expected = [
  186. 'controller' => 'Articles',
  187. 'action' => 'view',
  188. 'id' => 'the-thing',
  189. 'pass' => [],
  190. 'plugin' => null,
  191. 'key' => 'value',
  192. '?' => ['one' => 'two'],
  193. '_matchedRoute' => '/b/:id',
  194. ];
  195. $this->assertEquals($expected, $result);
  196. $request = new ServerRequest(['url' => '/b/media/search']);
  197. $result = $this->collection->parseRequest($request);
  198. $expected = [
  199. 'key' => 'value',
  200. 'pass' => [],
  201. 'plugin' => null,
  202. 'controller' => 'Media',
  203. 'action' => 'search',
  204. '_matchedRoute' => '/b/media/search/*',
  205. ];
  206. $this->assertEquals($expected, $result);
  207. $request = new ServerRequest(['url' => '/b/media/search/thing']);
  208. $result = $this->collection->parseRequest($request);
  209. $expected = [
  210. 'key' => 'value',
  211. 'pass' => ['thing'],
  212. 'plugin' => null,
  213. 'controller' => 'Media',
  214. 'action' => 'search',
  215. '_matchedRoute' => '/b/media/search/*',
  216. ];
  217. $this->assertEquals($expected, $result);
  218. }
  219. /**
  220. * Test match() throws an error on unknown routes.
  221. *
  222. * @expectedException \Cake\Routing\Exception\MissingRouteException
  223. * @expectedExceptionMessage A route matching "array (
  224. */
  225. public function testMatchError()
  226. {
  227. $context = [
  228. '_base' => '/',
  229. '_scheme' => 'http',
  230. '_host' => 'example.org',
  231. ];
  232. $routes = new RouteBuilder($this->collection, '/b');
  233. $routes->connect('/', ['controller' => 'Articles']);
  234. $this->collection->match(['plugin' => null, 'controller' => 'Articles', 'action' => 'add'], $context);
  235. }
  236. /**
  237. * Test matching routes.
  238. *
  239. * @return void
  240. */
  241. public function testMatch()
  242. {
  243. $context = [
  244. '_base' => '/',
  245. '_scheme' => 'http',
  246. '_host' => 'example.org',
  247. ];
  248. $routes = new RouteBuilder($this->collection, '/b');
  249. $routes->connect('/', ['controller' => 'Articles']);
  250. $routes->connect('/:id', ['controller' => 'Articles', 'action' => 'view']);
  251. $result = $this->collection->match(['plugin' => null, 'controller' => 'Articles', 'action' => 'index'], $context);
  252. $this->assertEquals('b', $result);
  253. $result = $this->collection->match(
  254. ['id' => 'thing', 'plugin' => null, 'controller' => 'Articles', 'action' => 'view'],
  255. $context
  256. );
  257. $this->assertEquals('b/thing', $result);
  258. }
  259. /**
  260. * Test matching routes with names
  261. *
  262. * @return void
  263. */
  264. public function testMatchNamed()
  265. {
  266. $context = [
  267. '_base' => '/',
  268. '_scheme' => 'http',
  269. '_host' => 'example.org',
  270. ];
  271. $routes = new RouteBuilder($this->collection, '/b');
  272. $routes->connect('/', ['controller' => 'Articles']);
  273. $routes->connect('/:id', ['controller' => 'Articles', 'action' => 'view'], ['_name' => 'article:view']);
  274. $result = $this->collection->match(['_name' => 'article:view', 'id' => '2'], $context);
  275. $this->assertEquals('/b/2', $result);
  276. $result = $this->collection->match(['plugin' => null, 'controller' => 'Articles', 'action' => 'view', 'id' => '2'], $context);
  277. $this->assertEquals('b/2', $result);
  278. }
  279. /**
  280. * Test match() throws an error on named routes that fail to match
  281. *
  282. * @expectedException \Cake\Routing\Exception\MissingRouteException
  283. * @expectedExceptionMessage A named route was found for "fail", but matching failed
  284. */
  285. public function testMatchNamedError()
  286. {
  287. $context = [
  288. '_base' => '/',
  289. '_scheme' => 'http',
  290. '_host' => 'example.org',
  291. ];
  292. $routes = new RouteBuilder($this->collection, '/b');
  293. $routes->connect('/:lang/articles', ['controller' => 'Articles'], ['_name' => 'fail']);
  294. $this->collection->match(['_name' => 'fail'], $context);
  295. }
  296. /**
  297. * Test matching routes with names and failing
  298. *
  299. * @expectedException \Cake\Routing\Exception\MissingRouteException
  300. * @return void
  301. */
  302. public function testMatchNamedMissingError()
  303. {
  304. $context = [
  305. '_base' => '/',
  306. '_scheme' => 'http',
  307. '_host' => 'example.org',
  308. ];
  309. $routes = new RouteBuilder($this->collection, '/b');
  310. $routes->connect('/:id', ['controller' => 'Articles', 'action' => 'view'], ['_name' => 'article:view']);
  311. $this->collection->match(['_name' => 'derp'], $context);
  312. }
  313. /**
  314. * Test matching plugin routes.
  315. *
  316. * @return void
  317. */
  318. public function testMatchPlugin()
  319. {
  320. $context = [
  321. '_base' => '/',
  322. '_scheme' => 'http',
  323. '_host' => 'example.org',
  324. ];
  325. $routes = new RouteBuilder($this->collection, '/contacts', ['plugin' => 'Contacts']);
  326. $routes->connect('/', ['controller' => 'Contacts']);
  327. $result = $this->collection->match(['plugin' => 'Contacts', 'controller' => 'Contacts', 'action' => 'index'], $context);
  328. $this->assertEquals('contacts', $result);
  329. }
  330. /**
  331. * Test that prefixes increase the specificity of a route match.
  332. *
  333. * Connect the admin route after the non prefixed version, this means
  334. * the non-prefix route would have a more specific name (users:index)
  335. *
  336. * @return void
  337. */
  338. public function testMatchPrefixSpecificity()
  339. {
  340. $context = [
  341. '_base' => '/',
  342. '_scheme' => 'http',
  343. '_host' => 'example.org',
  344. ];
  345. $routes = new RouteBuilder($this->collection, '/');
  346. $routes->connect('/:action/*', ['controller' => 'Users']);
  347. $routes->connect('/admin/:controller', ['prefix' => 'admin', 'action' => 'index']);
  348. $url = [
  349. 'plugin' => null,
  350. 'prefix' => 'admin',
  351. 'controller' => 'Users',
  352. 'action' => 'index'
  353. ];
  354. $result = $this->collection->match($url, $context);
  355. $this->assertEquals('admin/Users', $result);
  356. $url = [
  357. 'plugin' => null,
  358. 'controller' => 'Users',
  359. 'action' => 'index'
  360. ];
  361. $result = $this->collection->match($url, $context);
  362. $this->assertEquals('index', $result);
  363. }
  364. /**
  365. * Test getting named routes.
  366. *
  367. * @return void
  368. */
  369. public function testNamed()
  370. {
  371. $routes = new RouteBuilder($this->collection, '/l');
  372. $routes->connect('/:controller', ['action' => 'index'], ['_name' => 'cntrl']);
  373. $routes->connect('/:controller/:action/*');
  374. $all = $this->collection->named();
  375. $this->assertCount(1, $all);
  376. $this->assertInstanceOf('Cake\Routing\Route\Route', $all['cntrl']);
  377. $this->assertEquals('/l/:controller', $all['cntrl']->template);
  378. }
  379. /**
  380. * Test the add() and routes() method.
  381. *
  382. * @return void
  383. */
  384. public function testAddingRoutes()
  385. {
  386. $one = new Route('/pages/*', ['controller' => 'Pages', 'action' => 'display']);
  387. $two = new Route('/', ['controller' => 'Dashboards', 'action' => 'display']);
  388. $this->collection->add($one);
  389. $this->collection->add($two);
  390. $routes = $this->collection->routes();
  391. $this->assertCount(2, $routes);
  392. $this->assertSame($one, $routes[0]);
  393. $this->assertSame($two, $routes[1]);
  394. }
  395. /**
  396. * Test the add() with some _name.
  397. *
  398. * @expectedException \Cake\Routing\Exception\DuplicateNamedRouteException
  399. *
  400. * @return void
  401. */
  402. public function testAddingDuplicateNamedRoutes()
  403. {
  404. $one = new Route('/pages/*', ['controller' => 'Pages', 'action' => 'display']);
  405. $two = new Route('/', ['controller' => 'Dashboards', 'action' => 'display']);
  406. $this->collection->add($one, ['_name' => 'test']);
  407. $this->collection->add($two, ['_name' => 'test']);
  408. }
  409. /**
  410. * Test basic get/set of extensions.
  411. *
  412. * @return void
  413. */
  414. public function testExtensions()
  415. {
  416. $this->assertEquals([], $this->collection->extensions());
  417. $this->collection->extensions('json');
  418. $this->assertEquals(['json'], $this->collection->extensions());
  419. $this->collection->extensions(['rss', 'xml']);
  420. $this->assertEquals(['json', 'rss', 'xml'], $this->collection->extensions());
  421. $this->collection->extensions(['csv'], false);
  422. $this->assertEquals(['csv'], $this->collection->extensions());
  423. }
  424. }