ActionDispatcherTest.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  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.3.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\Http;
  16. use Cake\Core\Configure;
  17. use Cake\Event\Event;
  18. use Cake\Http\ActionDispatcher;
  19. use Cake\Http\Response;
  20. use Cake\Network\Request;
  21. use Cake\Network\Session;
  22. use Cake\Routing\DispatcherFactory;
  23. use Cake\Routing\Filter\ControllerFactoryFilter;
  24. use Cake\Routing\Router;
  25. use Cake\TestSuite\TestCase;
  26. /**
  27. * Test case for the ActionDispatcher.
  28. */
  29. class ActionDispatcherTest extends TestCase
  30. {
  31. /**
  32. * Setup
  33. *
  34. * @return void
  35. */
  36. public function setUp()
  37. {
  38. parent::setUp();
  39. Router::reload();
  40. Configure::write('App.namespace', 'TestApp');
  41. $this->dispatcher = new ActionDispatcher();
  42. $this->dispatcher->addFilter(new ControllerFactoryFilter());
  43. }
  44. /**
  45. * Teardown
  46. *
  47. * @return void
  48. */
  49. public function tearDown()
  50. {
  51. parent::tearDown();
  52. DispatcherFactory::clear();
  53. }
  54. /**
  55. * Ensure the constructor args end up on the right protected properties.
  56. *
  57. * @return void
  58. */
  59. public function testConstructorArgs()
  60. {
  61. $factory = $this->getMockBuilder('Cake\Http\ControllerFactory')->getMock();
  62. $events = $this->getMockBuilder('Cake\Event\EventManager')->getMock();
  63. $dispatcher = new ActionDispatcher($factory, $events);
  64. $this->assertAttributeSame($events, '_eventManager', $dispatcher);
  65. $this->assertAttributeSame($factory, 'factory', $dispatcher);
  66. }
  67. /**
  68. * Ensure that filters connected to the DispatcherFactory are
  69. * also applied
  70. */
  71. public function testDispatcherFactoryCompat()
  72. {
  73. $filter = $this->getMockBuilder('Cake\Routing\DispatcherFilter')
  74. ->setMethods(['beforeDispatch', 'afterDispatch'])
  75. ->getMock();
  76. DispatcherFactory::add($filter);
  77. $dispatcher = new ActionDispatcher(null, null, DispatcherFactory::filters());
  78. $this->assertCount(1, $dispatcher->getFilters());
  79. $this->assertSame($filter, $dispatcher->getFilters()[0]);
  80. }
  81. /**
  82. * Test adding routing filters
  83. *
  84. * @return void
  85. */
  86. public function testAddFilter()
  87. {
  88. $this->assertCount(1, $this->dispatcher->getFilters());
  89. $events = $this->dispatcher->eventManager();
  90. $this->assertCount(1, $events->listeners('Dispatcher.beforeDispatch'));
  91. $this->assertCount(1, $events->listeners('Dispatcher.afterDispatch'));
  92. $filter = $this->getMockBuilder('Cake\Routing\DispatcherFilter')
  93. ->setMethods(['beforeDispatch', 'afterDispatch'])
  94. ->getMock();
  95. $this->dispatcher->addFilter($filter);
  96. $this->assertCount(2, $this->dispatcher->getFilters());
  97. $this->assertCount(2, $events->listeners('Dispatcher.beforeDispatch'));
  98. $this->assertCount(2, $events->listeners('Dispatcher.afterDispatch'));
  99. }
  100. /**
  101. * Ensure that aborting in the beforeDispatch doesn't invoke the controller
  102. *
  103. * @return void
  104. */
  105. public function testBeforeDispatchEventAbort()
  106. {
  107. $response = new Response();
  108. $dispatcher = new ActionDispatcher();
  109. $filter = $this->getMockBuilder('Cake\Routing\DispatcherFilter')
  110. ->setMethods(['beforeDispatch', 'afterDispatch'])
  111. ->getMock();
  112. $filter->expects($this->once())
  113. ->method('beforeDispatch')
  114. ->will($this->returnValue($response));
  115. $req = new Request();
  116. $res = new Response();
  117. $dispatcher->addFilter($filter);
  118. $result = $dispatcher->dispatch($req, $res);
  119. $this->assertSame($response, $result, 'Should be response from filter.');
  120. }
  121. /**
  122. * Ensure afterDispatch can replace the response
  123. *
  124. * @return void
  125. */
  126. public function testDispatchAfterDispatchEventModifyResponse()
  127. {
  128. $filter = $this->getMockBuilder('Cake\Routing\DispatcherFilter')
  129. ->setMethods(['beforeDispatch', 'afterDispatch'])
  130. ->getMock();
  131. $filter->expects($this->once())
  132. ->method('afterDispatch')
  133. ->will($this->returnCallback(function (Event $event) {
  134. $event->data('response')->body('Filter body');
  135. }));
  136. $req = new Request([
  137. 'url' => '/cakes',
  138. 'params' => [
  139. 'plugin' => null,
  140. 'controller' => 'Cakes',
  141. 'action' => 'index',
  142. 'pass' => [],
  143. ],
  144. 'session' => new Session
  145. ]);
  146. $res = new Response();
  147. $this->dispatcher->addFilter($filter);
  148. $result = $this->dispatcher->dispatch($req, $res);
  149. $this->assertSame('Filter body', $result->body(), 'Should be response from filter.');
  150. }
  151. /**
  152. * Test that a controller action returning a response
  153. * results in no afterDispatch event.
  154. *
  155. * @return void
  156. */
  157. public function testDispatchActionReturnResponseNoAfterDispatch()
  158. {
  159. $filter = $this->getMockBuilder('Cake\Routing\DispatcherFilter')
  160. ->setMethods(['beforeDispatch', 'afterDispatch'])
  161. ->getMock();
  162. $filter->expects($this->never())
  163. ->method('afterDispatch');
  164. $req = new Request([
  165. 'url' => '/cakes',
  166. 'params' => [
  167. 'plugin' => null,
  168. 'controller' => 'Cakes',
  169. 'action' => 'index',
  170. 'pass' => [],
  171. 'return' => true,
  172. ],
  173. ]);
  174. $res = new Response();
  175. $this->dispatcher->addFilter($filter);
  176. $result = $this->dispatcher->dispatch($req, $res);
  177. $this->assertSame('Hello Jane', $result->body(), 'Response from controller.');
  178. }
  179. /**
  180. * Test that dispatching sets the Router request state.
  181. *
  182. * @return void
  183. */
  184. public function testDispatchSetsRequestContext()
  185. {
  186. $this->assertNull(Router::getRequest());
  187. $req = new Request([
  188. 'url' => '/cakes',
  189. 'params' => [
  190. 'plugin' => null,
  191. 'controller' => 'Cakes',
  192. 'action' => 'index',
  193. 'pass' => [],
  194. 'return' => true,
  195. ],
  196. ]);
  197. $res = new Response();
  198. $this->dispatcher->dispatch($req, $res);
  199. $this->assertSame($req, Router::getRequest(true));
  200. }
  201. /**
  202. * test invalid response from dispatch process.
  203. *
  204. * @expectedException \LogicException
  205. * @expectedExceptionMessage Controller actions can only return Cake\Http\Response or null
  206. * @return void
  207. */
  208. public function testDispatchInvalidResponse()
  209. {
  210. $req = new Request([
  211. 'url' => '/cakes',
  212. 'params' => [
  213. 'plugin' => null,
  214. 'controller' => 'Cakes',
  215. 'action' => 'invalid',
  216. 'pass' => [],
  217. ],
  218. ]);
  219. $res = new Response();
  220. $result = $this->dispatcher->dispatch($req, $res);
  221. }
  222. /**
  223. * Test dispatch with autorender
  224. *
  225. * @return void
  226. */
  227. public function testDispatchAutoRender()
  228. {
  229. $request = new Request([
  230. 'url' => 'posts',
  231. 'params' => [
  232. 'controller' => 'Posts',
  233. 'action' => 'index',
  234. 'pass' => [],
  235. ]
  236. ]);
  237. $response = new Response();
  238. $result = $this->dispatcher->dispatch($request, $response);
  239. $this->assertInstanceOf('Cake\Http\Response', $result);
  240. $this->assertContains('posts index', $result->body());
  241. }
  242. /**
  243. * Test dispatch with autorender=false
  244. *
  245. * @return void
  246. */
  247. public function testDispatchAutoRenderFalse()
  248. {
  249. $request = new Request([
  250. 'url' => 'posts',
  251. 'params' => [
  252. 'controller' => 'Cakes',
  253. 'action' => 'noRender',
  254. 'pass' => [],
  255. ]
  256. ]);
  257. $response = new Response();
  258. $result = $this->dispatcher->dispatch($request, $response);
  259. $this->assertInstanceOf('Cake\Http\Response', $result);
  260. $this->assertContains('autoRender false body', $result->body());
  261. }
  262. /**
  263. * testMissingController method
  264. *
  265. * @expectedException \Cake\Routing\Exception\MissingControllerException
  266. * @expectedExceptionMessage Controller class SomeController could not be found.
  267. * @return void
  268. */
  269. public function testMissingController()
  270. {
  271. $request = new Request([
  272. 'url' => 'some_controller/home',
  273. 'params' => [
  274. 'controller' => 'SomeController',
  275. 'action' => 'home',
  276. ]
  277. ]);
  278. $response = $this->getMockBuilder('Cake\Http\Response')->getMock();
  279. $this->dispatcher->dispatch($request, $response);
  280. }
  281. /**
  282. * testMissingControllerInterface method
  283. *
  284. * @expectedException \Cake\Routing\Exception\MissingControllerException
  285. * @expectedExceptionMessage Controller class Interface could not be found.
  286. * @return void
  287. */
  288. public function testMissingControllerInterface()
  289. {
  290. $request = new Request([
  291. 'url' => 'interface/index',
  292. 'params' => [
  293. 'controller' => 'Interface',
  294. 'action' => 'index',
  295. ]
  296. ]);
  297. $response = $this->getMockBuilder('Cake\Http\Response')->getMock();
  298. $this->dispatcher->dispatch($request, $response);
  299. }
  300. /**
  301. * testMissingControllerInterface method
  302. *
  303. * @expectedException \Cake\Routing\Exception\MissingControllerException
  304. * @expectedExceptionMessage Controller class Abstract could not be found.
  305. * @return void
  306. */
  307. public function testMissingControllerAbstract()
  308. {
  309. $request = new Request([
  310. 'url' => 'abstract/index',
  311. 'params' => [
  312. 'controller' => 'Abstract',
  313. 'action' => 'index',
  314. ]
  315. ]);
  316. $response = $this->getMockBuilder('Cake\Http\Response')->getMock();
  317. $this->dispatcher->dispatch($request, $response);
  318. }
  319. /**
  320. * Test that lowercase controller names result in missing controller errors.
  321. *
  322. * In case-insensitive file systems, lowercase controller names will kind of work.
  323. * This causes annoying deployment issues for lots of folks.
  324. *
  325. * @expectedException \Cake\Routing\Exception\MissingControllerException
  326. * @expectedExceptionMessage Controller class somepages could not be found.
  327. * @return void
  328. */
  329. public function testMissingControllerLowercase()
  330. {
  331. $request = new Request([
  332. 'url' => 'pages/home',
  333. 'params' => [
  334. 'plugin' => null,
  335. 'controller' => 'somepages',
  336. 'action' => 'display',
  337. 'pass' => ['home'],
  338. ]
  339. ]);
  340. $response = $this->getMockBuilder('Cake\Http\Response')->getMock();
  341. $this->dispatcher->dispatch($request, $response);
  342. }
  343. /**
  344. * Ensure that a controller's startup event can stop the request.
  345. *
  346. * @return void
  347. */
  348. public function testStartupProcessAbort()
  349. {
  350. $request = new Request([
  351. 'url' => 'cakes/index',
  352. 'params' => [
  353. 'plugin' => null,
  354. 'controller' => 'Cakes',
  355. 'action' => 'index',
  356. 'stop' => 'startup',
  357. 'pass' => [],
  358. ]
  359. ]);
  360. $response = new Response();
  361. $result = $this->dispatcher->dispatch($request, $response);
  362. $this->assertSame('startup stop', $result->body());
  363. }
  364. /**
  365. * Ensure that a controllers startup process can emit a response
  366. *
  367. * @return void
  368. */
  369. public function testShutdownProcessResponse()
  370. {
  371. $request = new Request([
  372. 'url' => 'cakes/index',
  373. 'params' => [
  374. 'plugin' => null,
  375. 'controller' => 'Cakes',
  376. 'action' => 'index',
  377. 'stop' => 'shutdown',
  378. 'pass' => [],
  379. ]
  380. ]);
  381. $response = new Response();
  382. $result = $this->dispatcher->dispatch($request, $response);
  383. $this->assertSame('shutdown stop', $result->body());
  384. }
  385. }