ActionDispatcherTest.php 13 KB

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