ControllerTest.php 38 KB

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