You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

561 lines
19 KiB

8 years ago
  1. <?php
  2. namespace FastRoute\Dispatcher;
  3. use FastRoute\RouteCollector;
  4. abstract class DispatcherTest extends \PHPUnit_Framework_TestCase {
  5. /**
  6. * Delegate dispatcher selection to child test classes
  7. */
  8. abstract protected function getDispatcherClass();
  9. /**
  10. * Delegate dataGenerator selection to child test classes
  11. */
  12. abstract protected function getDataGeneratorClass();
  13. /**
  14. * Set appropriate options for the specific Dispatcher class we're testing
  15. */
  16. private function generateDispatcherOptions() {
  17. return [
  18. 'dataGenerator' => $this->getDataGeneratorClass(),
  19. 'dispatcher' => $this->getDispatcherClass()
  20. ];
  21. }
  22. /**
  23. * @dataProvider provideFoundDispatchCases
  24. */
  25. public function testFoundDispatches($method, $uri, $callback, $handler, $argDict) {
  26. $dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions());
  27. $info = $dispatcher->dispatch($method, $uri);
  28. $this->assertSame($dispatcher::FOUND, $info[0]);
  29. $this->assertSame($handler, $info[1]);
  30. $this->assertSame($argDict, $info[2]);
  31. }
  32. /**
  33. * @dataProvider provideNotFoundDispatchCases
  34. */
  35. public function testNotFoundDispatches($method, $uri, $callback) {
  36. $dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions());
  37. $routeInfo = $dispatcher->dispatch($method, $uri);
  38. $this->assertFalse(isset($routeInfo[1]),
  39. "NOT_FOUND result must only contain a single element in the returned info array"
  40. );
  41. $this->assertSame($dispatcher::NOT_FOUND, $routeInfo[0]);
  42. }
  43. /**
  44. * @dataProvider provideMethodNotAllowedDispatchCases
  45. */
  46. public function testMethodNotAllowedDispatches($method, $uri, $callback, $availableMethods) {
  47. $dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions());
  48. $routeInfo = $dispatcher->dispatch($method, $uri);
  49. $this->assertTrue(isset($routeInfo[1]),
  50. "METHOD_NOT_ALLOWED result must return an array of allowed methods at index 1"
  51. );
  52. list($routedStatus, $methodArray) = $dispatcher->dispatch($method, $uri);
  53. $this->assertSame($dispatcher::METHOD_NOT_ALLOWED, $routedStatus);
  54. $this->assertSame($availableMethods, $methodArray);
  55. }
  56. /**
  57. * @expectedException \FastRoute\BadRouteException
  58. * @expectedExceptionMessage Cannot use the same placeholder "test" twice
  59. */
  60. public function testDuplicateVariableNameError() {
  61. \FastRoute\simpleDispatcher(function(RouteCollector $r) {
  62. $r->addRoute('GET', '/foo/{test}/{test:\d+}', 'handler0');
  63. }, $this->generateDispatcherOptions());
  64. }
  65. /**
  66. * @expectedException \FastRoute\BadRouteException
  67. * @expectedExceptionMessage Cannot register two routes matching "/user/([^/]+)" for method "GET"
  68. */
  69. public function testDuplicateVariableRoute() {
  70. \FastRoute\simpleDispatcher(function(RouteCollector $r) {
  71. $r->addRoute('GET', '/user/{id}', 'handler0'); // oops, forgot \d+ restriction ;)
  72. $r->addRoute('GET', '/user/{name}', 'handler1');
  73. }, $this->generateDispatcherOptions());
  74. }
  75. /**
  76. * @expectedException \FastRoute\BadRouteException
  77. * @expectedExceptionMessage Cannot register two routes matching "/user" for method "GET"
  78. */
  79. public function testDuplicateStaticRoute() {
  80. \FastRoute\simpleDispatcher(function(RouteCollector $r) {
  81. $r->addRoute('GET', '/user', 'handler0');
  82. $r->addRoute('GET', '/user', 'handler1');
  83. }, $this->generateDispatcherOptions());
  84. }
  85. /**
  86. * @expectedException \FastRoute\BadRouteException
  87. * @expectedExceptionMessage Static route "/user/nikic" is shadowed by previously defined variable route "/user/([^/]+)" for method "GET"
  88. */
  89. public function testShadowedStaticRoute() {
  90. \FastRoute\simpleDispatcher(function(RouteCollector $r) {
  91. $r->addRoute('GET', '/user/{name}', 'handler0');
  92. $r->addRoute('GET', '/user/nikic', 'handler1');
  93. }, $this->generateDispatcherOptions());
  94. }
  95. /**
  96. * @expectedException \FastRoute\BadRouteException
  97. * @expectedExceptionMessage Regex "(en|de)" for parameter "lang" contains a capturing group
  98. */
  99. public function testCapturing() {
  100. \FastRoute\simpleDispatcher(function(RouteCollector $r) {
  101. $r->addRoute('GET', '/{lang:(en|de)}', 'handler0');
  102. }, $this->generateDispatcherOptions());
  103. }
  104. public function provideFoundDispatchCases() {
  105. $cases = [];
  106. // 0 -------------------------------------------------------------------------------------->
  107. $callback = function(RouteCollector $r) {
  108. $r->addRoute('GET', '/resource/123/456', 'handler0');
  109. };
  110. $method = 'GET';
  111. $uri = '/resource/123/456';
  112. $handler = 'handler0';
  113. $argDict = [];
  114. $cases[] = [$method, $uri, $callback, $handler, $argDict];
  115. // 1 -------------------------------------------------------------------------------------->
  116. $callback = function(RouteCollector $r) {
  117. $r->addRoute('GET', '/handler0', 'handler0');
  118. $r->addRoute('GET', '/handler1', 'handler1');
  119. $r->addRoute('GET', '/handler2', 'handler2');
  120. };
  121. $method = 'GET';
  122. $uri = '/handler2';
  123. $handler = 'handler2';
  124. $argDict = [];
  125. $cases[] = [$method, $uri, $callback, $handler, $argDict];
  126. // 2 -------------------------------------------------------------------------------------->
  127. $callback = function(RouteCollector $r) {
  128. $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0');
  129. $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1');
  130. $r->addRoute('GET', '/user/{name}', 'handler2');
  131. };
  132. $method = 'GET';
  133. $uri = '/user/rdlowrey';
  134. $handler = 'handler2';
  135. $argDict = ['name' => 'rdlowrey'];
  136. $cases[] = [$method, $uri, $callback, $handler, $argDict];
  137. // 3 -------------------------------------------------------------------------------------->
  138. // reuse $callback from #2
  139. $method = 'GET';
  140. $uri = '/user/12345';
  141. $handler = 'handler1';
  142. $argDict = ['id' => '12345'];
  143. $cases[] = [$method, $uri, $callback, $handler, $argDict];
  144. // 4 -------------------------------------------------------------------------------------->
  145. // reuse $callback from #3
  146. $method = 'GET';
  147. $uri = '/user/NaN';
  148. $handler = 'handler2';
  149. $argDict = ['name' => 'NaN'];
  150. $cases[] = [$method, $uri, $callback, $handler, $argDict];
  151. // 5 -------------------------------------------------------------------------------------->
  152. // reuse $callback from #4
  153. $method = 'GET';
  154. $uri = '/user/rdlowrey/12345';
  155. $handler = 'handler0';
  156. $argDict = ['name' => 'rdlowrey', 'id' => '12345'];
  157. $cases[] = [$method, $uri, $callback, $handler, $argDict];
  158. // 6 -------------------------------------------------------------------------------------->
  159. $callback = function(RouteCollector $r) {
  160. $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler0');
  161. $r->addRoute('GET', '/user/12345/extension', 'handler1');
  162. $r->addRoute('GET', '/user/{id:[0-9]+}.{extension}', 'handler2');
  163. };
  164. $method = 'GET';
  165. $uri = '/user/12345.svg';
  166. $handler = 'handler2';
  167. $argDict = ['id' => '12345', 'extension' => 'svg'];
  168. $cases[] = [$method, $uri, $callback, $handler, $argDict];
  169. // 7 ----- Test GET method fallback on HEAD route miss ------------------------------------>
  170. $callback = function(RouteCollector $r) {
  171. $r->addRoute('GET', '/user/{name}', 'handler0');
  172. $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler1');
  173. $r->addRoute('GET', '/static0', 'handler2');
  174. $r->addRoute('GET', '/static1', 'handler3');
  175. $r->addRoute('HEAD', '/static1', 'handler4');
  176. };
  177. $method = 'HEAD';
  178. $uri = '/user/rdlowrey';
  179. $handler = 'handler0';
  180. $argDict = ['name' => 'rdlowrey'];
  181. $cases[] = [$method, $uri, $callback, $handler, $argDict];
  182. // 8 ----- Test GET method fallback on HEAD route miss ------------------------------------>
  183. // reuse $callback from #7
  184. $method = 'HEAD';
  185. $uri = '/user/rdlowrey/1234';
  186. $handler = 'handler1';
  187. $argDict = ['name' => 'rdlowrey', 'id' => '1234'];
  188. $cases[] = [$method, $uri, $callback, $handler, $argDict];
  189. // 9 ----- Test GET method fallback on HEAD route miss ------------------------------------>
  190. // reuse $callback from #8
  191. $method = 'HEAD';
  192. $uri = '/static0';
  193. $handler = 'handler2';
  194. $argDict = [];
  195. $cases[] = [$method, $uri, $callback, $handler, $argDict];
  196. // 10 ---- Test existing HEAD route used if available (no fallback) ----------------------->
  197. // reuse $callback from #9
  198. $method = 'HEAD';
  199. $uri = '/static1';
  200. $handler = 'handler4';
  201. $argDict = [];
  202. $cases[] = [$method, $uri, $callback, $handler, $argDict];
  203. // 11 ---- More specified routes are not shadowed by less specific of another method ------>
  204. $callback = function(RouteCollector $r) {
  205. $r->addRoute('GET', '/user/{name}', 'handler0');
  206. $r->addRoute('POST', '/user/{name:[a-z]+}', 'handler1');
  207. };
  208. $method = 'POST';
  209. $uri = '/user/rdlowrey';
  210. $handler = 'handler1';
  211. $argDict = ['name' => 'rdlowrey'];
  212. $cases[] = [$method, $uri, $callback, $handler, $argDict];
  213. // 12 ---- Handler of more specific routes is used, if it occurs first -------------------->
  214. $callback = function(RouteCollector $r) {
  215. $r->addRoute('GET', '/user/{name}', 'handler0');
  216. $r->addRoute('POST', '/user/{name:[a-z]+}', 'handler1');
  217. $r->addRoute('POST', '/user/{name}', 'handler2');
  218. };
  219. $method = 'POST';
  220. $uri = '/user/rdlowrey';
  221. $handler = 'handler1';
  222. $argDict = ['name' => 'rdlowrey'];
  223. $cases[] = [$method, $uri, $callback, $handler, $argDict];
  224. // 13 ---- Route with constant suffix ----------------------------------------------------->
  225. $callback = function(RouteCollector $r) {
  226. $r->addRoute('GET', '/user/{name}', 'handler0');
  227. $r->addRoute('GET', '/user/{name}/edit', 'handler1');
  228. };
  229. $method = 'GET';
  230. $uri = '/user/rdlowrey/edit';
  231. $handler = 'handler1';
  232. $argDict = ['name' => 'rdlowrey'];
  233. $cases[] = [$method, $uri, $callback, $handler, $argDict];
  234. // 14 ---- Handle multiple methods with the same handler ---------------------------------->
  235. $callback = function(RouteCollector $r) {
  236. $r->addRoute(['GET', 'POST'], '/user', 'handlerGetPost');
  237. $r->addRoute(['DELETE'], '/user', 'handlerDelete');
  238. $r->addRoute([], '/user', 'handlerNone');
  239. };
  240. $argDict = [];
  241. $cases[] = ['GET', '/user', $callback, 'handlerGetPost', $argDict];
  242. $cases[] = ['POST', '/user', $callback, 'handlerGetPost', $argDict];
  243. $cases[] = ['DELETE', '/user', $callback, 'handlerDelete', $argDict];
  244. // 15 ----
  245. $callback = function(RouteCollector $r) {
  246. $r->addRoute('POST', '/user.json', 'handler0');
  247. $r->addRoute('GET', '/{entity}.json', 'handler1');
  248. };
  249. $cases[] = ['GET', '/user.json', $callback, 'handler1', ['entity' => 'user']];
  250. // 16 ----
  251. $callback = function(RouteCollector $r) {
  252. $r->addRoute('GET', '', 'handler0');
  253. };
  254. $cases[] = ['GET', '', $callback, 'handler0', []];
  255. // 17 ----
  256. $callback = function(RouteCollector $r) {
  257. $r->addRoute('HEAD', '/a/{foo}', 'handler0');
  258. $r->addRoute('GET', '/b/{foo}', 'handler1');
  259. };
  260. $cases[] = ['HEAD', '/b/bar', $callback, 'handler1', ['foo' => 'bar']];
  261. // 18 ----
  262. $callback = function(RouteCollector $r) {
  263. $r->addRoute('HEAD', '/a', 'handler0');
  264. $r->addRoute('GET', '/b', 'handler1');
  265. };
  266. $cases[] = ['HEAD', '/b', $callback, 'handler1', []];
  267. // 19 ----
  268. $callback = function(RouteCollector $r) {
  269. $r->addRoute('GET', '/foo', 'handler0');
  270. $r->addRoute('HEAD', '/{bar}', 'handler1');
  271. };
  272. $cases[] = ['HEAD', '/foo', $callback, 'handler1', ['bar' => 'foo']];
  273. // 20 ----
  274. $callback = function(RouteCollector $r) {
  275. $r->addRoute('*', '/user', 'handler0');
  276. $r->addRoute('*', '/{user}', 'handler1');
  277. $r->addRoute('GET', '/user', 'handler2');
  278. };
  279. $cases[] = ['GET', '/user', $callback, 'handler2', []];
  280. // 21 ----
  281. $callback = function(RouteCollector $r) {
  282. $r->addRoute('*', '/user', 'handler0');
  283. $r->addRoute('GET', '/user', 'handler1');
  284. };
  285. $cases[] = ['POST', '/user', $callback, 'handler0', []];
  286. // 22 ----
  287. $cases[] = ['HEAD', '/user', $callback, 'handler1', []];
  288. // 23 ----
  289. $callback = function(RouteCollector $r) {
  290. $r->addRoute('GET', '/{bar}', 'handler0');
  291. $r->addRoute('*', '/foo', 'handler1');
  292. };
  293. $cases[] = ['GET', '/foo', $callback, 'handler0', ['bar' => 'foo']];
  294. // x -------------------------------------------------------------------------------------->
  295. return $cases;
  296. }
  297. public function provideNotFoundDispatchCases() {
  298. $cases = [];
  299. // 0 -------------------------------------------------------------------------------------->
  300. $callback = function(RouteCollector $r) {
  301. $r->addRoute('GET', '/resource/123/456', 'handler0');
  302. };
  303. $method = 'GET';
  304. $uri = '/not-found';
  305. $cases[] = [$method, $uri, $callback];
  306. // 1 -------------------------------------------------------------------------------------->
  307. // reuse callback from #0
  308. $method = 'POST';
  309. $uri = '/not-found';
  310. $cases[] = [$method, $uri, $callback];
  311. // 2 -------------------------------------------------------------------------------------->
  312. // reuse callback from #1
  313. $method = 'PUT';
  314. $uri = '/not-found';
  315. $cases[] = [$method, $uri, $callback];
  316. // 3 -------------------------------------------------------------------------------------->
  317. $callback = function(RouteCollector $r) {
  318. $r->addRoute('GET', '/handler0', 'handler0');
  319. $r->addRoute('GET', '/handler1', 'handler1');
  320. $r->addRoute('GET', '/handler2', 'handler2');
  321. };
  322. $method = 'GET';
  323. $uri = '/not-found';
  324. $cases[] = [$method, $uri, $callback];
  325. // 4 -------------------------------------------------------------------------------------->
  326. $callback = function(RouteCollector $r) {
  327. $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0');
  328. $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1');
  329. $r->addRoute('GET', '/user/{name}', 'handler2');
  330. };
  331. $method = 'GET';
  332. $uri = '/not-found';
  333. $cases[] = [$method, $uri, $callback];
  334. // 5 -------------------------------------------------------------------------------------->
  335. // reuse callback from #4
  336. $method = 'GET';
  337. $uri = '/user/rdlowrey/12345/not-found';
  338. $cases[] = [$method, $uri, $callback];
  339. // 6 -------------------------------------------------------------------------------------->
  340. // reuse callback from #5
  341. $method = 'HEAD';
  342. $cases[] = array($method, $uri, $callback);
  343. // x -------------------------------------------------------------------------------------->
  344. return $cases;
  345. }
  346. public function provideMethodNotAllowedDispatchCases() {
  347. $cases = [];
  348. // 0 -------------------------------------------------------------------------------------->
  349. $callback = function(RouteCollector $r) {
  350. $r->addRoute('GET', '/resource/123/456', 'handler0');
  351. };
  352. $method = 'POST';
  353. $uri = '/resource/123/456';
  354. $allowedMethods = ['GET'];
  355. $cases[] = [$method, $uri, $callback, $allowedMethods];
  356. // 1 -------------------------------------------------------------------------------------->
  357. $callback = function(RouteCollector $r) {
  358. $r->addRoute('GET', '/resource/123/456', 'handler0');
  359. $r->addRoute('POST', '/resource/123/456', 'handler1');
  360. $r->addRoute('PUT', '/resource/123/456', 'handler2');
  361. $r->addRoute('*', '/', 'handler3');
  362. };
  363. $method = 'DELETE';
  364. $uri = '/resource/123/456';
  365. $allowedMethods = ['GET', 'POST', 'PUT'];
  366. $cases[] = [$method, $uri, $callback, $allowedMethods];
  367. // 2 -------------------------------------------------------------------------------------->
  368. $callback = function(RouteCollector $r) {
  369. $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0');
  370. $r->addRoute('POST', '/user/{name}/{id:[0-9]+}', 'handler1');
  371. $r->addRoute('PUT', '/user/{name}/{id:[0-9]+}', 'handler2');
  372. $r->addRoute('PATCH', '/user/{name}/{id:[0-9]+}', 'handler3');
  373. };
  374. $method = 'DELETE';
  375. $uri = '/user/rdlowrey/42';
  376. $allowedMethods = ['GET', 'POST', 'PUT', 'PATCH'];
  377. $cases[] = [$method, $uri, $callback, $allowedMethods];
  378. // 3 -------------------------------------------------------------------------------------->
  379. $callback = function(RouteCollector $r) {
  380. $r->addRoute('POST', '/user/{name}', 'handler1');
  381. $r->addRoute('PUT', '/user/{name:[a-z]+}', 'handler2');
  382. $r->addRoute('PATCH', '/user/{name:[a-z]+}', 'handler3');
  383. };
  384. $method = 'GET';
  385. $uri = '/user/rdlowrey';
  386. $allowedMethods = ['POST', 'PUT', 'PATCH'];
  387. $cases[] = [$method, $uri, $callback, $allowedMethods];
  388. // 4 -------------------------------------------------------------------------------------->
  389. $callback = function(RouteCollector $r) {
  390. $r->addRoute(['GET', 'POST'], '/user', 'handlerGetPost');
  391. $r->addRoute(['DELETE'], '/user', 'handlerDelete');
  392. $r->addRoute([], '/user', 'handlerNone');
  393. };
  394. $cases[] = ['PUT', '/user', $callback, ['GET', 'POST', 'DELETE']];
  395. // 5
  396. $callback = function(RouteCollector $r) {
  397. $r->addRoute('POST', '/user.json', 'handler0');
  398. $r->addRoute('GET', '/{entity}.json', 'handler1');
  399. };
  400. $cases[] = ['PUT', '/user.json', $callback, ['POST', 'GET']];
  401. // x -------------------------------------------------------------------------------------->
  402. return $cases;
  403. }
  404. }