RouteTest.php 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://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. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 2.0.0
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\Routing\Route;
  16. use Cake\Core\Configure;
  17. use Cake\Http\ServerRequest;
  18. use Cake\Routing\Router;
  19. use Cake\Routing\Route\Route;
  20. use Cake\TestSuite\TestCase;
  21. /**
  22. * Used to expose protected methods for testing.
  23. */
  24. class RouteProtected extends Route
  25. {
  26. /**
  27. * @param $url
  28. * @return array
  29. */
  30. public function parseExtension($url)
  31. {
  32. return $this->_parseExtension($url);
  33. }
  34. }
  35. /**
  36. * Test case for Route
  37. */
  38. class RouteTest extends TestCase
  39. {
  40. /**
  41. * setUp method
  42. *
  43. * @return void
  44. */
  45. public function setUp()
  46. {
  47. parent::setUp();
  48. Configure::write('Routing', ['admin' => null, 'prefixes' => []]);
  49. }
  50. /**
  51. * Test the construction of a Route
  52. *
  53. * @return void
  54. */
  55. public function testConstruction()
  56. {
  57. $route = new Route('/:controller/:action/:id', [], ['id' => '[0-9]+']);
  58. $this->assertEquals('/:controller/:action/:id', $route->template);
  59. $this->assertEquals([], $route->defaults);
  60. $this->assertEquals(['id' => '[0-9]+', '_ext' => []], $route->options);
  61. $this->assertFalse($route->compiled());
  62. }
  63. /**
  64. * Test set middleware in the constructor
  65. *
  66. * @return void
  67. */
  68. public function testConstructorSetMiddleware()
  69. {
  70. $route = new Route('/:controller/:action/*', [], ['_middleware' => ['auth', 'cookie']]);
  71. $this->assertSame(['auth', 'cookie'], $route->getMiddleware());
  72. }
  73. /**
  74. * Test Route compiling.
  75. *
  76. * @return void
  77. */
  78. public function testBasicRouteCompiling()
  79. {
  80. $route = new Route('/', ['controller' => 'pages', 'action' => 'display', 'home']);
  81. $result = $route->compile();
  82. $expected = '#^/*$#';
  83. $this->assertEquals($expected, $result);
  84. $this->assertEquals([], $route->keys);
  85. $route = new Route('/:controller/:action', ['controller' => 'posts']);
  86. $result = $route->compile();
  87. $this->assertRegExp($result, '/posts/edit');
  88. $this->assertRegExp($result, '/posts/super_delete');
  89. $this->assertNotRegExp($result, '/posts');
  90. $this->assertNotRegExp($result, '/posts/super_delete/1');
  91. $this->assertSame($result, $route->compile());
  92. $route = new Route('/posts/foo:id', ['controller' => 'posts', 'action' => 'view']);
  93. $result = $route->compile();
  94. $this->assertRegExp($result, '/posts/foo:1');
  95. $this->assertRegExp($result, '/posts/foo:param');
  96. $this->assertNotRegExp($result, '/posts');
  97. $this->assertNotRegExp($result, '/posts/');
  98. $this->assertEquals(['id'], $route->keys);
  99. $route = new Route('/:plugin/:controller/:action/*', ['plugin' => 'test_plugin', 'action' => 'index']);
  100. $result = $route->compile();
  101. $this->assertRegExp($result, '/test_plugin/posts/index');
  102. $this->assertRegExp($result, '/test_plugin/posts/edit/5');
  103. $this->assertRegExp($result, '/test_plugin/posts/edit/5/name:value/nick:name');
  104. }
  105. /**
  106. * Test that single letter placeholders work.
  107. *
  108. * @return void
  109. */
  110. public function testRouteBuildingSmallPlaceholders()
  111. {
  112. $route = new Route(
  113. '/fighters/:id/move/:x/:y',
  114. ['controller' => 'Fighters', 'action' => 'move'],
  115. ['id' => '\d+', 'x' => '\d+', 'y' => '\d+', 'pass' => ['id', 'x', 'y']]
  116. );
  117. $pattern = $route->compile();
  118. $this->assertRegExp($pattern, '/fighters/123/move/8/42');
  119. $result = $route->match([
  120. 'controller' => 'Fighters',
  121. 'action' => 'move',
  122. 'id' => 123,
  123. 'x' => 8,
  124. 'y' => 42
  125. ]);
  126. $this->assertEquals('/fighters/123/move/8/42', $result);
  127. }
  128. /**
  129. * Test parsing routes with extensions.
  130. *
  131. * @return void
  132. */
  133. public function testRouteParsingWithExtensions()
  134. {
  135. $route = new Route(
  136. '/:controller/:action/*',
  137. [],
  138. ['_ext' => ['json', 'xml']]
  139. );
  140. $result = $route->parse('/posts/index', 'GET');
  141. $this->assertFalse(isset($result['_ext']));
  142. $result = $route->parse('/posts/index.pdf', 'GET');
  143. $this->assertFalse(isset($result['_ext']));
  144. $result = $route->setExtensions(['pdf', 'json', 'xml', 'xml.gz'])->parse('/posts/index.pdf', 'GET');
  145. $this->assertEquals('pdf', $result['_ext']);
  146. $result = $route->parse('/posts/index.json', 'GET');
  147. $this->assertEquals('json', $result['_ext']);
  148. $result = $route->parse('/posts/index.xml', 'GET');
  149. $this->assertEquals('xml', $result['_ext']);
  150. $result = $route->parse('/posts/index.xml.gz', 'GET');
  151. $this->assertEquals('xml.gz', $result['_ext']);
  152. }
  153. /**
  154. * @return array
  155. */
  156. public function provideMatchParseExtension()
  157. {
  158. return [
  159. ['/foo/bar.xml', ['/foo/bar', 'xml'], ['xml', 'json', 'xml.gz']],
  160. ['/foo/bar.json', ['/foo/bar', 'json'], ['xml', 'json', 'xml.gz']],
  161. ['/foo/bar.xml.gz', ['/foo/bar', 'xml.gz'], ['xml', 'json', 'xml.gz']],
  162. ['/foo/with.dots.json.xml.zip', ['/foo/with.dots.json.xml', 'zip'], ['zip']],
  163. ['/foo/confusing.extensions.dots.json.xml.zip', ['/foo/confusing.extensions.dots.json.xml', 'zip'], ['json', 'xml', 'zip']],
  164. ['/foo/confusing.extensions.dots.json.xml', ['/foo/confusing.extensions.dots.json', 'xml'], ['json', 'xml', 'zip']],
  165. ['/foo/confusing.extensions.dots.json', ['/foo/confusing.extensions.dots', 'json'], ['json', 'xml', 'zip']],
  166. ];
  167. }
  168. /**
  169. * Expects _parseExtension to match extensions in URLs
  170. *
  171. * @param string $url
  172. * @param array $expected
  173. * @param array $ext
  174. * @return void
  175. * @dataProvider provideMatchParseExtension
  176. */
  177. public function testMatchParseExtension($url, array $expected, array $ext)
  178. {
  179. $route = new RouteProtected('/:controller/:action/*', [], ['_ext' => $ext]);
  180. $result = $route->parseExtension($url);
  181. $this->assertEquals($expected, $result);
  182. }
  183. /**
  184. * @return array
  185. */
  186. public function provideNoMatchParseExtension()
  187. {
  188. return [
  189. ['/foo/bar', ['xml']],
  190. ['/foo/bar.zip', ['xml']],
  191. ['/foo/bar.xml.zip', ['xml']],
  192. ['/foo/bar.', ['xml']],
  193. ['/foo/bar.xml', []],
  194. ['/foo/bar...xml...zip...', ['xml']]
  195. ];
  196. }
  197. /**
  198. * Expects _parseExtension to not match extensions in URLs
  199. *
  200. * @param string $url
  201. * @param array $ext
  202. * @return void
  203. * @dataProvider provideNoMatchParseExtension
  204. */
  205. public function testNoMatchParseExtension($url, array $ext)
  206. {
  207. $route = new RouteProtected('/:controller/:action/*', [], ['_ext' => $ext]);
  208. list($outUrl, $outExt) = $route->parseExtension($url);
  209. $this->assertEquals($url, $outUrl);
  210. $this->assertNull($outExt);
  211. }
  212. /**
  213. * Expects extensions to be set
  214. *
  215. * @group deprecated
  216. * @return void
  217. */
  218. public function testExtensions()
  219. {
  220. $this->deprecated(function () {
  221. $route = new RouteProtected('/:controller/:action/*', []);
  222. $this->assertEquals([], $route->extensions());
  223. $route->extensions(['xml']);
  224. $this->assertEquals(['xml'], $route->extensions());
  225. });
  226. }
  227. /**
  228. * Expects extensions to be set
  229. *
  230. * @return void
  231. */
  232. public function testSetExtensions()
  233. {
  234. $route = new RouteProtected('/:controller/:action/*', []);
  235. $this->assertEquals([], $route->getExtensions());
  236. $route->setExtensions(['xml']);
  237. $this->assertEquals(['xml'], $route->getExtensions());
  238. $route->setExtensions(['xml', 'json', 'zip']);
  239. $this->assertEquals(['xml', 'json', 'zip'], $route->getExtensions());
  240. $route->setExtensions([]);
  241. $this->assertEquals([], $route->getExtensions());
  242. $route = new RouteProtected('/:controller/:action/*', [], ['_ext' => ['one', 'two']]);
  243. $this->assertEquals(['one', 'two'], $route->getExtensions());
  244. }
  245. /**
  246. * Expects extensions to be return.
  247. *
  248. * @return void
  249. */
  250. public function testGetExtensions()
  251. {
  252. $route = new RouteProtected('/:controller/:action/*', []);
  253. $this->assertEquals([], $route->getExtensions());
  254. $route = new RouteProtected('/:controller/:action/*', [], ['_ext' => ['one', 'two']]);
  255. $this->assertEquals(['one', 'two'], $route->getExtensions());
  256. $route = new RouteProtected('/:controller/:action/*', []);
  257. $this->assertEquals([], $route->getExtensions());
  258. $route->setExtensions(['xml', 'json', 'zip']);
  259. $this->assertEquals(['xml', 'json', 'zip'], $route->getExtensions());
  260. }
  261. /**
  262. * Test that route parameters that overlap don't cause errors.
  263. *
  264. * @return void
  265. */
  266. public function testRouteParameterOverlap()
  267. {
  268. $route = new Route('/invoices/add/:idd/:id', ['controller' => 'invoices', 'action' => 'add']);
  269. $result = $route->compile();
  270. $this->assertRegExp($result, '/invoices/add/1/3');
  271. $route = new Route('/invoices/add/:id/:idd', ['controller' => 'invoices', 'action' => 'add']);
  272. $result = $route->compile();
  273. $this->assertRegExp($result, '/invoices/add/1/3');
  274. }
  275. /**
  276. * Test compiling routes with keys that have patterns
  277. *
  278. * @return void
  279. */
  280. public function testRouteCompilingWithParamPatterns()
  281. {
  282. $route = new Route(
  283. '/:controller/:action/:id',
  284. [],
  285. ['id' => Router::ID]
  286. );
  287. $result = $route->compile();
  288. $this->assertRegExp($result, '/posts/edit/1');
  289. $this->assertRegExp($result, '/posts/view/518098');
  290. $this->assertNotRegExp($result, '/posts/edit/name-of-post');
  291. $this->assertNotRegExp($result, '/posts/edit/4/other:param');
  292. $this->assertEquals(['id', 'controller', 'action'], $route->keys);
  293. $route = new Route(
  294. '/:lang/:controller/:action/:id',
  295. ['controller' => 'testing4'],
  296. ['id' => Router::ID, 'lang' => '[a-z]{3}']
  297. );
  298. $result = $route->compile();
  299. $this->assertRegExp($result, '/eng/posts/edit/1');
  300. $this->assertRegExp($result, '/cze/articles/view/1');
  301. $this->assertNotRegExp($result, '/language/articles/view/2');
  302. $this->assertNotRegExp($result, '/eng/articles/view/name-of-article');
  303. $this->assertEquals(['lang', 'id', 'controller', 'action'], $route->keys);
  304. foreach ([':', '@', ';', '$', '-'] as $delim) {
  305. $route = new Route('/posts/:id' . $delim . ':title');
  306. $result = $route->compile();
  307. $this->assertRegExp($result, '/posts/1' . $delim . 'name-of-article');
  308. $this->assertRegExp($result, '/posts/13244' . $delim . 'name-of_Article[]');
  309. $this->assertNotRegExp($result, '/posts/11!nameofarticle');
  310. $this->assertNotRegExp($result, '/posts/11');
  311. $this->assertEquals(['title', 'id'], $route->keys);
  312. }
  313. $route = new Route(
  314. '/posts/:id::title/:year',
  315. ['controller' => 'posts', 'action' => 'view'],
  316. ['id' => Router::ID, 'year' => Router::YEAR, 'title' => '[a-z-_]+']
  317. );
  318. $result = $route->compile();
  319. $this->assertRegExp($result, '/posts/1:name-of-article/2009/');
  320. $this->assertRegExp($result, '/posts/13244:name-of-article/1999');
  321. $this->assertNotRegExp($result, '/posts/hey_now:nameofarticle');
  322. $this->assertNotRegExp($result, '/posts/:nameofarticle/2009');
  323. $this->assertNotRegExp($result, '/posts/:nameofarticle/01');
  324. $this->assertEquals(['year', 'title', 'id'], $route->keys);
  325. $route = new Route(
  326. '/posts/:url_title-(uuid::id)',
  327. ['controller' => 'posts', 'action' => 'view'],
  328. ['pass' => ['id', 'url_title'], 'id' => Router::ID]
  329. );
  330. $result = $route->compile();
  331. $this->assertRegExp($result, '/posts/some_title_for_article-(uuid:12534)/');
  332. $this->assertRegExp($result, '/posts/some_title_for_article-(uuid:12534)');
  333. $this->assertNotRegExp($result, '/posts/');
  334. $this->assertNotRegExp($result, '/posts/nameofarticle');
  335. $this->assertNotRegExp($result, '/posts/nameofarticle-12347');
  336. $this->assertEquals(['url_title', 'id'], $route->keys);
  337. }
  338. /**
  339. * Test route with unicode
  340. *
  341. * @return void
  342. */
  343. public function testRouteCompilingWithUnicodePatterns()
  344. {
  345. $route = new Route(
  346. '/test/:slug',
  347. ['controller' => 'Pages', 'action' => 'display'],
  348. ['pass' => ['slug'], 'multibytePattern' => false, 'slug' => '[A-zА-я\-\ ]+']
  349. );
  350. $result = $route->compile();
  351. $this->assertNotRegExp($result, '/test/bla-blan-тест');
  352. $route = new Route(
  353. '/test/:slug',
  354. ['controller' => 'Pages', 'action' => 'display'],
  355. ['pass' => ['slug'], 'multibytePattern' => true, 'slug' => '[A-zА-я\-\ ]+']
  356. );
  357. $result = $route->compile();
  358. $this->assertRegExp($result, '/test/bla-blan-тест');
  359. }
  360. /**
  361. * Test more complex route compiling & parsing with mid route greedy stars
  362. * and optional routing parameters
  363. *
  364. * @return void
  365. */
  366. public function testComplexRouteCompilingAndParsing()
  367. {
  368. $route = new Route(
  369. '/posts/:month/:day/:year/*',
  370. ['controller' => 'posts', 'action' => 'view'],
  371. ['year' => Router::YEAR, 'month' => Router::MONTH, 'day' => Router::DAY]
  372. );
  373. $result = $route->compile();
  374. $this->assertRegExp($result, '/posts/08/01/2007/title-of-post');
  375. $result = $route->parse('/posts/08/01/2007/title-of-post', 'GET');
  376. $this->assertCount(7, $result);
  377. $this->assertEquals($result['controller'], 'posts');
  378. $this->assertEquals($result['action'], 'view');
  379. $this->assertEquals($result['year'], '2007');
  380. $this->assertEquals($result['month'], '08');
  381. $this->assertEquals($result['day'], '01');
  382. $this->assertEquals($result['pass'][0], 'title-of-post');
  383. $this->assertEquals($result['_matchedRoute'], '/posts/:month/:day/:year/*');
  384. $route = new Route(
  385. '/:extra/page/:slug/*',
  386. ['controller' => 'pages', 'action' => 'view', 'extra' => null],
  387. ['extra' => '[a-z1-9_]*', 'slug' => '[a-z1-9_]+', 'action' => 'view']
  388. );
  389. $result = $route->compile();
  390. $this->assertRegExp($result, '/some_extra/page/this_is_the_slug');
  391. $this->assertRegExp($result, '/page/this_is_the_slug');
  392. $this->assertEquals(['slug', 'extra'], $route->keys);
  393. $this->assertEquals(['extra' => '[a-z1-9_]*', 'slug' => '[a-z1-9_]+', 'action' => 'view', '_ext' => []], $route->options);
  394. $expected = [
  395. 'controller' => 'pages',
  396. 'action' => 'view'
  397. ];
  398. $this->assertEquals($expected, $route->defaults);
  399. $route = new Route(
  400. '/:controller/:action/*',
  401. ['project' => false],
  402. [
  403. 'controller' => 'source|wiki|commits|tickets|comments|view',
  404. 'action' => 'branches|history|branch|logs|view|start|add|edit|modify'
  405. ]
  406. );
  407. $this->assertFalse($route->parse('/chaw_test/wiki', 'GET'));
  408. $result = $route->compile();
  409. $this->assertNotRegExp($result, '/some_project/source');
  410. $this->assertRegExp($result, '/source/view');
  411. $this->assertRegExp($result, '/source/view/other/params');
  412. $this->assertNotRegExp($result, '/chaw_test/wiki');
  413. $this->assertNotRegExp($result, '/source/wierd_action');
  414. }
  415. /**
  416. * Test that routes match their pattern.
  417. *
  418. * @return void
  419. */
  420. public function testMatchBasic()
  421. {
  422. $route = new Route('/:controller/:action/:id', ['plugin' => null]);
  423. $result = $route->match(['controller' => 'posts', 'action' => 'view', 'plugin' => null]);
  424. $this->assertFalse($result);
  425. $result = $route->match(['plugin' => null, 'controller' => 'posts', 'action' => 'view', 0]);
  426. $this->assertFalse($result);
  427. $result = $route->match(['plugin' => null, 'controller' => 'posts', 'action' => 'view', 'id' => 1]);
  428. $this->assertEquals('/posts/view/1', $result);
  429. $route = new Route('/', ['controller' => 'pages', 'action' => 'display', 'home']);
  430. $result = $route->match(['controller' => 'pages', 'action' => 'display', 'home']);
  431. $this->assertEquals('/', $result);
  432. $result = $route->match(['controller' => 'pages', 'action' => 'display', 'about']);
  433. $this->assertFalse($result);
  434. $route = new Route('/pages/*', ['controller' => 'pages', 'action' => 'display']);
  435. $result = $route->match(['controller' => 'pages', 'action' => 'display', 'home']);
  436. $this->assertEquals('/pages/home', $result);
  437. $result = $route->match(['controller' => 'pages', 'action' => 'display', 'about']);
  438. $this->assertEquals('/pages/about', $result);
  439. $route = new Route('/blog/:action', ['controller' => 'posts']);
  440. $result = $route->match(['controller' => 'posts', 'action' => 'view']);
  441. $this->assertEquals('/blog/view', $result);
  442. $result = $route->match(['controller' => 'posts', 'action' => 'view', 'id' => 2]);
  443. $this->assertEquals('/blog/view?id=2', $result);
  444. $result = $route->match(['controller' => 'nodes', 'action' => 'view']);
  445. $this->assertFalse($result);
  446. $result = $route->match(['controller' => 'posts', 'action' => 'view', 1]);
  447. $this->assertFalse($result);
  448. $route = new Route('/foo/:controller/:action', ['action' => 'index']);
  449. $result = $route->match(['controller' => 'posts', 'action' => 'view']);
  450. $this->assertEquals('/foo/posts/view', $result);
  451. $route = new Route('/:plugin/:id/*', ['controller' => 'posts', 'action' => 'view']);
  452. $result = $route->match(['plugin' => 'test', 'controller' => 'posts', 'action' => 'view', 'id' => '1']);
  453. $this->assertEquals('/test/1/', $result);
  454. $result = $route->match(['plugin' => 'fo', 'controller' => 'posts', 'action' => 'view', 'id' => '1', '0']);
  455. $this->assertEquals('/fo/1/0', $result);
  456. $result = $route->match(['plugin' => 'fo', 'controller' => 'nodes', 'action' => 'view', 'id' => 1]);
  457. $this->assertFalse($result);
  458. $result = $route->match(['plugin' => 'fo', 'controller' => 'posts', 'action' => 'edit', 'id' => 1]);
  459. $this->assertFalse($result);
  460. $route = new Route('/admin/subscriptions/:action/*', [
  461. 'controller' => 'subscribe', 'prefix' => 'admin'
  462. ]);
  463. $url = ['controller' => 'subscribe', 'prefix' => 'admin', 'action' => 'edit', 1];
  464. $result = $route->match($url);
  465. $expected = '/admin/subscriptions/edit/1';
  466. $this->assertEquals($expected, $result);
  467. $url = [
  468. 'controller' => 'subscribe',
  469. 'prefix' => 'admin',
  470. 'action' => 'edit_admin_e',
  471. 1
  472. ];
  473. $result = $route->match($url);
  474. $expected = '/admin/subscriptions/edit_admin_e/1';
  475. $this->assertEquals($expected, $result);
  476. }
  477. /**
  478. * Test match() with persist option
  479. *
  480. * @return void
  481. */
  482. public function testMatchWithPersistOption()
  483. {
  484. $context = [
  485. 'params' => ['lang' => 'en']
  486. ];
  487. $route = new Route('/:lang/:controller/:action', [], ['persist' => ['lang']]);
  488. $result = $route->match(
  489. ['controller' => 'tasks', 'action' => 'add'],
  490. $context
  491. );
  492. $this->assertEquals('/en/tasks/add', $result);
  493. }
  494. /**
  495. * Test match() with _host and other keys.
  496. *
  497. * @return void
  498. */
  499. public function testMatchWithHostKeys()
  500. {
  501. $context = [
  502. '_host' => 'foo.com',
  503. '_scheme' => 'http',
  504. '_port' => 80,
  505. '_base' => ''
  506. ];
  507. $route = new Route('/:controller/:action');
  508. $result = $route->match(
  509. ['controller' => 'posts', 'action' => 'index', '_host' => 'example.com'],
  510. $context
  511. );
  512. $this->assertEquals('http://example.com/posts/index', $result);
  513. $result = $route->match(
  514. ['controller' => 'posts', 'action' => 'index', '_scheme' => 'webcal'],
  515. $context
  516. );
  517. $this->assertEquals('webcal://foo.com/posts/index', $result);
  518. $result = $route->match(
  519. ['controller' => 'posts', 'action' => 'index', '_port' => '8080'],
  520. $context
  521. );
  522. $this->assertEquals('http://foo.com:8080/posts/index', $result);
  523. $result = $route->match(
  524. ['controller' => 'posts', 'action' => 'index', '_base' => '/dir'],
  525. $context
  526. );
  527. $this->assertEquals('/dir/posts/index', $result);
  528. $result = $route->match(
  529. [
  530. 'controller' => 'posts',
  531. 'action' => 'index',
  532. '_port' => '8080',
  533. '_host' => 'example.com',
  534. '_scheme' => 'https',
  535. '_base' => '/dir'
  536. ],
  537. $context
  538. );
  539. $this->assertEquals('https://example.com:8080/dir/posts/index', $result);
  540. }
  541. /**
  542. * Test that the _host option sets the default host.
  543. *
  544. * @return void
  545. */
  546. public function testMatchWithHostOption()
  547. {
  548. $route = new Route(
  549. '/fallback',
  550. ['controller' => 'Articles', 'action' => 'index'],
  551. ['_host' => 'www.example.com']
  552. );
  553. $result = $route->match([
  554. 'controller' => 'Articles',
  555. 'action' => 'index'
  556. ]);
  557. $this->assertSame('http://www.example.com/fallback', $result);
  558. }
  559. /**
  560. * Test wildcard host options
  561. *
  562. * @return void
  563. */
  564. public function testMatchWithHostWildcardOption()
  565. {
  566. $route = new Route(
  567. '/fallback',
  568. ['controller' => 'Articles', 'action' => 'index'],
  569. ['_host' => '*.example.com']
  570. );
  571. $result = $route->match([
  572. 'controller' => 'Articles',
  573. 'action' => 'index'
  574. ]);
  575. $this->assertFalse($result, 'No request context means no match');
  576. $result = $route->match([
  577. 'controller' => 'Articles',
  578. 'action' => 'index',
  579. ], ['_host' => 'wrong.com']);
  580. $this->assertFalse($result, 'Request context has bad host');
  581. $result = $route->match([
  582. 'controller' => 'Articles',
  583. 'action' => 'index',
  584. '_host' => 'wrong.com'
  585. ]);
  586. $this->assertFalse($result, 'Url param is wrong');
  587. $result = $route->match([
  588. 'controller' => 'Articles',
  589. 'action' => 'index',
  590. '_host' => 'foo.example.com'
  591. ]);
  592. $this->assertSame('http://foo.example.com/fallback', $result);
  593. $result = $route->match([
  594. 'controller' => 'Articles',
  595. 'action' => 'index',
  596. ], [
  597. '_host' => 'foo.example.com'
  598. ]);
  599. $this->assertSame('http://foo.example.com/fallback', $result);
  600. }
  601. /**
  602. * Test that non-greedy routes fail with extra passed args
  603. *
  604. * @return void
  605. */
  606. public function testMatchGreedyRouteFailurePassedArg()
  607. {
  608. $route = new Route('/:controller/:action', ['plugin' => null]);
  609. $result = $route->match(['controller' => 'posts', 'action' => 'view', '0']);
  610. $this->assertFalse($result);
  611. $route = new Route('/:controller/:action', ['plugin' => null]);
  612. $result = $route->match(['controller' => 'posts', 'action' => 'view', 'test']);
  613. $this->assertFalse($result);
  614. }
  615. /**
  616. * Test that falsey values do not interrupt a match.
  617. *
  618. * @return void
  619. */
  620. public function testMatchWithFalseyValues()
  621. {
  622. $route = new Route('/:controller/:action/*', ['plugin' => null]);
  623. $result = $route->match([
  624. 'controller' => 'posts', 'action' => 'index', 'plugin' => null, 'admin' => false
  625. ]);
  626. $this->assertEquals('/posts/index/', $result);
  627. }
  628. /**
  629. * Test match() with greedy routes, and passed args.
  630. *
  631. * @return void
  632. */
  633. public function testMatchWithPassedArgs()
  634. {
  635. $route = new Route('/:controller/:action/*', ['plugin' => null]);
  636. $result = $route->match(['controller' => 'posts', 'action' => 'view', 'plugin' => null, 5]);
  637. $this->assertEquals('/posts/view/5', $result);
  638. $result = $route->match(['controller' => 'posts', 'action' => 'view', 'plugin' => null, 0]);
  639. $this->assertEquals('/posts/view/0', $result);
  640. $result = $route->match(['controller' => 'posts', 'action' => 'view', 'plugin' => null, '0']);
  641. $this->assertEquals('/posts/view/0', $result);
  642. $result = $route->match(['controller' => 'posts', 'action' => 'view', 'plugin' => null, 'word space']);
  643. $this->assertEquals('/posts/view/word%20space', $result);
  644. $route = new Route('/test2/*', ['controller' => 'pages', 'action' => 'display', 2]);
  645. $result = $route->match(['controller' => 'pages', 'action' => 'display', 1]);
  646. $this->assertFalse($result);
  647. $result = $route->match(['controller' => 'pages', 'action' => 'display', 2, 'something']);
  648. $this->assertEquals('/test2/something', $result);
  649. $result = $route->match(['controller' => 'pages', 'action' => 'display', 5, 'something']);
  650. $this->assertFalse($result);
  651. }
  652. /**
  653. * Test that the pass option lets you use positional arguments for the
  654. * route elements that were named.
  655. *
  656. * @return void
  657. */
  658. public function testMatchWithPassOption()
  659. {
  660. $route = new Route(
  661. '/blog/:id-:slug',
  662. ['controller' => 'Blog', 'action' => 'view'],
  663. ['pass' => ['id', 'slug']]
  664. );
  665. $result = $route->match([
  666. 'controller' => 'Blog',
  667. 'action' => 'view',
  668. 'id' => 1,
  669. 'slug' => 'second'
  670. ]);
  671. $this->assertEquals('/blog/1-second', $result);
  672. $result = $route->match([
  673. 'controller' => 'Blog',
  674. 'action' => 'view',
  675. 1,
  676. 'second'
  677. ]);
  678. $this->assertEquals('/blog/1-second', $result);
  679. $result = $route->match([
  680. 'controller' => 'Blog',
  681. 'action' => 'view',
  682. 1,
  683. 'second',
  684. 'query' => 'string'
  685. ]);
  686. $this->assertEquals('/blog/1-second?query=string', $result);
  687. $result = $route->match([
  688. 'controller' => 'Blog',
  689. 'action' => 'view',
  690. 1 => 2,
  691. 2 => 'second'
  692. ]);
  693. $this->assertFalse($result, 'Positional args must match exactly.');
  694. }
  695. /**
  696. * Test that match() with pass and greedy routes.
  697. *
  698. * @return void
  699. */
  700. public function testMatchWithPassOptionGreedy()
  701. {
  702. $route = new Route(
  703. '/blog/:id-:slug/*',
  704. ['controller' => 'Blog', 'action' => 'view'],
  705. ['pass' => ['id', 'slug']]
  706. );
  707. $result = $route->match([
  708. 'controller' => 'Blog',
  709. 'action' => 'view',
  710. 'id' => 1,
  711. 'slug' => 'second',
  712. 'third',
  713. 'fourth',
  714. 'query' => 'string'
  715. ]);
  716. $this->assertEquals('/blog/1-second/third/fourth?query=string', $result);
  717. $result = $route->match([
  718. 'controller' => 'Blog',
  719. 'action' => 'view',
  720. 1,
  721. 'second',
  722. 'third',
  723. 'fourth',
  724. 'query' => 'string'
  725. ]);
  726. $this->assertEquals('/blog/1-second/third/fourth?query=string', $result);
  727. }
  728. /**
  729. * Test that extensions work.
  730. *
  731. * @return void
  732. */
  733. public function testMatchWithExtension()
  734. {
  735. $route = new Route('/:controller/:action');
  736. $result = $route->match([
  737. 'controller' => 'posts',
  738. 'action' => 'index',
  739. '_ext' => 'json'
  740. ]);
  741. $this->assertEquals('/posts/index.json', $result);
  742. $route = new Route('/:controller/:action/*');
  743. $result = $route->match([
  744. 'controller' => 'posts',
  745. 'action' => 'index',
  746. '_ext' => 'json',
  747. ]);
  748. $this->assertEquals('/posts/index.json', $result);
  749. $result = $route->match([
  750. 'controller' => 'posts',
  751. 'action' => 'view',
  752. 1,
  753. '_ext' => 'json',
  754. ]);
  755. $this->assertEquals('/posts/view/1.json', $result);
  756. $result = $route->match([
  757. 'controller' => 'posts',
  758. 'action' => 'view',
  759. 1,
  760. '_ext' => 'json',
  761. 'id' => 'b',
  762. 'c' => 'd'
  763. ]);
  764. $this->assertEquals('/posts/view/1.json?id=b&c=d', $result);
  765. $result = $route->match([
  766. 'controller' => 'posts',
  767. 'action' => 'index',
  768. '_ext' => 'json.gz',
  769. ]);
  770. $this->assertEquals('/posts/index.json.gz', $result);
  771. }
  772. /**
  773. * Test that match with patterns works.
  774. *
  775. * @return void
  776. */
  777. public function testMatchWithPatterns()
  778. {
  779. $route = new Route('/:controller/:action/:id', ['plugin' => null], ['id' => '[0-9]+']);
  780. $result = $route->match(['controller' => 'posts', 'action' => 'view', 'id' => 'foo']);
  781. $this->assertFalse($result);
  782. $result = $route->match(['plugin' => null, 'controller' => 'posts', 'action' => 'view', 'id' => '9']);
  783. $this->assertEquals('/posts/view/9', $result);
  784. $result = $route->match(['plugin' => null, 'controller' => 'posts', 'action' => 'view', 'id' => '922']);
  785. $this->assertEquals('/posts/view/922', $result);
  786. $result = $route->match(['plugin' => null, 'controller' => 'posts', 'action' => 'view', 'id' => 'a99']);
  787. $this->assertFalse($result);
  788. }
  789. /**
  790. * Test that match() pulls out extra arguments as query string params.
  791. *
  792. * @return void
  793. */
  794. public function testMatchExtractQueryStringArgs()
  795. {
  796. $route = new Route('/:controller/:action/*');
  797. $result = $route->match([
  798. 'controller' => 'posts',
  799. 'action' => 'index',
  800. 'page' => 1
  801. ]);
  802. $this->assertEquals('/posts/index?page=1', $result);
  803. $result = $route->match([
  804. 'controller' => 'posts',
  805. 'action' => 'index',
  806. 'page' => 0
  807. ]);
  808. $this->assertEquals('/posts/index?page=0', $result);
  809. $result = $route->match([
  810. 'controller' => 'posts',
  811. 'action' => 'index',
  812. 1,
  813. 'page' => 1,
  814. 'dir' => 'desc',
  815. 'order' => 'title'
  816. ]);
  817. $this->assertEquals('/posts/index/1?page=1&dir=desc&order=title', $result);
  818. }
  819. /**
  820. * Test separartor.
  821. *
  822. * @return void
  823. */
  824. public function testQueryStringGeneration()
  825. {
  826. $route = new Route('/:controller/:action/*');
  827. $restore = ini_get('arg_separator.output');
  828. ini_set('arg_separator.output', '&amp;');
  829. $result = $route->match([
  830. 'controller' => 'posts',
  831. 'action' => 'index',
  832. 0,
  833. 'test' => 'var',
  834. 'var2' => 'test2',
  835. 'more' => 'test data'
  836. ]);
  837. $expected = '/posts/index/0?test=var&amp;var2=test2&amp;more=test+data';
  838. $this->assertEquals($expected, $result);
  839. ini_set('arg_separator.output', $restore);
  840. }
  841. /**
  842. * Ensure that parseRequest() calls parse() as that is required
  843. * for backwards compat
  844. *
  845. * @return void
  846. */
  847. public function testParseRequestDelegates()
  848. {
  849. $route = $this->getMockBuilder('Cake\Routing\Route\Route')
  850. ->setMethods(['parse'])
  851. ->setConstructorArgs(['/forward', ['controller' => 'Articles', 'action' => 'index']])
  852. ->getMock();
  853. $route->expects($this->once())
  854. ->method('parse')
  855. ->with('/forward', 'GET')
  856. ->will($this->returnValue('works!'));
  857. $request = new ServerRequest([
  858. 'environment' => [
  859. 'REQUEST_METHOD' => 'GET',
  860. 'PATH_INFO' => '/forward'
  861. ]
  862. ]);
  863. $result = $route->parseRequest($request);
  864. }
  865. /**
  866. * Test that parseRequest() applies host conditions
  867. *
  868. * @return void
  869. */
  870. public function testParseRequestHostConditions()
  871. {
  872. $route = new Route(
  873. '/fallback',
  874. ['controller' => 'Articles', 'action' => 'index'],
  875. ['_host' => '*.example.com']
  876. );
  877. $request = new ServerRequest([
  878. 'environment' => [
  879. 'HTTP_HOST' => 'a.example.com',
  880. 'PATH_INFO' => '/fallback'
  881. ]
  882. ]);
  883. $result = $route->parseRequest($request);
  884. $expected = [
  885. 'controller' => 'Articles',
  886. 'action' => 'index',
  887. 'pass' => [],
  888. '_matchedRoute' => '/fallback'
  889. ];
  890. $this->assertEquals($expected, $result, 'Should match, domain is correct');
  891. $request = new ServerRequest([
  892. 'environment' => [
  893. 'HTTP_HOST' => 'foo.bar.example.com',
  894. 'PATH_INFO' => '/fallback'
  895. ]
  896. ]);
  897. $result = $route->parseRequest($request);
  898. $this->assertEquals($expected, $result, 'Should match, domain is a matching subdomain');
  899. $request = new ServerRequest([
  900. 'environment' => [
  901. 'HTTP_HOST' => 'example.test.com',
  902. 'PATH_INFO' => '/fallback'
  903. ]
  904. ]);
  905. $this->assertFalse($route->parseRequest($request));
  906. }
  907. /**
  908. * test the parse method of Route.
  909. *
  910. * @return void
  911. */
  912. public function testParse()
  913. {
  914. $route = new Route(
  915. '/:controller/:action/:id',
  916. ['controller' => 'testing4', 'id' => null],
  917. ['id' => Router::ID]
  918. );
  919. $route->compile();
  920. $result = $route->parse('/posts/view/1', 'GET');
  921. $this->assertEquals('posts', $result['controller']);
  922. $this->assertEquals('view', $result['action']);
  923. $this->assertEquals('1', $result['id']);
  924. $route = new Route(
  925. '/admin/:controller',
  926. ['prefix' => 'admin', 'admin' => 1, 'action' => 'index']
  927. );
  928. $route->compile();
  929. $result = $route->parse('/admin/', 'GET');
  930. $this->assertFalse($result);
  931. $result = $route->parse('/admin/posts', 'GET');
  932. $this->assertEquals('posts', $result['controller']);
  933. $this->assertEquals('index', $result['action']);
  934. $route = new Route(
  935. '/media/search/*',
  936. ['controller' => 'Media', 'action' => 'search']
  937. );
  938. $result = $route->parse('/media/search', 'GET');
  939. $this->assertEquals('Media', $result['controller']);
  940. $this->assertEquals('search', $result['action']);
  941. $this->assertEquals([], $result['pass']);
  942. $result = $route->parse('/media/search/tv/shows', 'GET');
  943. $this->assertEquals('Media', $result['controller']);
  944. $this->assertEquals('search', $result['action']);
  945. $this->assertEquals(['tv', 'shows'], $result['pass']);
  946. }
  947. /**
  948. * Test that :key elements are urldecoded
  949. *
  950. * @return void
  951. */
  952. public function testParseUrlDecodeElements()
  953. {
  954. $route = new Route(
  955. '/:controller/:slug',
  956. ['action' => 'view']
  957. );
  958. $route->compile();
  959. $result = $route->parse('/posts/%E2%88%82%E2%88%82', 'GET');
  960. $this->assertEquals('posts', $result['controller']);
  961. $this->assertEquals('view', $result['action']);
  962. $this->assertEquals('∂∂', $result['slug']);
  963. $result = $route->parse('/posts/∂∂', 'GET');
  964. $this->assertEquals('posts', $result['controller']);
  965. $this->assertEquals('view', $result['action']);
  966. $this->assertEquals('∂∂', $result['slug']);
  967. }
  968. /**
  969. * Test numerically indexed defaults, get appended to pass
  970. *
  971. * @return void
  972. */
  973. public function testParseWithPassDefaults()
  974. {
  975. $route = new Route('/:controller', ['action' => 'display', 'home']);
  976. $result = $route->parse('/posts', 'GET');
  977. $expected = [
  978. 'controller' => 'posts',
  979. 'action' => 'display',
  980. 'pass' => ['home'],
  981. '_matchedRoute' => '/:controller'
  982. ];
  983. $this->assertEquals($expected, $result);
  984. }
  985. /**
  986. * Test that middleware is returned from parse()
  987. *
  988. * @return void
  989. */
  990. public function testParseWithMiddleware()
  991. {
  992. $route = new Route('/:controller', ['action' => 'display', 'home']);
  993. $route->setMiddleware(['auth', 'cookie']);
  994. $result = $route->parse('/posts', 'GET');
  995. $expected = [
  996. 'controller' => 'posts',
  997. 'action' => 'display',
  998. 'pass' => ['home'],
  999. '_matchedRoute' => '/:controller',
  1000. '_middleware' => ['auth', 'cookie'],
  1001. ];
  1002. $this->assertEquals($expected, $result);
  1003. }
  1004. /**
  1005. * Test that http header conditions can cause route failures.
  1006. *
  1007. * @return void
  1008. */
  1009. public function testParseWithHttpHeaderConditions()
  1010. {
  1011. $route = new Route('/sample', ['controller' => 'posts', 'action' => 'index', '_method' => 'POST']);
  1012. $this->assertFalse($route->parse('/sample', 'GET'));
  1013. $expected = [
  1014. 'controller' => 'posts',
  1015. 'action' => 'index',
  1016. 'pass' => [],
  1017. '_method' => 'POST',
  1018. '_matchedRoute' => '/sample'
  1019. ];
  1020. $this->assertEquals($expected, $route->parse('/sample', 'POST'));
  1021. }
  1022. /**
  1023. * Test that http header conditions can cause route failures.
  1024. *
  1025. * @return void
  1026. */
  1027. public function testParseWithMultipleHttpMethodConditions()
  1028. {
  1029. $route = new Route('/sample', [
  1030. 'controller' => 'posts',
  1031. 'action' => 'index',
  1032. '_method' => ['PUT', 'POST']
  1033. ]);
  1034. $this->assertFalse($route->parse('/sample', 'GET'));
  1035. $expected = [
  1036. 'controller' => 'posts',
  1037. 'action' => 'index',
  1038. 'pass' => [],
  1039. '_method' => ['PUT', 'POST'],
  1040. '_matchedRoute' => '/sample'
  1041. ];
  1042. $this->assertEquals($expected, $route->parse('/sample', 'POST'));
  1043. }
  1044. /**
  1045. * Test deprecated globals reading for method matching
  1046. *
  1047. * @group deprecated
  1048. * @return void
  1049. */
  1050. public function testParseWithMultipleHttpMethodDeprecated()
  1051. {
  1052. $this->deprecated(function () {
  1053. $_SERVER['REQUEST_METHOD'] = 'GET';
  1054. $route = new Route('/sample', [
  1055. 'controller' => 'posts',
  1056. 'action' => 'index',
  1057. '_method' => ['PUT', 'POST']
  1058. ]);
  1059. $this->assertFalse($route->parse('/sample'));
  1060. $_SERVER['REQUEST_METHOD'] = 'POST';
  1061. $expected = [
  1062. 'controller' => 'posts',
  1063. 'action' => 'index',
  1064. 'pass' => [],
  1065. '_method' => ['PUT', 'POST'],
  1066. '_matchedRoute' => '/sample'
  1067. ];
  1068. $this->assertEquals($expected, $route->parse('/sample'));
  1069. });
  1070. }
  1071. /**
  1072. * Test that http header conditions can work with URL generation
  1073. *
  1074. * @return void
  1075. */
  1076. public function testMatchWithMultipleHttpMethodConditions()
  1077. {
  1078. $route = new Route('/sample', [
  1079. 'controller' => 'posts',
  1080. 'action' => 'index',
  1081. '_method' => ['PUT', 'POST']
  1082. ]);
  1083. $url = [
  1084. 'controller' => 'posts',
  1085. 'action' => 'index',
  1086. ];
  1087. $this->assertFalse($route->match($url));
  1088. $url = [
  1089. 'controller' => 'posts',
  1090. 'action' => 'index',
  1091. '_method' => 'GET',
  1092. ];
  1093. $this->assertFalse($route->match($url));
  1094. $url = [
  1095. 'controller' => 'posts',
  1096. 'action' => 'index',
  1097. '_method' => 'PUT',
  1098. ];
  1099. $this->assertEquals('/sample', $route->match($url));
  1100. $url = [
  1101. 'controller' => 'posts',
  1102. 'action' => 'index',
  1103. '_method' => 'POST',
  1104. ];
  1105. $this->assertEquals('/sample', $route->match($url));
  1106. $url = [
  1107. 'controller' => 'posts',
  1108. 'action' => 'index',
  1109. '_method' => ['PUT', 'POST'],
  1110. ];
  1111. $this->assertEquals('/sample', $route->match($url));
  1112. }
  1113. /**
  1114. * Check [method] compatibility.
  1115. *
  1116. * @group deprecated
  1117. * @return void
  1118. */
  1119. public function testMethodCompatibility()
  1120. {
  1121. $this->deprecated(function () {
  1122. $_SERVER['REQUEST_METHOD'] = 'POST';
  1123. $route = new Route('/sample', [
  1124. 'controller' => 'Articles',
  1125. 'action' => 'index',
  1126. '[method]' => 'POST',
  1127. ]);
  1128. $url = [
  1129. 'controller' => 'Articles',
  1130. 'action' => 'index',
  1131. '_method' => 'POST',
  1132. ];
  1133. $this->assertEquals('/sample', $route->match($url));
  1134. $url = [
  1135. 'controller' => 'Articles',
  1136. 'action' => 'index',
  1137. '[method]' => 'POST',
  1138. ];
  1139. $this->assertEquals('/sample', $route->match($url));
  1140. });
  1141. }
  1142. /**
  1143. * Test that patterns work for :action
  1144. *
  1145. * @return void
  1146. */
  1147. public function testPatternOnAction()
  1148. {
  1149. $route = new Route(
  1150. '/blog/:action/*',
  1151. ['controller' => 'blog_posts'],
  1152. ['action' => 'other|actions']
  1153. );
  1154. $result = $route->match(['controller' => 'blog_posts', 'action' => 'foo']);
  1155. $this->assertFalse($result);
  1156. $result = $route->match(['controller' => 'blog_posts', 'action' => 'actions']);
  1157. $this->assertNotEmpty($result);
  1158. $result = $route->parse('/blog/other', 'GET');
  1159. $expected = [
  1160. 'controller' => 'blog_posts',
  1161. 'action' => 'other',
  1162. 'pass' => [],
  1163. '_matchedRoute' => '/blog/:action/*'
  1164. ];
  1165. $this->assertEquals($expected, $result);
  1166. $result = $route->parse('/blog/foobar', 'GET');
  1167. $this->assertFalse($result);
  1168. }
  1169. /**
  1170. * Test the parseArgs method
  1171. *
  1172. * @return void
  1173. */
  1174. public function testParsePassedArgument()
  1175. {
  1176. $route = new Route('/:controller/:action/*');
  1177. $result = $route->parse('/posts/edit/1/2/0', 'GET');
  1178. $expected = [
  1179. 'controller' => 'posts',
  1180. 'action' => 'edit',
  1181. 'pass' => ['1', '2', '0'],
  1182. '_matchedRoute' => '/:controller/:action/*'
  1183. ];
  1184. $this->assertEquals($expected, $result);
  1185. }
  1186. /**
  1187. * Test matching of parameters where one parameter name starts with another parameter name
  1188. *
  1189. * @return void
  1190. */
  1191. public function testMatchSimilarParameters()
  1192. {
  1193. $route = new Route('/:thisParam/:thisParamIsLonger');
  1194. $url = [
  1195. 'thisParamIsLonger' => 'bar',
  1196. 'thisParam' => 'foo',
  1197. ];
  1198. $result = $route->match($url);
  1199. $expected = '/foo/bar';
  1200. $this->assertEquals($expected, $result);
  1201. }
  1202. /**
  1203. * Test match() with trailing ** style routes.
  1204. *
  1205. * @return void
  1206. */
  1207. public function testMatchTrailing()
  1208. {
  1209. $route = new Route('/pages/**', ['controller' => 'pages', 'action' => 'display']);
  1210. $id = 'test/ spaces/漢字/la†în';
  1211. $result = $route->match([
  1212. 'controller' => 'pages',
  1213. 'action' => 'display',
  1214. $id
  1215. ]);
  1216. $expected = '/pages/test/%20spaces/%E6%BC%A2%E5%AD%97/la%E2%80%A0%C3%AEn';
  1217. $this->assertEquals($expected, $result);
  1218. }
  1219. /**
  1220. * Test restructuring args with pass key
  1221. *
  1222. * @return void
  1223. */
  1224. public function testPassArgRestructure()
  1225. {
  1226. $route = new Route('/:controller/:action/:slug', [], [
  1227. 'pass' => ['slug']
  1228. ]);
  1229. $result = $route->parse('/posts/view/my-title', 'GET');
  1230. $expected = [
  1231. 'controller' => 'posts',
  1232. 'action' => 'view',
  1233. 'slug' => 'my-title',
  1234. 'pass' => ['my-title'],
  1235. '_matchedRoute' => '/:controller/:action/:slug'
  1236. ];
  1237. $this->assertEquals($expected, $result, 'Slug should have moved');
  1238. }
  1239. /**
  1240. * Test the /** special type on parsing.
  1241. *
  1242. * @return void
  1243. */
  1244. public function testParseTrailing()
  1245. {
  1246. $route = new Route('/:controller/:action/**');
  1247. $result = $route->parse('/posts/index/1/2/3/foo:bar', 'GET');
  1248. $expected = [
  1249. 'controller' => 'posts',
  1250. 'action' => 'index',
  1251. 'pass' => ['1/2/3/foo:bar'],
  1252. '_matchedRoute' => '/:controller/:action/**',
  1253. ];
  1254. $this->assertEquals($expected, $result);
  1255. $result = $route->parse('/posts/index/http://example.com', 'GET');
  1256. $expected = [
  1257. 'controller' => 'posts',
  1258. 'action' => 'index',
  1259. 'pass' => ['http://example.com'],
  1260. '_matchedRoute' => '/:controller/:action/**',
  1261. ];
  1262. $this->assertEquals($expected, $result);
  1263. }
  1264. /**
  1265. * Test the /** special type on parsing - UTF8.
  1266. *
  1267. * @return void
  1268. */
  1269. public function testParseTrailingUTF8()
  1270. {
  1271. $route = new Route('/category/**', ['controller' => 'categories', 'action' => 'index']);
  1272. $result = $route->parse('/category/%D9%85%D9%88%D8%A8%D8%A7%DB%8C%D9%84', 'GET');
  1273. $expected = [
  1274. 'controller' => 'categories',
  1275. 'action' => 'index',
  1276. 'pass' => ['موبایل'],
  1277. '_matchedRoute' => '/category/**',
  1278. ];
  1279. $this->assertEquals($expected, $result);
  1280. }
  1281. /**
  1282. * Test getName();
  1283. *
  1284. * @return void
  1285. */
  1286. public function testGetName()
  1287. {
  1288. $route = new Route('/foo/bar', [], ['_name' => 'testing']);
  1289. $this->assertEquals('', $route->getName());
  1290. $route = new Route('/:controller/:action');
  1291. $this->assertEquals('_controller:_action', $route->getName());
  1292. $route = new Route('/articles/:action', ['controller' => 'posts']);
  1293. $this->assertEquals('posts:_action', $route->getName());
  1294. $route = new Route('/articles/list', ['controller' => 'posts', 'action' => 'index']);
  1295. $this->assertEquals('posts:index', $route->getName());
  1296. $route = new Route('/:controller/:action', ['action' => 'index']);
  1297. $this->assertEquals('_controller:_action', $route->getName());
  1298. }
  1299. /**
  1300. * Test getName() with plugins.
  1301. *
  1302. * @return void
  1303. */
  1304. public function testGetNamePlugins()
  1305. {
  1306. $route = new Route(
  1307. '/a/:controller/:action',
  1308. ['plugin' => 'asset']
  1309. );
  1310. $this->assertEquals('asset._controller:_action', $route->getName());
  1311. $route = new Route(
  1312. '/a/assets/:action',
  1313. ['plugin' => 'asset', 'controller' => 'assets']
  1314. );
  1315. $this->assertEquals('asset.assets:_action', $route->getName());
  1316. $route = new Route(
  1317. '/assets/get',
  1318. ['plugin' => 'asset', 'controller' => 'assets', 'action' => 'get']
  1319. );
  1320. $this->assertEquals('asset.assets:get', $route->getName());
  1321. }
  1322. /**
  1323. * Test getName() with prefixes.
  1324. *
  1325. * @return void
  1326. */
  1327. public function testGetNamePrefix()
  1328. {
  1329. $route = new Route(
  1330. '/admin/:controller/:action',
  1331. ['prefix' => 'admin']
  1332. );
  1333. $this->assertEquals('admin:_controller:_action', $route->getName());
  1334. $route = new Route(
  1335. '/:prefix/assets/:action',
  1336. ['controller' => 'assets']
  1337. );
  1338. $this->assertEquals('_prefix:assets:_action', $route->getName());
  1339. $route = new Route(
  1340. '/admin/assets/get',
  1341. ['prefix' => 'admin', 'plugin' => 'asset', 'controller' => 'assets', 'action' => 'get']
  1342. );
  1343. $this->assertEquals('admin:asset.assets:get', $route->getName());
  1344. $route = new Route(
  1345. '/:prefix/:plugin/:controller/:action/*',
  1346. []
  1347. );
  1348. $this->assertEquals('_prefix:_plugin._controller:_action', $route->getName());
  1349. }
  1350. /**
  1351. * Test that utf-8 patterns work for :section
  1352. *
  1353. * @return void
  1354. */
  1355. public function testUTF8PatternOnSection()
  1356. {
  1357. $route = new Route(
  1358. '/:section',
  1359. ['plugin' => 'blogs', 'controller' => 'posts', 'action' => 'index'],
  1360. [
  1361. 'persist' => ['section'],
  1362. 'section' => 'آموزش|weblog'
  1363. ]
  1364. );
  1365. $result = $route->parse('/%D8%A2%D9%85%D9%88%D8%B2%D8%B4', 'GET');
  1366. $expected = [
  1367. 'section' => 'آموزش',
  1368. 'plugin' => 'blogs',
  1369. 'controller' => 'posts',
  1370. 'action' => 'index',
  1371. 'pass' => [],
  1372. '_matchedRoute' => '/:section',
  1373. ];
  1374. $this->assertEquals($expected, $result);
  1375. $result = $route->parse('/weblog', 'GET');
  1376. $expected = [
  1377. 'section' => 'weblog',
  1378. 'plugin' => 'blogs',
  1379. 'controller' => 'posts',
  1380. 'action' => 'index',
  1381. 'pass' => [],
  1382. '_matchedRoute' => '/:section',
  1383. ];
  1384. $this->assertEquals($expected, $result);
  1385. }
  1386. /**
  1387. * Test getting the static path for a route.
  1388. *
  1389. * @return void
  1390. */
  1391. public function testStaticPath()
  1392. {
  1393. $route = new Route('/*', ['controller' => 'Pages', 'action' => 'display']);
  1394. $this->assertEquals('/', $route->staticPath());
  1395. $route = new Route('/pages/*', ['controller' => 'Pages', 'action' => 'display']);
  1396. $this->assertEquals('/pages', $route->staticPath());
  1397. $route = new Route('/pages/:id/*', ['controller' => 'Pages', 'action' => 'display']);
  1398. $this->assertEquals('/pages/', $route->staticPath());
  1399. $route = new Route('/:controller/:action/*');
  1400. $this->assertEquals('/', $route->staticPath());
  1401. $route = new Route('/books/reviews', ['controller' => 'Reviews', 'action' => 'index']);
  1402. $this->assertEquals('/books/reviews', $route->staticPath());
  1403. }
  1404. /**
  1405. * Test for __set_state magic method on CakeRoute
  1406. *
  1407. * @return void
  1408. */
  1409. public function testSetState()
  1410. {
  1411. $route = Route::__set_state([
  1412. 'keys' => [],
  1413. 'options' => [],
  1414. 'defaults' => [
  1415. 'controller' => 'pages',
  1416. 'action' => 'display',
  1417. 'home',
  1418. ],
  1419. 'template' => '/',
  1420. '_greedy' => false,
  1421. '_compiledRoute' => null,
  1422. ]);
  1423. $this->assertInstanceOf('Cake\Routing\Route\Route', $route);
  1424. $this->assertSame('/', $route->match(['controller' => 'pages', 'action' => 'display', 'home']));
  1425. $this->assertFalse($route->match(['controller' => 'pages', 'action' => 'display', 'about']));
  1426. $expected = [
  1427. 'controller' => 'pages',
  1428. 'action' => 'display',
  1429. 'pass' => ['home'],
  1430. '_matchedRoute' => '/',
  1431. ];
  1432. $this->assertEquals($expected, $route->parse('/', 'GET'));
  1433. }
  1434. /**
  1435. * Test setting the method on a route.
  1436. *
  1437. * @return void
  1438. */
  1439. public function testSetMethods()
  1440. {
  1441. $route = new Route('/books/reviews', ['controller' => 'Reviews', 'action' => 'index']);
  1442. $result = $route->setMethods(['put']);
  1443. $this->assertSame($result, $route, 'Should return this');
  1444. $this->assertSame(['PUT'], $route->defaults['_method'], 'method is wrong');
  1445. $route->setMethods(['post', 'get', 'patch']);
  1446. $this->assertSame(['POST', 'GET', 'PATCH'], $route->defaults['_method']);
  1447. }
  1448. /**
  1449. * Test setting the method on a route to an invalid method
  1450. *
  1451. * @return void
  1452. */
  1453. public function testSetMethodsInvalid()
  1454. {
  1455. $this->expectException(\InvalidArgumentException::class);
  1456. $this->expectExceptionMessage('Invalid HTTP method received. NOPE is invalid');
  1457. $route = new Route('/books/reviews', ['controller' => 'Reviews', 'action' => 'index']);
  1458. $route->setMethods(['nope']);
  1459. }
  1460. /**
  1461. * Test setting patterns through the method
  1462. *
  1463. * @return void
  1464. */
  1465. public function testSetPatterns()
  1466. {
  1467. $route = new Route('/reviews/:date/:id', ['controller' => 'Reviews', 'action' => 'view']);
  1468. $result = $route->setPatterns([
  1469. 'date' => '\d+\-\d+\-\d+',
  1470. 'id' => '[a-z]+'
  1471. ]);
  1472. $this->assertSame($result, $route, 'Should return this');
  1473. $this->assertArrayHasKey('id', $route->options);
  1474. $this->assertArrayHasKey('date', $route->options);
  1475. $this->assertSame('[a-z]+', $route->options['id']);
  1476. $this->assertArrayNotHasKey('multibytePattern', $route->options);
  1477. $this->assertFalse($route->parse('/reviews/a-b-c/xyz'));
  1478. $this->assertNotEmpty($route->parse('/reviews/2016-05-12/xyz'));
  1479. }
  1480. /**
  1481. * Test setting patterns enables multibyte mode
  1482. *
  1483. * @return void
  1484. */
  1485. public function testSetPatternsMultibyte()
  1486. {
  1487. $route = new Route('/reviews/:accountid/:slug', ['controller' => 'Reviews', 'action' => 'view']);
  1488. $result = $route->setPatterns([
  1489. 'date' => '[A-zА-я\-\ ]+',
  1490. 'accountid' => '[a-z]+'
  1491. ]);
  1492. $this->assertArrayHasKey('multibytePattern', $route->options);
  1493. $this->assertNotEmpty($route->parse('/reviews/abcs/bla-blan-тест'));
  1494. }
  1495. /**
  1496. * Test setting host requirements
  1497. *
  1498. * @return void
  1499. */
  1500. public function testSetHost()
  1501. {
  1502. $route = new Route('/reviews', ['controller' => 'Reviews', 'action' => 'index']);
  1503. $result = $route->setHost('blog.example.com');
  1504. $this->assertSame($result, $route, 'Should return this');
  1505. $request = new ServerRequest([
  1506. 'environment' => [
  1507. 'HTTP_HOST' => 'a.example.com',
  1508. 'PATH_INFO' => '/reviews'
  1509. ]
  1510. ]);
  1511. $this->assertFalse($route->parseRequest($request));
  1512. $uri = $request->getUri();
  1513. $request = $request->withUri($uri->withHost('blog.example.com'));
  1514. $this->assertNotEmpty($route->parseRequest($request));
  1515. }
  1516. /**
  1517. * Test setting pass parameters
  1518. *
  1519. * @return void
  1520. */
  1521. public function testSetPass()
  1522. {
  1523. $route = new Route('/reviews/:date/:id', ['controller' => 'Reviews', 'action' => 'view']);
  1524. $result = $route->setPass(['date', 'id']);
  1525. $this->assertSame($result, $route, 'Should return this');
  1526. $this->assertEquals(['date', 'id'], $route->options['pass']);
  1527. }
  1528. /**
  1529. * Test setting persisted parameters
  1530. *
  1531. * @return void
  1532. */
  1533. public function testSetPersist()
  1534. {
  1535. $route = new Route('/reviews/:date/:id', ['controller' => 'Reviews', 'action' => 'view']);
  1536. $result = $route->setPersist(['date']);
  1537. $this->assertSame($result, $route, 'Should return this');
  1538. $this->assertEquals(['date'], $route->options['persist']);
  1539. }
  1540. /**
  1541. * Test setting/getting middleware.
  1542. *
  1543. * @return void
  1544. */
  1545. public function testSetMiddleware()
  1546. {
  1547. $route = new Route('/reviews/:date/:id', ['controller' => 'Reviews', 'action' => 'view']);
  1548. $result = $route->setMiddleware(['auth', 'cookie']);
  1549. $this->assertSame($result, $route);
  1550. $this->assertSame(['auth', 'cookie'], $route->getMiddleware());
  1551. }
  1552. }