ControllerTest.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2012, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\tests\cases\action;
  9. use lithium\action\Request;
  10. use lithium\action\Response;
  11. use lithium\action\Controller;
  12. use lithium\tests\mocks\action\MockPostsController;
  13. use lithium\tests\mocks\action\MockControllerRequest;
  14. class ControllerTest extends \lithium\test\Unit {
  15. /**
  16. * Tests that controllers can be instantiated with custom request objects.
  17. *
  18. * @return void
  19. */
  20. public function testConstructionWithCustomRequest() {
  21. $request = new MockControllerRequest();
  22. $postsController = new MockPostsController(compact('request'));
  23. $result = get_class($postsController->request);
  24. $this->assertEqual($result, 'lithium\tests\mocks\action\MockControllerRequest');
  25. }
  26. /**
  27. * Tests the use of `Controller::__invoke()` for dispatching requests to action methods. Also
  28. * tests that using PHP's callable syntax yields the same result as calling `__invoke()`
  29. * explicitly.
  30. *
  31. * @return void
  32. */
  33. public function testMethodInvocation() {
  34. $postsController = new MockPostsController();
  35. $result = $postsController->__invoke(null, array('action' => 'index', 'args' => array()));
  36. $this->assertTrue($result instanceof Response);
  37. $this->assertEqual('List of posts', $result->body());
  38. $this->assertEqual(array('Content-Type' => 'text/plain; charset=UTF-8'), $result->headers);
  39. $result2 = $postsController(null, array('action' => 'index', 'args' => array()));
  40. $this->assertEqual($result2, $result);
  41. $postsController = new MockPostsController();
  42. $this->expectException('/Unhandled media type/');
  43. $result = $postsController(null, array('action' => 'index', 'args' => array(true)));
  44. $this->assertTrue($result instanceof Response);
  45. $this->assertEqual($result->body, '');
  46. $headers = array('Content-Type' => 'text/html; charset=UTF-8');
  47. $this->assertEqual($result->headers, $headers);
  48. $result = $postsController->access('_render');
  49. $this->assertEqual($result['data'], array('foo' => 'bar'));
  50. $postsController = new MockPostsController();
  51. $result = $postsController(null, array('action' => 'view', 'args' => array('2')));
  52. $this->assertTrue($result instanceof Response);
  53. $this->assertEqual($result->body, "Array\n(\n [0] => This is a post\n)\n");
  54. $headers = array('status' => 200, 'Content-Type' => 'text/plain; charset=UTF-8');
  55. $this->assertEqual($result->headers(), $headers);
  56. $result = $postsController->access('_render');
  57. $this->assertEqual($result['data'], array('This is a post'));
  58. }
  59. /**
  60. * Tests that calls to `Controller::redirect()` correctly write redirect headers to the
  61. * response object.
  62. *
  63. * @return void
  64. */
  65. public function testRedirectResponse() {
  66. $postsController = new MockPostsController();
  67. $result = $postsController(null, array('action' => 'delete'));
  68. $this->assertEqual($result->body(), '');
  69. $headers = array('Location' => '/posts', 'Content-Type' => 'text/html');
  70. $this->assertEqual($result->headers, $headers);
  71. $postsController = new MockPostsController();
  72. $result = $postsController(null, array('action' => 'delete', 'args' => array('5')));
  73. $this->assertEqual($result->body(), 'Deleted 5');
  74. $this->assertFalse($postsController->stopped);
  75. $postsController = new MockPostsController(array('classes' => array(
  76. 'response' => 'lithium\tests\mocks\action\MockControllerResponse'
  77. )));
  78. $this->assertFalse($postsController->stopped);
  79. $postsController->__invoke(null, array('action' => 'send'));
  80. $this->assertTrue($postsController->stopped);
  81. $result = $postsController->access('_render');
  82. $this->assertTrue($result['hasRendered']);
  83. $this->assertEqual($postsController->response->body(), null);
  84. $this->assertEqual(
  85. $postsController->response->headers,
  86. array('Location' => '/posts', 'Content-Type' => 'text/html')
  87. );
  88. }
  89. /**
  90. * Tests calling `Controller::render()` with parameters to render an alternate template from
  91. * the default.
  92. *
  93. * @return void
  94. */
  95. public function testRenderWithAlternateTemplate() {
  96. $postsController = new MockPostsController(array('classes' => array(
  97. 'media' => 'lithium\tests\mocks\action\MockMediaClass'
  98. )));
  99. $result = $postsController(null, array('action' => 'view2'));
  100. $this->assertEqual('view', $result->options['template']);
  101. $this->assertEqual('default', $result->options['layout']);
  102. $result = $postsController(null, array('action' => 'view3'));
  103. $this->assertEqual('view', $result->options['template']);
  104. $this->assertFalse($result->options['layout']);
  105. }
  106. /**
  107. * Tests that requests where the controller class is specified manually continue to route to
  108. * the correct template path.
  109. *
  110. * @return void
  111. */
  112. public function testRenderWithNamespacedController() {
  113. $request = new Request();
  114. $request->params['controller'] = 'lithium\tests\mocks\action\MockPostsController';
  115. $controller = new MockPostsController(compact('request') + array('classes' => array(
  116. 'media' => 'lithium\tests\mocks\action\MockMediaClass'
  117. )));
  118. $controller->render();
  119. $this->assertEqual('mock_posts', $controller->response->options['controller']);
  120. }
  121. /**
  122. * Verifies that data array is passed on to controller's response.
  123. *
  124. * @return void
  125. */
  126. public function testRenderWithDataArray() {
  127. $request = new Request();
  128. $request->params['controller'] = 'lithium\tests\mocks\action\MockPostsController';
  129. $controller = new MockPostsController(compact('request') + array('classes' => array(
  130. 'media' => 'lithium\tests\mocks\action\MockMediaClass'
  131. )));
  132. $controller->set(array('set' => 'data'));
  133. $controller->render(array('data' => array('render' => 'data')));
  134. $expected = array(
  135. 'set' => 'data',
  136. 'render' => 'data'
  137. );
  138. $this->assertEqual($expected, $controller->response->data);
  139. }
  140. /**
  141. * Verifies that the Controller does not modify data when passed an array (or RecordSet)
  142. * with a single element.
  143. *
  144. * @return void
  145. */
  146. public function testRenderWithDataSingleIndexedArray() {
  147. $request = new Request();
  148. $request->params['controller'] = 'lithium\tests\mocks\action\MockPostsController';
  149. $controller = new MockPostsController(compact('request') + array('classes' => array(
  150. 'media' => 'lithium\tests\mocks\action\MockMediaClass'
  151. )));
  152. $expected = array(array('id' => 1));
  153. $controller->render(array('data' => $expected));
  154. $this->assertEqual($expected, $controller->response->data);
  155. }
  156. /**
  157. * Verifies that protected methods (i.e. prefixed with '_'), and methods declared in the
  158. * Controller base class cannot be accessed.
  159. *
  160. * @return void
  161. */
  162. public function testProtectedMethodAccessAttempt() {
  163. $postsController = new MockPostsController();
  164. $this->expectException('/^Attempted to invoke a private method/');
  165. $result = $postsController->__invoke(null, array('action' => 'redirect'));
  166. $this->assertEqual($result->body, null);
  167. $this->assertEqual($result->headers(), array());
  168. $postsController = new MockPostsController();
  169. $this->expectException('/^Private/');
  170. $result = $postsController->invoke('_safe');
  171. $this->assertEqual($result->body, null);
  172. $this->assertEqual($result->headers(), array());
  173. }
  174. public function testResponseStatus() {
  175. $postsController = new MockPostsController(array('classes' => array(
  176. 'response' => 'lithium\tests\mocks\action\MockControllerResponse'
  177. )));
  178. $this->assertFalse($postsController->stopped);
  179. $postsController(null, array('action' => 'notFound'));
  180. $result = $postsController->access('_render');
  181. $this->assertTrue($result['hasRendered']);
  182. $expected = array('code' => 404, 'message' => 'Not Found');
  183. $result = $postsController->response->status;
  184. $this->assertEqual($expected, $result);
  185. $result = $postsController->response->body();
  186. $this->assertEqual($expected, $result);
  187. }
  188. public function testResponseTypeBasedOnRequestType() {
  189. $request = new MockControllerRequest();
  190. $request->params['type'] = 'json';
  191. $postsController = new MockPostsController(array(
  192. 'request' => $request,
  193. 'classes' => array(
  194. 'response' => 'lithium\tests\mocks\action\MockControllerResponse'
  195. )
  196. ));
  197. $this->assertFalse($postsController->stopped);
  198. $postsController($request, array('action' => 'type'));
  199. $expected = array(
  200. 'type' => 'json', 'data' => array('data' => 'test'), 'auto' => true,
  201. 'layout' => 'default', 'template' => 'type', 'hasRendered' => true, 'negotiate' => false
  202. );
  203. $result = $postsController->access('_render');
  204. $this->assertEqual($expected, $result);
  205. $result = $postsController->response->headers('Content-Type');
  206. $this->assertEqual('application/json; charset=UTF-8', $result);
  207. $result = $postsController->response->body();
  208. $this->assertEqual(array('data' => 'test'), $result);
  209. }
  210. public function testResponseTypeBasedOnRequestParamsType() {
  211. $request = new MockControllerRequest();
  212. $request->params['type'] = 'json';
  213. $postsController = new MockPostsController(array(
  214. 'request' => $request,
  215. 'classes' => array(
  216. 'response' => 'lithium\tests\mocks\action\MockControllerResponse'
  217. )
  218. ));
  219. $this->assertFalse($postsController->stopped);
  220. $postsController->__invoke($request, array('action' => 'type'));
  221. $expected = array(
  222. 'type' => 'json', 'data' => array('data' => 'test'), 'auto' => true,
  223. 'layout' => 'default', 'template' => 'type', 'hasRendered' => true, 'negotiate' => false
  224. );
  225. $result = $postsController->access('_render');
  226. $this->assertEqual($expected, $result);
  227. $result = $postsController->response->headers('Content-Type');
  228. $this->assertEqual('application/json; charset=UTF-8', $result);
  229. $expected = array('data' => 'test');
  230. $result = $postsController->response->body();
  231. $this->assertEqual($expected, $result);
  232. }
  233. /**
  234. * Tests that `$_render['template']` can be manually set in a controller action and will not be
  235. * overwritten.
  236. *
  237. * @return void
  238. */
  239. public function testManuallySettingTemplate() {
  240. $postsController = new MockPostsController(array('classes' => array(
  241. 'media' => 'lithium\tests\mocks\action\MockMediaClass'
  242. )));
  243. $postsController(new Request(), array('action' => 'changeTemplate'));
  244. $result = $postsController->access('_render');
  245. $this->assertEqual('foo', $result['template']);
  246. }
  247. public function testSetData() {
  248. $postController = new MockPostsController();
  249. $setData = array('foo' => 'bar');
  250. $postController->set($setData);
  251. $_render = $postController->access('_render');
  252. $data = $_render['data'];
  253. $expected = $setData;
  254. $this->assertEqual($expected, $data);
  255. $setData = array('foo' => 'baz');
  256. $postController->set($setData);
  257. $_render = $postController->access('_render');
  258. $data = $_render['data'];
  259. $expected = $setData;
  260. $this->assertEqual($expected, $data);
  261. }
  262. public function testResponseTypeBasedOnRequestHeaderType() {
  263. $request = new MockControllerRequest(array(
  264. 'env' => array('HTTP_ACCEPT' => 'application/json,*/*')
  265. ));
  266. $postsController = new MockPostsController(array(
  267. 'request' => $request,
  268. 'classes' => array('response' => 'lithium\tests\mocks\action\MockControllerResponse'),
  269. 'render' => array('negotiate' => true)
  270. ));
  271. $this->assertFalse($postsController->stopped);
  272. $postsController($request, array('action' => 'type'));
  273. $expected = array(
  274. 'type' => 'json', 'data' => array('data' => 'test'), 'auto' => true,
  275. 'layout' => 'default', 'template' => 'type', 'hasRendered' => true, 'negotiate' => true
  276. );
  277. $result = $postsController->access('_render');
  278. $this->assertEqual($expected, $result);
  279. $result = $postsController->response->headers('Content-Type');
  280. $this->assertEqual('application/json; charset=UTF-8', $result);
  281. $result = $postsController->response->body();
  282. $this->assertEqual(array('data' => 'test'), $result);
  283. }
  284. /**
  285. * Tests that requests which are dispotched with the controller route parameter specified as
  286. * a fully-qualified class name are able to locate their templates correctly.
  287. *
  288. * @return void
  289. */
  290. public function testDispatchingWithExplicitControllerName() {
  291. $request = new Request(array('url' => '/'));
  292. $request->params = array(
  293. 'controller' => 'lithium\tests\mocks\action\MockPostsController',
  294. 'action' => 'index'
  295. );
  296. $postsController = new MockPostsController(compact('request'));
  297. $postsController->__invoke($request, $request->params);
  298. }
  299. public function testNonExistentFunction() {
  300. $postsController = new MockPostsController();
  301. $this->expectException("Action `foo` not found.");
  302. $postsController(new Request(), array('action' => 'foo'));
  303. }
  304. /**
  305. * Tests that the library of the controller is automatically added to the default rendering
  306. * options.
  307. */
  308. public function testLibraryScoping() {
  309. $request = new Request();
  310. $request->params['controller'] = 'lithium\tests\mocks\action\MockPostsController';
  311. $controller = new MockPostsController(compact('request') + array('classes' => array(
  312. 'media' => 'lithium\tests\mocks\action\MockMediaClass'
  313. )));
  314. $controller->render();
  315. $this->assertEqual('lithium', $controller->response->options['library']);
  316. }
  317. }
  318. ?>