ActionDispatcherTest.php 12 KB

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