ControllerTest.php 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP Project
  13. * @since 1.2.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\Controller;
  17. use Cake\Controller\Controller;
  18. use Cake\Controller\Exception\MissingActionException;
  19. use Cake\Core\Configure;
  20. use Cake\Datasource\Paging\PaginatedInterface;
  21. use Cake\Event\Event;
  22. use Cake\Event\EventInterface;
  23. use Cake\Http\Exception\NotFoundException;
  24. use Cake\Http\Response;
  25. use Cake\Http\ServerRequest;
  26. use Cake\Routing\Router;
  27. use Cake\TestSuite\TestCase;
  28. use Cake\View\View;
  29. use Laminas\Diactoros\Uri;
  30. use ReflectionFunction;
  31. use RuntimeException;
  32. use TestApp\Controller\Admin\PostsController as AdminPostsController;
  33. use TestApp\Controller\ArticlesController;
  34. use TestApp\Controller\ContentTypesController;
  35. use TestApp\Controller\PagesController;
  36. use TestApp\Controller\PostsController;
  37. use TestApp\Controller\TestController;
  38. use TestApp\Controller\WithDefaultTableController;
  39. use TestApp\Model\Table\ArticlesTable;
  40. use TestApp\Model\Table\PostsTable;
  41. use TestPlugin\Controller\Admin\CommentsController;
  42. use TestPlugin\Controller\TestPluginController;
  43. use UnexpectedValueException;
  44. /**
  45. * ControllerTest class
  46. */
  47. class ControllerTest extends TestCase
  48. {
  49. /**
  50. * fixtures property
  51. *
  52. * @var array<string>
  53. */
  54. protected array $fixtures = [
  55. 'core.Comments',
  56. 'core.Posts',
  57. ];
  58. /**
  59. * reset environment.
  60. */
  61. public function setUp(): void
  62. {
  63. parent::setUp();
  64. static::setAppNamespace();
  65. Router::reload();
  66. }
  67. /**
  68. * tearDown
  69. */
  70. public function tearDown(): void
  71. {
  72. parent::tearDown();
  73. $this->clearPlugins();
  74. }
  75. /**
  76. * test autoload default table
  77. */
  78. public function testTableAutoload(): void
  79. {
  80. $request = new ServerRequest(['url' => 'controller/posts/index']);
  81. $Controller = new Controller($request, new Response(), 'Articles');
  82. $this->assertInstanceOf(
  83. 'TestApp\Model\Table\ArticlesTable',
  84. $Controller->Articles
  85. );
  86. }
  87. /**
  88. * testUndefinedPropertyError
  89. */
  90. public function testUndefinedPropertyError(): void
  91. {
  92. $controller = new Controller();
  93. $this->expectNotice();
  94. $this->expectNoticeMessage(sprintf(
  95. 'Undefined property: Controller::$Foo in %s on line %s',
  96. __FILE__,
  97. __LINE__ + 2
  98. ));
  99. $controller->Foo->baz();
  100. }
  101. /**
  102. * testGetTable method
  103. */
  104. public function testGetTable(): void
  105. {
  106. $request = new ServerRequest(['url' => 'controller/posts/index']);
  107. $Controller = new Controller($request, new Response());
  108. $this->assertFalse(isset($Controller->Articles));
  109. $result = $Controller->fetchTable('Articles');
  110. $this->assertInstanceOf(
  111. 'TestApp\Model\Table\ArticlesTable',
  112. $result
  113. );
  114. }
  115. public function testAutoLoadModelUsingDefaultTable()
  116. {
  117. Configure::write('App.namespace', 'TestApp');
  118. $Controller = new WithDefaultTableController(new ServerRequest(), new Response());
  119. $this->assertInstanceOf(PostsTable::class, $Controller->Posts);
  120. Configure::write('App.namespace', 'App');
  121. }
  122. /**
  123. * @link https://github.com/cakephp/cakephp/issues/14804
  124. */
  125. public function testAutoLoadTableUsingFqcn(): void
  126. {
  127. Configure::write('App.namespace', 'TestApp');
  128. $Controller = new ArticlesController(new ServerRequest(), new Response());
  129. $this->assertInstanceOf(ArticlesTable::class, $Controller->fetchTable());
  130. Configure::write('App.namespace', 'App');
  131. }
  132. public function testGetTableInPlugins(): void
  133. {
  134. $this->loadPlugins(['TestPlugin']);
  135. $Controller = new TestPluginController();
  136. $Controller->setPlugin('TestPlugin');
  137. $this->assertFalse(isset($Controller->TestPluginComments));
  138. $result = $Controller->fetchTable('TestPlugin.TestPluginComments');
  139. $this->assertInstanceOf(
  140. 'TestPlugin\Model\Table\TestPluginCommentsTable',
  141. $result
  142. );
  143. }
  144. /**
  145. * Test that the constructor sets defaultTable properly.
  146. */
  147. public function testConstructSetDefaultTable(): void
  148. {
  149. $this->loadPlugins(['TestPlugin']);
  150. $request = new ServerRequest();
  151. $response = new Response();
  152. $controller = new PostsController($request, $response);
  153. $this->assertInstanceOf(PostsTable::class, $controller->fetchTable());
  154. $controller = new AdminPostsController($request, $response);
  155. $this->assertInstanceOf(PostsTable::class, $controller->fetchTable());
  156. $request = $request->withParam('plugin', 'TestPlugin');
  157. $controller = new CommentsController($request, $response);
  158. $this->assertInstanceOf('TestPlugin\Model\Table\CommentsTable', $controller->fetchTable());
  159. }
  160. /**
  161. * testConstructClassesWithComponents method
  162. */
  163. public function testConstructClassesWithComponents(): void
  164. {
  165. $this->loadPlugins(['TestPlugin']);
  166. $Controller = new TestPluginController(new ServerRequest(), new Response());
  167. $Controller->loadComponent('TestPlugin.Other');
  168. $this->assertInstanceOf('TestPlugin\Controller\Component\OtherComponent', $Controller->Other);
  169. }
  170. /**
  171. * testRender method
  172. */
  173. public function testRender(): void
  174. {
  175. $this->loadPlugins(['TestPlugin']);
  176. $request = new ServerRequest([
  177. 'url' => 'controller_posts/index',
  178. 'params' => [
  179. 'action' => 'header',
  180. ],
  181. ]);
  182. $Controller = new Controller($request, new Response());
  183. $Controller->viewBuilder()->setTemplatePath('Posts');
  184. $result = $Controller->render('index');
  185. $this->assertMatchesRegularExpression('/posts index/', (string)$result);
  186. $Controller->viewBuilder()->setTemplate('index');
  187. $result = $Controller->render();
  188. $this->assertMatchesRegularExpression('/posts index/', (string)$result);
  189. $result = $Controller->render('/element/test_element');
  190. $this->assertMatchesRegularExpression('/this is the test element/', (string)$result);
  191. }
  192. /**
  193. * Test that render() will do content negotiation when supported
  194. * by the controller.
  195. */
  196. public function testRenderViewClassesContentNegotiationMatch()
  197. {
  198. $request = new ServerRequest([
  199. 'url' => '/',
  200. 'environment' => ['HTTP_ACCEPT' => 'application/json'],
  201. ]);
  202. $controller = new ContentTypesController($request, new Response());
  203. $controller->all();
  204. $response = $controller->render();
  205. $this->assertSame('application/json', $response->getHeaderLine('Content-Type'), 'Has correct header');
  206. $this->assertNotEmpty(json_decode($response->getBody() . ''), 'Body should be json');
  207. }
  208. /**
  209. * Test that render() will do content negotiation when supported
  210. * by the controller.
  211. */
  212. public function testRenderViewClassContentNegotiationMatchLast()
  213. {
  214. $request = new ServerRequest([
  215. 'url' => '/',
  216. 'environment' => ['HTTP_ACCEPT' => 'application/xml'],
  217. ]);
  218. $controller = new ContentTypesController($request, new Response());
  219. $controller->all();
  220. $response = $controller->render();
  221. $this->assertSame(
  222. 'application/xml; charset=UTF-8',
  223. $response->getHeaderLine('Content-Type'),
  224. 'Has correct header'
  225. );
  226. $this->assertStringContainsString('<?xml', $response->getBody() . '');
  227. }
  228. public function testRenderViewClassesContentNegotiationNoMatch()
  229. {
  230. $request = new ServerRequest([
  231. 'url' => '/',
  232. 'environment' => ['HTTP_ACCEPT' => 'text/plain'],
  233. 'params' => ['plugin' => null, 'controller' => 'ContentTypes', 'action' => 'all'],
  234. ]);
  235. $controller = new ContentTypesController($request, new Response());
  236. $controller->all();
  237. $response = $controller->render();
  238. $this->assertSame('text/html; charset=UTF-8', $response->getHeaderLine('Content-Type'));
  239. $this->assertStringContainsString('hello world', $response->getBody() . '');
  240. }
  241. /**
  242. * Test that render() will skip content-negotiation when a view class is set.
  243. */
  244. public function testRenderViewClassContentNegotiationSkipWithViewClass()
  245. {
  246. $request = new ServerRequest([
  247. 'url' => '/',
  248. 'environment' => ['HTTP_ACCEPT' => 'application/xml'],
  249. 'params' => ['plugin' => null, 'controller' => 'ContentTypes', 'action' => 'all'],
  250. ]);
  251. $controller = new ContentTypesController($request, new Response());
  252. $controller->all();
  253. $controller->viewBuilder()->setClassName(View::class);
  254. $response = $controller->render();
  255. $this->assertSame(
  256. 'text/html; charset=UTF-8',
  257. $response->getHeaderLine('Content-Type'),
  258. 'Should not be XML response.'
  259. );
  260. $this->assertStringContainsString('hello world', $response->getBody() . '');
  261. }
  262. public function testRenderViewClassesSetContentTypeHeader()
  263. {
  264. $request = new ServerRequest([
  265. 'url' => '/',
  266. 'environment' => ['HTTP_ACCEPT' => 'text/plain'],
  267. 'params' => ['plugin' => null, 'controller' => 'ContentTypes', 'action' => 'plain'],
  268. ]);
  269. $controller = new ContentTypesController($request, new Response());
  270. $controller->plain();
  271. $response = $controller->render();
  272. $this->assertSame('text/plain; charset=UTF-8', $response->getHeaderLine('Content-Type'));
  273. $this->assertStringContainsString('hello world', $response->getBody() . '');
  274. }
  275. public function testRenderViewClassesUsesSingleMimeExt()
  276. {
  277. $request = new ServerRequest([
  278. 'url' => '/',
  279. 'environment' => [],
  280. 'params' => ['plugin' => null, 'controller' => 'ContentTypes', 'action' => 'all', '_ext' => 'json'],
  281. ]);
  282. $controller = new ContentTypesController($request, new Response());
  283. $controller->all();
  284. $response = $controller->render();
  285. $this->assertSame('application/json', $response->getHeaderLine('Content-Type'));
  286. $this->assertNotEmpty(json_decode($response->getBody() . ''), 'Body should be json');
  287. }
  288. public function testRenderViewClassesUsesMultiMimeExt()
  289. {
  290. $request = new ServerRequest([
  291. 'url' => '/',
  292. 'environment' => [],
  293. 'params' => ['plugin' => null, 'controller' => 'ContentTypes', 'action' => 'all', '_ext' => 'xml'],
  294. ]);
  295. $controller = new ContentTypesController($request, new Response());
  296. $controller->all();
  297. $response = $controller->render();
  298. $this->assertSame('application/xml; charset=UTF-8', $response->getHeaderLine('Content-Type'));
  299. $this->assertTextStartsWith('<?xml', $response->getBody() . '', 'Body should be xml');
  300. }
  301. /**
  302. * test view rendering changing response
  303. */
  304. public function testRenderViewChangesResponse(): void
  305. {
  306. $request = new ServerRequest([
  307. 'url' => 'controller_posts/index',
  308. 'params' => [
  309. 'action' => 'header',
  310. ],
  311. ]);
  312. $controller = new Controller($request, new Response());
  313. $controller->viewBuilder()->setTemplatePath('Posts');
  314. $result = $controller->render('header');
  315. $this->assertStringContainsString('header template', (string)$result);
  316. $this->assertTrue($controller->getResponse()->hasHeader('X-view-template'));
  317. $this->assertSame('yes', $controller->getResponse()->getHeaderLine('X-view-template'));
  318. }
  319. /**
  320. * test that a component beforeRender can change the controller view class.
  321. */
  322. public function testBeforeRenderCallbackChangingViewClass(): void
  323. {
  324. $Controller = new Controller(new ServerRequest(), new Response());
  325. $Controller->getEventManager()->on('Controller.beforeRender', function (EventInterface $event): void {
  326. $controller = $event->getSubject();
  327. $controller->viewBuilder()->setClassName('Json');
  328. });
  329. $Controller->set([
  330. 'test' => 'value',
  331. ]);
  332. $Controller->viewBuilder()->setOption('serialize', ['test']);
  333. $debug = Configure::read('debug');
  334. Configure::write('debug', false);
  335. $result = $Controller->render('index');
  336. $this->assertSame('{"test":"value"}', (string)$result->getBody());
  337. Configure::write('debug', $debug);
  338. }
  339. /**
  340. * test that a component beforeRender can change the controller view class.
  341. */
  342. public function testBeforeRenderEventCancelsRender(): void
  343. {
  344. $Controller = new Controller(new ServerRequest(), new Response());
  345. $Controller->getEventManager()->on('Controller.beforeRender', function (EventInterface $event) {
  346. return false;
  347. });
  348. $result = $Controller->render('index');
  349. $this->assertInstanceOf('Cake\Http\Response', $result);
  350. }
  351. public function testControllerRedirect(): void
  352. {
  353. $Controller = new Controller();
  354. $uri = new Uri('/foo/bar');
  355. $response = $Controller->redirect($uri);
  356. $this->assertSame('http://localhost/foo/bar', $response->getHeaderLine('Location'));
  357. $Controller = new Controller();
  358. $uri = new Uri('http://cakephp.org/foo/bar');
  359. $response = $Controller->redirect($uri);
  360. $this->assertSame('http://cakephp.org/foo/bar', $response->getHeaderLine('Location'));
  361. }
  362. /**
  363. * Generates status codes for redirect test.
  364. *
  365. * @return array
  366. */
  367. public static function statusCodeProvider(): array
  368. {
  369. return [
  370. [300, 'Multiple Choices'],
  371. [301, 'Moved Permanently'],
  372. [302, 'Found'],
  373. [303, 'See Other'],
  374. [304, 'Not Modified'],
  375. [305, 'Use Proxy'],
  376. [307, 'Temporary Redirect'],
  377. [403, 'Forbidden'],
  378. ];
  379. }
  380. /**
  381. * testRedirect method
  382. *
  383. * @dataProvider statusCodeProvider
  384. */
  385. public function testRedirectByCode(int $code, string $msg): void
  386. {
  387. $Controller = new Controller(null, new Response());
  388. $response = $Controller->redirect('http://cakephp.org', (int)$code);
  389. $this->assertSame($response, $Controller->getResponse());
  390. $this->assertEquals($code, $response->getStatusCode());
  391. $this->assertSame('http://cakephp.org', $response->getHeaderLine('Location'));
  392. $this->assertFalse($Controller->isAutoRenderEnabled());
  393. }
  394. /**
  395. * test that beforeRedirect callbacks can set the URL that is being redirected to.
  396. */
  397. public function testRedirectBeforeRedirectModifyingUrl(): void
  398. {
  399. $Controller = new Controller(null, new Response());
  400. $Controller->getEventManager()->on('Controller.beforeRedirect', function (EventInterface $event, $url, Response $response): void {
  401. $controller = $event->getSubject();
  402. $controller->setResponse($response->withLocation('https://book.cakephp.org'));
  403. });
  404. $response = $Controller->redirect('http://cakephp.org', 301);
  405. $this->assertSame('https://book.cakephp.org', $response->getHeaderLine('Location'));
  406. $this->assertSame(301, $response->getStatusCode());
  407. }
  408. /**
  409. * test that beforeRedirect callback returning null doesn't affect things.
  410. */
  411. public function testRedirectBeforeRedirectModifyingStatusCode(): void
  412. {
  413. $response = new Response();
  414. $Controller = new Controller(null, $response);
  415. $Controller->getEventManager()->on('Controller.beforeRedirect', function (EventInterface $event, $url, Response $response): void {
  416. $controller = $event->getSubject();
  417. $controller->setResponse($response->withStatus(302));
  418. });
  419. $response = $Controller->redirect('http://cakephp.org', 301);
  420. $this->assertSame('http://cakephp.org', $response->getHeaderLine('Location'));
  421. $this->assertSame(302, $response->getStatusCode());
  422. }
  423. public function testRedirectBeforeRedirectListenerReturnResponse(): void
  424. {
  425. $Controller = new Controller(null, new Response());
  426. $newResponse = new Response();
  427. $Controller->getEventManager()->on('Controller.beforeRedirect', function (EventInterface $event, $url, Response $response) use ($newResponse) {
  428. return $newResponse;
  429. });
  430. $result = $Controller->redirect('http://cakephp.org');
  431. $this->assertSame($newResponse, $result);
  432. $this->assertSame($newResponse, $Controller->getResponse());
  433. }
  434. /**
  435. * testReferer method
  436. */
  437. public function testReferer(): void
  438. {
  439. $request = new ServerRequest([
  440. 'environment' => ['HTTP_REFERER' => 'http://localhost/posts/index'],
  441. ]);
  442. $Controller = new Controller($request);
  443. $result = $Controller->referer();
  444. $this->assertSame('/posts/index', $result);
  445. $request = new ServerRequest([
  446. 'environment' => ['HTTP_REFERER' => 'http://localhost/posts/index'],
  447. ]);
  448. $Controller = new Controller($request);
  449. $result = $Controller->referer(['controller' => 'Posts', 'action' => 'index'], true);
  450. $this->assertSame('/posts/index', $result);
  451. $request = $this->getMockBuilder('Cake\Http\ServerRequest')
  452. ->onlyMethods(['referer'])
  453. ->getMock();
  454. $request = new ServerRequest([
  455. 'environment' => ['HTTP_REFERER' => 'http://localhost/posts/index'],
  456. ]);
  457. $Controller = new Controller($request);
  458. $result = $Controller->referer(null, false);
  459. $this->assertSame('http://localhost/posts/index', $result);
  460. $Controller = new Controller(null);
  461. $result = $Controller->referer('/', false);
  462. $this->assertSame('http://localhost/', $result);
  463. }
  464. /**
  465. * Test that the referer is not absolute if it is '/'.
  466. *
  467. * This avoids the base path being applied twice on string urls.
  468. */
  469. public function testRefererSlash(): void
  470. {
  471. $request = new ServerRequest();
  472. $request = $request->withAttribute('base', '/base');
  473. Router::setRequest($request);
  474. $controller = new Controller($request);
  475. $result = $controller->referer('/', true);
  476. $this->assertSame('/', $result);
  477. $controller = new Controller($request);
  478. $result = $controller->referer('/some/path', true);
  479. $this->assertSame('/some/path', $result);
  480. }
  481. /**
  482. * Tests that the startup process calls the correct functions
  483. */
  484. public function testStartupProcess(): void
  485. {
  486. $eventManager = $this->getMockBuilder('Cake\Event\EventManagerInterface')->getMock();
  487. $controller = new Controller(null, null, null, $eventManager);
  488. $eventManager
  489. ->expects($this->exactly(2))
  490. ->method('dispatch')
  491. ->withConsecutive(
  492. [$this->callback(function (EventInterface $event) {
  493. return $event->getName() === 'Controller.initialize';
  494. })],
  495. [$this->callback(function (EventInterface $event) {
  496. return $event->getName() === 'Controller.startup';
  497. })]
  498. )
  499. ->will($this->returnValue(new Event('stub')));
  500. $controller->startupProcess();
  501. }
  502. /**
  503. * Tests that the shutdown process calls the correct functions
  504. */
  505. public function testShutdownProcess(): void
  506. {
  507. $eventManager = $this->getMockBuilder('Cake\Event\EventManagerInterface')->getMock();
  508. $controller = new Controller(null, null, null, $eventManager);
  509. $eventManager->expects($this->once())
  510. ->method('dispatch')
  511. ->with($this->callback(function (EventInterface $event) {
  512. return $event->getName() === 'Controller.shutdown';
  513. }))
  514. ->will($this->returnValue(new Event('stub')));
  515. $controller->shutdownProcess();
  516. }
  517. /**
  518. * test using Controller::paginate()
  519. */
  520. public function testPaginate(): void
  521. {
  522. $request = new ServerRequest(['url' => 'controller_posts/index']);
  523. $response = new Response();
  524. $Controller = new Controller($request, $response);
  525. $Controller->setRequest($Controller->getRequest()->withQueryParams([
  526. 'posts' => [
  527. 'page' => 2,
  528. 'limit' => 2,
  529. ],
  530. ]));
  531. $this->assertNotContains('Paginator', $Controller->viewBuilder()->getHelpers());
  532. $this->assertArrayNotHasKey('Paginator', $Controller->viewBuilder()->getHelpers());
  533. $results = $Controller->paginate('Posts');
  534. $this->assertInstanceOf(PaginatedInterface::class, $results);
  535. $this->assertCount(3, $results);
  536. $results = $Controller->paginate($this->getTableLocator()->get('Posts'));
  537. $this->assertInstanceOf(PaginatedInterface::class, $results);
  538. $this->assertCount(3, $results);
  539. $paging = $Controller->getRequest()->getAttribute('paging');
  540. $this->assertSame($results->currentPage(), 1);
  541. $this->assertSame($results->pageCount(), 1);
  542. $this->assertFalse($results->hasPrevPage());
  543. $this->assertFalse($results->hasPrevPage());
  544. $this->assertNull($results->pagingParam('scope'));
  545. $results = $Controller->paginate(
  546. $this->getTableLocator()->get('Posts'),
  547. ['scope' => 'posts', 'className' => 'Numeric']
  548. );
  549. $this->assertInstanceOf(PaginatedInterface::class, $results);
  550. $this->assertCount(1, $results);
  551. $paging = $Controller->getRequest()->getAttribute('paging');
  552. $this->assertSame($results->currentPage(), 2);
  553. $this->assertSame($results->pageCount(), 2);
  554. $this->assertTrue($results->hasPrevPage());
  555. $this->assertFalse($results->hasNextPage());
  556. $this->assertSame($results->pagingParam('scope'), 'posts');
  557. $results = $Controller->paginate(
  558. $this->getTableLocator()->get('Posts'),
  559. ['className' => 'Simple']
  560. );
  561. $this->assertInstanceOf(PaginatedInterface::class, $results);
  562. $this->assertNull($results->pageCount(), 'SimplePaginator doesn\'t have a page count');
  563. }
  564. /**
  565. * test that paginate uses modelClass property.
  566. */
  567. public function testPaginateUsesModelClass(): void
  568. {
  569. $request = new ServerRequest([
  570. 'url' => 'controller_posts/index',
  571. ]);
  572. $response = new Response();
  573. $Controller = new Controller($request, $response, 'Posts');
  574. $results = $Controller->paginate();
  575. $this->assertInstanceOf(PaginatedInterface::class, $results);
  576. }
  577. public function testPaginateException()
  578. {
  579. $this->expectException(NotFoundException::class);
  580. $request = new ServerRequest(['url' => 'controller_posts/index?page=2&limit=100']);
  581. $response = new Response();
  582. $Controller = new Controller($request, $response, 'Posts');
  583. $Controller->paginate();
  584. }
  585. /**
  586. * testMissingAction method
  587. */
  588. public function testGetActionMissingAction(): void
  589. {
  590. $this->expectException(MissingActionException::class);
  591. $this->expectExceptionMessage('Action TestController::missing() could not be found, or is not accessible.');
  592. $url = new ServerRequest([
  593. 'url' => 'test/missing',
  594. 'params' => ['controller' => 'Test', 'action' => 'missing'],
  595. ]);
  596. $response = new Response();
  597. $Controller = new TestController($url, $response);
  598. $Controller->getAction();
  599. }
  600. /**
  601. * test invoking private methods.
  602. */
  603. public function testGetActionPrivate(): void
  604. {
  605. $this->expectException(MissingActionException::class);
  606. $this->expectExceptionMessage('Action TestController::private_m() could not be found, or is not accessible.');
  607. $url = new ServerRequest([
  608. 'url' => 'test/private_m/',
  609. 'params' => ['controller' => 'Test', 'action' => 'private_m'],
  610. ]);
  611. $response = new Response();
  612. $Controller = new TestController($url, $response);
  613. $Controller->getAction();
  614. }
  615. /**
  616. * test invoking protected methods.
  617. */
  618. public function testGetActionProtected(): void
  619. {
  620. $this->expectException(MissingActionException::class);
  621. $this->expectExceptionMessage('Action TestController::protected_m() could not be found, or is not accessible.');
  622. $url = new ServerRequest([
  623. 'url' => 'test/protected_m/',
  624. 'params' => ['controller' => 'Test', 'action' => 'protected_m'],
  625. ]);
  626. $response = new Response();
  627. $Controller = new TestController($url, $response);
  628. $Controller->getAction();
  629. }
  630. /**
  631. * test invoking controller methods.
  632. */
  633. public function testGetActionBaseMethods(): void
  634. {
  635. $this->expectException(MissingActionException::class);
  636. $this->expectExceptionMessage('Action TestController::redirect() could not be found, or is not accessible.');
  637. $url = new ServerRequest([
  638. 'url' => 'test/redirect/',
  639. 'params' => ['controller' => 'Test', 'action' => 'redirect'],
  640. ]);
  641. $response = new Response();
  642. $Controller = new TestController($url, $response);
  643. $Controller->getAction();
  644. }
  645. /**
  646. * test invoking action method with mismatched casing.
  647. */
  648. public function testGetActionMethodCasing(): void
  649. {
  650. $this->expectException(MissingActionException::class);
  651. $this->expectExceptionMessage('Action TestController::RETURNER() could not be found, or is not accessible.');
  652. $url = new ServerRequest([
  653. 'url' => 'test/RETURNER/',
  654. 'params' => ['controller' => 'Test', 'action' => 'RETURNER'],
  655. ]);
  656. $response = new Response();
  657. $Controller = new TestController($url, $response);
  658. $Controller->getAction();
  659. }
  660. public function testGetActionArgsReflection(): void
  661. {
  662. $request = new ServerRequest([
  663. 'url' => 'test/reflection/1',
  664. 'params' => [
  665. 'controller' => 'Test',
  666. 'action' => 'reflection',
  667. 'pass' => ['1'],
  668. ],
  669. ]);
  670. $controller = new TestController($request, new Response());
  671. $closure = $controller->getAction();
  672. $args = (new ReflectionFunction($closure))->getParameters();
  673. $this->assertSame('Parameter #0 [ <required> $passed ]', (string)$args[0]);
  674. $this->assertSame('Parameter #1 [ <required> Cake\ORM\Table $table ]', (string)$args[1]);
  675. }
  676. /**
  677. * test invoking controller methods.
  678. */
  679. public function testInvokeActionReturnValue(): void
  680. {
  681. $url = new ServerRequest([
  682. 'url' => 'test/returner/',
  683. 'params' => [
  684. 'controller' => 'Test',
  685. 'action' => 'returner',
  686. 'pass' => [],
  687. ],
  688. ]);
  689. $response = new Response();
  690. $Controller = new TestController($url, $response);
  691. $Controller->invokeAction($Controller->getAction(), $Controller->getRequest()->getParam('pass'));
  692. $this->assertSame('I am from the controller.', (string)$Controller->getResponse());
  693. }
  694. /**
  695. * test invoking controller methods with passed params
  696. */
  697. public function testInvokeActionWithPassedParams(): void
  698. {
  699. $request = new ServerRequest([
  700. 'url' => 'test/index/1/2',
  701. 'params' => [
  702. 'controller' => 'Test',
  703. 'action' => 'index',
  704. 'pass' => ['param1' => '1', 'param2' => '2'],
  705. ],
  706. ]);
  707. $controller = new TestController($request, new Response());
  708. $controller->disableAutoRender();
  709. $controller->invokeAction($controller->getAction(), array_values($controller->getRequest()->getParam('pass')));
  710. $this->assertEquals(
  711. ['testId' => '1', 'test2Id' => '2'],
  712. $controller->getRequest()->getData()
  713. );
  714. }
  715. /**
  716. * test invalid return value from action method.
  717. */
  718. public function testInvokeActionException(): void
  719. {
  720. $this->expectException(UnexpectedValueException::class);
  721. $this->expectExceptionMessage(
  722. 'Controller actions can only return ResponseInterface instance or null. '
  723. . 'Got string instead.'
  724. );
  725. $url = new ServerRequest([
  726. 'url' => 'test/willCauseException',
  727. 'params' => [
  728. 'controller' => 'Test',
  729. 'action' => 'willCauseException',
  730. 'pass' => [],
  731. ],
  732. ]);
  733. $response = new Response();
  734. $Controller = new TestController($url, $response);
  735. $Controller->invokeAction($Controller->getAction(), $Controller->getRequest()->getParam('pass'));
  736. }
  737. /**
  738. * test that a classes namespace is used in the viewPath.
  739. */
  740. public function testViewPathConventions(): void
  741. {
  742. $request = new ServerRequest([
  743. 'url' => 'admin/posts',
  744. 'params' => ['prefix' => 'Admin'],
  745. ]);
  746. $response = new Response();
  747. $Controller = new AdminPostsController($request, $response);
  748. $Controller->getEventManager()->on('Controller.beforeRender', function (EventInterface $e) {
  749. return $e->getSubject()->getResponse();
  750. });
  751. $Controller->render();
  752. $this->assertSame('Admin' . DS . 'Posts', $Controller->viewBuilder()->getTemplatePath());
  753. $request = $request->withParam('prefix', 'admin/super');
  754. $response = new Response();
  755. $Controller = new AdminPostsController($request, $response);
  756. $Controller->getEventManager()->on('Controller.beforeRender', function (EventInterface $e) {
  757. return $e->getSubject()->getResponse();
  758. });
  759. $Controller->render();
  760. $this->assertSame('Admin' . DS . 'Super' . DS . 'Posts', $Controller->viewBuilder()->getTemplatePath());
  761. $request = new ServerRequest([
  762. 'url' => 'pages/home',
  763. 'params' => [
  764. 'prefix' => false,
  765. ],
  766. ]);
  767. $Controller = new PagesController($request, $response);
  768. $Controller->getEventManager()->on('Controller.beforeRender', function (EventInterface $e) {
  769. return $e->getSubject()->getResponse();
  770. });
  771. $Controller->render();
  772. $this->assertSame('Pages', $Controller->viewBuilder()->getTemplatePath());
  773. }
  774. /**
  775. * Test the components() method.
  776. */
  777. public function testComponents(): void
  778. {
  779. $request = new ServerRequest(['url' => '/']);
  780. $response = new Response();
  781. $controller = new TestController($request, $response);
  782. $this->assertInstanceOf('Cake\Controller\ComponentRegistry', $controller->components());
  783. $result = $controller->components();
  784. $this->assertSame($result, $controller->components());
  785. }
  786. /**
  787. * Test the components() method with the custom ObjectRegistry.
  788. */
  789. public function testComponentsWithCustomRegistry(): void
  790. {
  791. $request = new ServerRequest(['url' => '/']);
  792. $response = new Response();
  793. $componentRegistry = $this->getMockBuilder('Cake\Controller\ComponentRegistry')
  794. ->addMethods(['offsetGet'])
  795. ->getMock();
  796. $controller = new TestController($request, $response, null, null, $componentRegistry);
  797. $this->assertInstanceOf(get_class($componentRegistry), $controller->components());
  798. $result = $controller->components();
  799. $this->assertSame($result, $controller->components());
  800. }
  801. /**
  802. * Test adding a component
  803. */
  804. public function testLoadComponent(): void
  805. {
  806. $request = new ServerRequest(['url' => '/']);
  807. $response = new Response();
  808. $controller = new TestController($request, $response);
  809. $result = $controller->loadComponent('FormProtection');
  810. $this->assertInstanceOf('Cake\Controller\Component\FormProtectionComponent', $result);
  811. $this->assertSame($result, $controller->FormProtection);
  812. $registry = $controller->components();
  813. $this->assertTrue(isset($registry->FormProtection));
  814. }
  815. /**
  816. * Test adding a component that is a duplicate.
  817. */
  818. public function testLoadComponentDuplicate(): void
  819. {
  820. $request = new ServerRequest(['url' => '/']);
  821. $response = new Response();
  822. $controller = new TestController($request, $response);
  823. $this->assertNotEmpty($controller->loadComponent('FormProtection'));
  824. $this->assertNotEmpty($controller->loadComponent('FormProtection'));
  825. try {
  826. $controller->loadComponent('FormProtection', ['bad' => 'settings']);
  827. $this->fail('No exception');
  828. } catch (RuntimeException $e) {
  829. $this->assertStringContainsString('The "FormProtection" alias has already been loaded', $e->getMessage());
  830. }
  831. }
  832. /**
  833. * Test the isAction method.
  834. */
  835. public function testIsAction(): void
  836. {
  837. $request = new ServerRequest(['url' => '/']);
  838. $response = new Response();
  839. $controller = new TestController($request, $response);
  840. $this->assertFalse($controller->isAction('redirect'));
  841. $this->assertFalse($controller->isAction('beforeFilter'));
  842. $this->assertTrue($controller->isAction('index'));
  843. }
  844. /**
  845. * Test that view variables are being set after the beforeRender event gets dispatched
  846. */
  847. public function testBeforeRenderViewVariables(): void
  848. {
  849. $controller = new AdminPostsController();
  850. $controller->getEventManager()->on('Controller.beforeRender', function (EventInterface $event): void {
  851. /** @var \Cake\Controller\Controller $controller */
  852. $controller = $event->getSubject();
  853. $controller->set('testVariable', 'test');
  854. });
  855. $controller->dispatchEvent('Controller.beforeRender');
  856. $view = $controller->createView();
  857. $this->assertNotEmpty('testVariable', $view->get('testVariable'));
  858. }
  859. /**
  860. * Test that render()'s arguments are available in beforeRender() through view builder.
  861. */
  862. public function testBeforeRenderTemplateAndLayout(): void
  863. {
  864. $Controller = new Controller(new ServerRequest(), new Response());
  865. $Controller->getEventManager()->on('Controller.beforeRender', function ($event): void {
  866. $this->assertSame(
  867. '/Element/test_element',
  868. $event->getSubject()->viewBuilder()->getTemplate()
  869. );
  870. $this->assertSame(
  871. 'default',
  872. $event->getSubject()->viewBuilder()->getLayout()
  873. );
  874. $event->getSubject()->viewBuilder()
  875. ->setTemplatePath('Posts')
  876. ->setTemplate('index');
  877. });
  878. $result = $Controller->render('/Element/test_element', 'default');
  879. $this->assertMatchesRegularExpression('/posts index/', (string)$result);
  880. }
  881. /**
  882. * Test name getter and setter.
  883. */
  884. public function testName(): void
  885. {
  886. $controller = new AdminPostsController();
  887. $this->assertSame('Posts', $controller->getName());
  888. $this->assertSame($controller, $controller->setName('Articles'));
  889. $this->assertSame('Articles', $controller->getName());
  890. }
  891. /**
  892. * Test plugin getter and setter.
  893. */
  894. public function testPlugin(): void
  895. {
  896. $controller = new AdminPostsController();
  897. $this->assertNull($controller->getPlugin());
  898. $this->assertSame($controller, $controller->setPlugin('Articles'));
  899. $this->assertSame('Articles', $controller->getPlugin());
  900. }
  901. /**
  902. * Test request getter and setter.
  903. */
  904. public function testRequest(): void
  905. {
  906. $controller = new AdminPostsController();
  907. $this->assertInstanceOf(ServerRequest::class, $controller->getRequest());
  908. $request = new ServerRequest([
  909. 'params' => [
  910. 'plugin' => 'Posts',
  911. 'pass' => [
  912. 'foo',
  913. 'bar',
  914. ],
  915. ],
  916. ]);
  917. $this->assertSame($controller, $controller->setRequest($request));
  918. $this->assertSame($request, $controller->getRequest());
  919. $this->assertSame('Posts', $controller->getRequest()->getParam('plugin'));
  920. $this->assertEquals(['foo', 'bar'], $controller->getRequest()->getParam('pass'));
  921. }
  922. /**
  923. * Test response getter and setter.
  924. */
  925. public function testResponse(): void
  926. {
  927. $controller = new AdminPostsController();
  928. $this->assertInstanceOf(Response::class, $controller->getResponse());
  929. $response = new Response();
  930. $this->assertSame($controller, $controller->setResponse($response));
  931. $this->assertSame($response, $controller->getResponse());
  932. }
  933. /**
  934. * Test autoRender getter and setter.
  935. */
  936. public function testAutoRender(): void
  937. {
  938. $controller = new AdminPostsController();
  939. $this->assertTrue($controller->isAutoRenderEnabled());
  940. $this->assertSame($controller, $controller->disableAutoRender());
  941. $this->assertFalse($controller->isAutoRenderEnabled());
  942. $this->assertSame($controller, $controller->enableAutoRender());
  943. $this->assertTrue($controller->isAutoRenderEnabled());
  944. }
  945. }