PageRenderTime 46ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/tests/ZendTest/Mvc/Controller/RestfulControllerTest.php

http://github.com/zendframework/zf2
PHP | 515 lines | 441 code | 55 blank | 19 comment | 1 complexity | 08641dd561cbe68c48e4a19a02f7fd8e MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * Zend Framework (http://framework.zend.com/)
  4. *
  5. * @link http://github.com/zendframework/zf2 for the canonical source repository
  6. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  7. * @license http://framework.zend.com/license/new-bsd New BSD License
  8. */
  9. namespace ZendTest\Mvc\Controller;
  10. use PHPUnit_Framework_TestCase as TestCase;
  11. use ReflectionObject;
  12. use stdClass;
  13. use Zend\EventManager\SharedEventManager;
  14. use Zend\Http\Response;
  15. use Zend\Mvc\MvcEvent;
  16. use Zend\Mvc\Router\RouteMatch;
  17. class RestfulControllerTest extends TestCase
  18. {
  19. public $controller;
  20. public $emptyController;
  21. public $request;
  22. public $response;
  23. public $routeMatch;
  24. public $event;
  25. public function setUp()
  26. {
  27. $this->controller = new TestAsset\RestfulTestController();
  28. $this->emptyController = new TestAsset\RestfulMethodNotAllowedTestController();
  29. $this->request = new TestAsset\Request();
  30. $this->response = new Response();
  31. $this->routeMatch = new RouteMatch(array('controller' => 'controller-restful'));
  32. $this->event = new MvcEvent;
  33. $this->event->setRouteMatch($this->routeMatch);
  34. $this->controller->setEvent($this->event);
  35. $this->emptyController->setEvent($this->event);
  36. }
  37. public function testDispatchInvokesListWhenNoActionPresentAndNoIdentifierOnGet()
  38. {
  39. $entities = array(
  40. new stdClass,
  41. new stdClass,
  42. new stdClass,
  43. );
  44. $this->controller->entities = $entities;
  45. $result = $this->controller->dispatch($this->request, $this->response);
  46. $this->assertArrayHasKey('entities', $result);
  47. $this->assertEquals($entities, $result['entities']);
  48. $this->assertEquals('getList', $this->routeMatch->getParam('action'));
  49. }
  50. public function testDispatchInvokesGetMethodWhenNoActionPresentAndIdentifierPresentOnGet()
  51. {
  52. $entity = new stdClass;
  53. $this->controller->entity = $entity;
  54. $this->routeMatch->setParam('id', 1);
  55. $result = $this->controller->dispatch($this->request, $this->response);
  56. $this->assertArrayHasKey('entity', $result);
  57. $this->assertEquals($entity, $result['entity']);
  58. $this->assertEquals('get', $this->routeMatch->getParam('action'));
  59. }
  60. public function testDispatchInvokesCreateMethodWhenNoActionPresentAndPostInvoked()
  61. {
  62. $entity = array('id' => 1, 'name' => __FUNCTION__);
  63. $this->request->setMethod('POST');
  64. $post = $this->request->getPost();
  65. $post->fromArray($entity);
  66. $result = $this->controller->dispatch($this->request, $this->response);
  67. $this->assertArrayHasKey('entity', $result);
  68. $this->assertEquals($entity, $result['entity']);
  69. $this->assertEquals('create', $this->routeMatch->getParam('action'));
  70. }
  71. public function testDispatchInvokesUpdateMethodWhenNoActionPresentAndPutInvokedWithIdentifier()
  72. {
  73. $entity = array('name' => __FUNCTION__);
  74. $string = http_build_query($entity);
  75. $this->request->setMethod('PUT')
  76. ->setContent($string);
  77. $this->routeMatch->setParam('id', 1);
  78. $result = $this->controller->dispatch($this->request, $this->response);
  79. $this->assertArrayHasKey('entity', $result);
  80. $test = $result['entity'];
  81. $this->assertArrayHasKey('id', $test);
  82. $this->assertEquals(1, $test['id']);
  83. $this->assertArrayHasKey('name', $test);
  84. $this->assertEquals(__FUNCTION__, $test['name']);
  85. $this->assertEquals('update', $this->routeMatch->getParam('action'));
  86. }
  87. public function testDispatchInvokesReplaceListMethodWhenNoActionPresentAndPutInvokedWithoutIdentifier()
  88. {
  89. $entities = array(
  90. array('id' => uniqid(), 'name' => __FUNCTION__),
  91. array('id' => uniqid(), 'name' => __FUNCTION__),
  92. array('id' => uniqid(), 'name' => __FUNCTION__),
  93. );
  94. $string = http_build_query($entities);
  95. $this->request->setMethod('PUT')
  96. ->setContent($string);
  97. $result = $this->controller->dispatch($this->request, $this->response);
  98. $this->assertEquals($entities, $result);
  99. $this->assertEquals('replaceList', $this->routeMatch->getParam('action'));
  100. }
  101. public function testDispatchInvokesPatchListMethodWhenNoActionPresentAndPatchInvokedWithoutIdentifier()
  102. {
  103. $entities = array(
  104. array('id' => uniqid(), 'name' => __FUNCTION__),
  105. array('id' => uniqid(), 'name' => __FUNCTION__),
  106. array('id' => uniqid(), 'name' => __FUNCTION__),
  107. );
  108. $string = http_build_query($entities);
  109. $this->request->setMethod('PATCH')
  110. ->setContent($string);
  111. $result = $this->controller->dispatch($this->request, $this->response);
  112. $this->assertEquals($entities, $result);
  113. $this->assertEquals('patchList', $this->routeMatch->getParam('action'));
  114. }
  115. public function testDispatchInvokesDeleteMethodWhenNoActionPresentAndDeleteInvokedWithIdentifier()
  116. {
  117. $entity = array('id' => 1, 'name' => __FUNCTION__);
  118. $this->controller->entity = $entity;
  119. $this->request->setMethod('DELETE');
  120. $this->routeMatch->setParam('id', 1);
  121. $result = $this->controller->dispatch($this->request, $this->response);
  122. $this->assertEquals(array(), $result);
  123. $this->assertEquals(array(), $this->controller->entity);
  124. $this->assertEquals('delete', $this->routeMatch->getParam('action'));
  125. }
  126. public function testDispatchInvokesDeleteListMethodWhenNoActionPresentAndDeleteInvokedWithoutIdentifier()
  127. {
  128. $entities = array(
  129. array('id' => uniqid(), 'name' => __FUNCTION__),
  130. array('id' => uniqid(), 'name' => __FUNCTION__),
  131. array('id' => uniqid(), 'name' => __FUNCTION__),
  132. );
  133. $this->controller->entity = $entities;
  134. $string = http_build_query($entities);
  135. $this->request->setMethod('DELETE')
  136. ->setContent($string);
  137. $result = $this->controller->dispatch($this->request, $this->response);
  138. $this->assertEmpty($this->controller->entity);
  139. $this->assertEquals(204, $result->getStatusCode());
  140. $this->assertTrue($result->getHeaders()->has('X-Deleted'));
  141. $this->assertEquals('deleteList', $this->routeMatch->getParam('action'));
  142. }
  143. public function testDispatchInvokesOptionsMethodWhenNoActionPresentAndOptionsInvoked()
  144. {
  145. $this->request->setMethod('OPTIONS');
  146. $result = $this->controller->dispatch($this->request, $this->response);
  147. $this->assertSame($this->response, $result);
  148. $this->assertEquals('options', $this->routeMatch->getParam('action'));
  149. $headers = $result->getHeaders();
  150. $this->assertTrue($headers->has('Allow'));
  151. $allow = $headers->get('Allow');
  152. $expected = explode(', ', 'GET, POST, PUT, DELETE, PATCH, HEAD, TRACE');
  153. sort($expected);
  154. $test = explode(', ', $allow->getFieldValue());
  155. sort($test);
  156. $this->assertEquals($expected, $test);
  157. }
  158. public function testDispatchInvokesPatchMethodWhenNoActionPresentAndPatchInvokedWithIdentifier()
  159. {
  160. $entity = new stdClass;
  161. $entity->name = 'foo';
  162. $entity->type = 'standard';
  163. $this->controller->entity = $entity;
  164. $entity = array('name' => __FUNCTION__);
  165. $string = http_build_query($entity);
  166. $this->request->setMethod('PATCH')
  167. ->setContent($string);
  168. $this->routeMatch->setParam('id', 1);
  169. $result = $this->controller->dispatch($this->request, $this->response);
  170. $this->assertArrayHasKey('entity', $result);
  171. $test = $result['entity'];
  172. $this->assertArrayHasKey('id', $test);
  173. $this->assertEquals(1, $test['id']);
  174. $this->assertArrayHasKey('name', $test);
  175. $this->assertEquals(__FUNCTION__, $test['name']);
  176. $this->assertArrayHasKey('type', $test);
  177. $this->assertEquals('standard', $test['type']);
  178. $this->assertEquals('patch', $this->routeMatch->getParam('action'));
  179. }
  180. /**
  181. * @group 7086
  182. */
  183. public function testOnDispatchHonorsStatusCodeWithHeadMethod()
  184. {
  185. $this->controller->headResponse = new Response();
  186. $this->controller->headResponse->setStatusCode(418);
  187. $this->controller->headResponse->getHeaders()->addHeaderLine('Custom-Header', 'Header Value');
  188. $this->routeMatch->setParam('id', 1);
  189. $this->request->setMethod('HEAD');
  190. $result = $this->controller->dispatch($this->request, $this->response);
  191. $this->assertEquals(418, $result->getStatusCode());
  192. $this->assertEquals('', $result->getContent());
  193. $this->assertEquals('head', $this->routeMatch->getParam('action'));
  194. $this->assertEquals('Header Value', $result->getHeaders()->get('Custom-Header')->getFieldValue());
  195. }
  196. public function testDispatchInvokesHeadMethodWhenNoActionPresentAndHeadInvokedWithoutIdentifier()
  197. {
  198. $entities = array(
  199. new stdClass,
  200. new stdClass,
  201. new stdClass,
  202. );
  203. $this->controller->entities = $entities;
  204. $this->request->setMethod('HEAD');
  205. $result = $this->controller->dispatch($this->request, $this->response);
  206. $this->assertSame($this->response, $result);
  207. $content = $result->getContent();
  208. $this->assertEquals('', $content);
  209. $this->assertEquals('head', $this->routeMatch->getParam('action'));
  210. }
  211. public function testDispatchInvokesHeadMethodWhenNoActionPresentAndHeadInvokedWithIdentifier()
  212. {
  213. $entity = new stdClass;
  214. $this->controller->entity = $entity;
  215. $this->routeMatch->setParam('id', 1);
  216. $this->request->setMethod('HEAD');
  217. $result = $this->controller->dispatch($this->request, $this->response);
  218. $this->assertSame($this->response, $result);
  219. $content = $result->getContent();
  220. $this->assertEquals('', $content);
  221. $this->assertEquals('head', $this->routeMatch->getParam('action'));
  222. $headers = $this->controller->getResponse()->getHeaders();
  223. $this->assertTrue($headers->has('X-ZF2-Id'));
  224. $header = $headers->get('X-ZF2-Id');
  225. $this->assertEquals(1, $header->getFieldValue());
  226. }
  227. public function testAllowsRegisteringCustomHttpMethodsWithHandlers()
  228. {
  229. $this->controller->addHttpMethodHandler('DESCRIBE', array($this->controller, 'describe'));
  230. $this->request->setMethod('DESCRIBE');
  231. $result = $this->controller->dispatch($this->request, $this->response);
  232. $this->assertArrayHasKey('description', $result);
  233. $this->assertContains('::describe', $result['description']);
  234. }
  235. public function testDispatchCallsActionMethodBasedOnNormalizingAction()
  236. {
  237. $this->routeMatch->setParam('action', 'test.some-strangely_separated.words');
  238. $result = $this->controller->dispatch($this->request, $this->response);
  239. $this->assertArrayHasKey('content', $result);
  240. $this->assertContains('Test Some Strangely Separated Words', $result['content']);
  241. }
  242. public function testDispatchCallsNotFoundActionWhenActionPassedThatCannotBeMatched()
  243. {
  244. $this->routeMatch->setParam('action', 'test-some-made-up-action');
  245. $result = $this->controller->dispatch($this->request, $this->response);
  246. $response = $this->controller->getResponse();
  247. $this->assertEquals(404, $response->getStatusCode());
  248. $this->assertArrayHasKey('content', $result);
  249. $this->assertContains('Page not found', $result['content']);
  250. }
  251. public function testShortCircuitsBeforeActionIfPreDispatchReturnsAResponse()
  252. {
  253. $response = new Response();
  254. $response->setContent('short circuited!');
  255. $this->controller->getEventManager()->attach(MvcEvent::EVENT_DISPATCH, function ($e) use ($response) {
  256. return $response;
  257. }, 10);
  258. $result = $this->controller->dispatch($this->request, $this->response);
  259. $this->assertSame($response, $result);
  260. }
  261. public function testPostDispatchEventAllowsReplacingResponse()
  262. {
  263. $response = new Response();
  264. $response->setContent('short circuited!');
  265. $this->controller->getEventManager()->attach(MvcEvent::EVENT_DISPATCH, function ($e) use ($response) {
  266. return $response;
  267. }, -10);
  268. $result = $this->controller->dispatch($this->request, $this->response);
  269. $this->assertSame($response, $result);
  270. }
  271. public function testEventManagerListensOnDispatchableInterfaceByDefault()
  272. {
  273. $response = new Response();
  274. $response->setContent('short circuited!');
  275. $events = new SharedEventManager();
  276. $events->attach('Zend\Stdlib\DispatchableInterface', MvcEvent::EVENT_DISPATCH, function ($e) use ($response) {
  277. return $response;
  278. }, 10);
  279. $this->controller->getEventManager()->setSharedManager($events);
  280. $result = $this->controller->dispatch($this->request, $this->response);
  281. $this->assertSame($response, $result);
  282. }
  283. public function testEventManagerListensOnRestfulControllerClassByDefault()
  284. {
  285. $response = new Response();
  286. $response->setContent('short circuited!');
  287. $events = new SharedEventManager();
  288. $events->attach('Zend\Mvc\Controller\AbstractRestfulController', MvcEvent::EVENT_DISPATCH, function ($e) use ($response) {
  289. return $response;
  290. }, 10);
  291. $this->controller->getEventManager()->setSharedManager($events);
  292. $result = $this->controller->dispatch($this->request, $this->response);
  293. $this->assertSame($response, $result);
  294. }
  295. public function testEventManagerListensOnClassNameByDefault()
  296. {
  297. $response = new Response();
  298. $response->setContent('short circuited!');
  299. $events = new SharedEventManager();
  300. $events->attach(get_class($this->controller), MvcEvent::EVENT_DISPATCH, function ($e) use ($response) {
  301. return $response;
  302. }, 10);
  303. $this->controller->getEventManager()->setSharedManager($events);
  304. $result = $this->controller->dispatch($this->request, $this->response);
  305. $this->assertSame($response, $result);
  306. }
  307. public function testDispatchInjectsEventIntoController()
  308. {
  309. $this->controller->dispatch($this->request, $this->response);
  310. $event = $this->controller->getEvent();
  311. $this->assertNotNull($event);
  312. $this->assertSame($this->event, $event);
  313. }
  314. public function testControllerIsLocatorAware()
  315. {
  316. $this->assertInstanceOf('Zend\ServiceManager\ServiceLocatorAwareInterface', $this->controller);
  317. }
  318. public function testControllerIsEventAware()
  319. {
  320. $this->assertInstanceOf('Zend\Mvc\InjectApplicationEventInterface', $this->controller);
  321. }
  322. public function testControllerIsPluggable()
  323. {
  324. $this->assertTrue(method_exists($this->controller, 'plugin'));
  325. }
  326. public function testMethodOverloadingShouldReturnPluginWhenFound()
  327. {
  328. $plugin = $this->controller->url();
  329. $this->assertInstanceOf('Zend\Mvc\Controller\Plugin\Url', $plugin);
  330. }
  331. public function testMethodOverloadingShouldInvokePluginAsFunctorIfPossible()
  332. {
  333. $model = $this->event->getViewModel();
  334. $this->controller->layout('alternate/layout');
  335. $this->assertEquals('alternate/layout', $model->getTemplate());
  336. }
  337. public function testParsingDataAsJsonWillReturnAsArray()
  338. {
  339. $this->request->setMethod('POST');
  340. $this->request->getHeaders()->addHeaderLine('Content-type', 'application/json');
  341. $this->request->setContent('{"foo":"bar"}');
  342. $this->controller->getEventManager()->setSharedManager(new SharedEventManager());
  343. $result = $this->controller->dispatch($this->request, $this->response);
  344. $this->assertInternalType('array', $result);
  345. $this->assertEquals(array('entity' => array('foo' => 'bar')), $result);
  346. }
  347. public function matchingContentTypes()
  348. {
  349. return array(
  350. 'exact-first' => array('application/hal+json'),
  351. 'exact-second' => array('application/json'),
  352. 'with-charset' => array('application/json; charset=utf-8'),
  353. 'with-whitespace' => array('application/json '),
  354. );
  355. }
  356. /**
  357. * @dataProvider matchingContentTypes
  358. */
  359. public function testRequestingContentTypeReturnsTrueForValidMatches($contentType)
  360. {
  361. $this->request->getHeaders()->addHeaderLine('Content-Type', $contentType);
  362. $this->assertTrue($this->controller->requestHasContentType($this->request, TestAsset\RestfulTestController::CONTENT_TYPE_JSON));
  363. }
  364. public function nonMatchingContentTypes()
  365. {
  366. return array(
  367. 'specific-type' => array('application/xml'),
  368. 'generic-type' => array('text/json'),
  369. );
  370. }
  371. /**
  372. * @dataProvider nonMatchingContentTypes
  373. */
  374. public function testRequestingContentTypeReturnsFalseForInvalidMatches($contentType)
  375. {
  376. $this->request->getHeaders()->addHeaderLine('Content-Type', $contentType);
  377. $this->assertFalse($this->controller->requestHasContentType($this->request, TestAsset\RestfulTestController::CONTENT_TYPE_JSON));
  378. }
  379. public function testDispatchWithUnrecognizedMethodReturns405Response()
  380. {
  381. $this->request->setMethod('PROPFIND');
  382. $result = $this->controller->dispatch($this->request, $this->response);
  383. $this->assertInstanceOf('Zend\Http\Response', $result);
  384. $this->assertEquals(405, $result->getStatusCode());
  385. }
  386. public function testDispatchInvokesGetMethodWhenNoActionPresentAndZeroIdentifierPresentOnGet()
  387. {
  388. $entity = new stdClass;
  389. $this->controller->entity = $entity;
  390. $this->routeMatch->setParam('id', 0);
  391. $result = $this->controller->dispatch($this->request, $this->response);
  392. $this->assertArrayHasKey('entity', $result);
  393. $this->assertEquals($entity, $result['entity']);
  394. $this->assertEquals('get', $this->routeMatch->getParam('action'));
  395. }
  396. public function testIdentifierNameDefaultsToId()
  397. {
  398. $this->assertEquals('id', $this->controller->getIdentifierName());
  399. }
  400. public function testCanSetIdentifierName()
  401. {
  402. $this->controller->setIdentifierName('name');
  403. $this->assertEquals('name', $this->controller->getIdentifierName());
  404. }
  405. public function testUsesConfiguredIdentifierNameToGetIdentifier()
  406. {
  407. $r = new ReflectionObject($this->controller);
  408. $getIdentifier = $r->getMethod('getIdentifier');
  409. $getIdentifier->setAccessible(true);
  410. $this->controller->setIdentifierName('name');
  411. $this->routeMatch->setParam('name', 'foo');
  412. $result = $getIdentifier->invoke($this->controller, $this->routeMatch, $this->request);
  413. $this->assertEquals('foo', $result);
  414. $this->routeMatch->setParam('name', false);
  415. $this->request->getQuery()->set('name', 'bar');
  416. $result = $getIdentifier->invoke($this->controller, $this->routeMatch, $this->request);
  417. $this->assertEquals('bar', $result);
  418. }
  419. /**
  420. * @dataProvider testNotImplementedMethodSets504HttpCodeProvider
  421. */
  422. public function testNotImplementedMethodSets504HttpCode($method, $content, array $routeParams)
  423. {
  424. $this->request->setMethod($method);
  425. if ($content) {
  426. $this->request->setContent($content);
  427. }
  428. foreach ($routeParams as $name => $value) {
  429. $this->routeMatch->setParam($name, $value);
  430. }
  431. $result = $this->emptyController->dispatch($this->request, $this->response);
  432. $response = $this->emptyController->getResponse();
  433. $this->assertEquals(405, $response->getStatusCode());
  434. $this->assertEquals('Method Not Allowed', $this->response->getReasonPhrase());
  435. }
  436. public function testNotImplementedMethodSets504HttpCodeProvider()
  437. {
  438. return array(
  439. array('DELETE', array(), array('id' => 1)), // AbstractRestfulController::delete()
  440. array('DELETE', array(), array()), // AbstractRestfulController::deleteList()
  441. array('GET', array(), array('id' => 1)), // AbstractRestfulController::get()
  442. array('GET', array(), array()), // AbstractRestfulController::getList()
  443. array('HEAD', array(), array('id' => 1)), // AbstractRestfulController::head()
  444. array('HEAD', array(), array()), // AbstractRestfulController::head()
  445. array('OPTIONS', array(), array()), // AbstractRestfulController::options()
  446. array('PATCH', http_build_query(array('foo' => 1)), array('id' => 1)), // AbstractRestfulController::patch()
  447. array('PATCH', json_encode(array('foo' => 1)), array('id' => 1)), // AbstractRestfulController::patch()
  448. array('PATCH', http_build_query(array('foo' => 1)), array()), // AbstractRestfulController::patchList()
  449. array('PATCH', json_encode(array('foo' => 1)), array()), // AbstractRestfulController::patchList()
  450. array('POST', http_build_query(array('foo' => 1)), array('id' => 1)), // AbstractRestfulController::update()
  451. array('POST', json_encode(array('foo' => 1)), array('id' => 1)), // AbstractRestfulController::update()
  452. array('POST', http_build_query(array('foo' => 1)), array()), // AbstractRestfulController::create()
  453. array('POST', json_encode(array('foo' => 1)), array()), // AbstractRestfulController::create()
  454. array('PUT', http_build_query(array('foo' => 1)), array('id' => 1)), // AbstractRestfulController::update()
  455. array('PUT', json_encode(array('foo' => 1)), array('id' => 1)), // AbstractRestfulController::update()
  456. array('PUT', http_build_query(array('foo' => 1)), array()), // AbstractRestfulController::replaceList()
  457. array('PUT', json_encode(array('foo' => 1)), array()), // AbstractRestfulController::replaceList()
  458. );
  459. }
  460. }