ActionDispatcherTest.php 12 KB

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