RequestHandlerComponentTest.php 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125
  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(tm) Project
  13. * @since 1.2.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\Controller\Component;
  17. use Cake\Controller\ComponentRegistry;
  18. use Cake\Controller\Component\RequestHandlerComponent;
  19. use Cake\Event\Event;
  20. use Cake\Http\Response;
  21. use Cake\Http\ServerRequest;
  22. use Cake\Routing\Router;
  23. use Cake\TestSuite\TestCase;
  24. use Cake\View\AjaxView;
  25. use Cake\View\JsonView;
  26. use Cake\View\XmlView;
  27. use TestApp\Controller\RequestHandlerTestController;
  28. use Zend\Diactoros\Stream;
  29. /**
  30. * RequestHandlerComponentTest class
  31. */
  32. class RequestHandlerComponentTest extends TestCase
  33. {
  34. /**
  35. * Controller property
  36. *
  37. * @var RequestHandlerTestController
  38. */
  39. public $Controller;
  40. /**
  41. * RequestHandler property
  42. *
  43. * @var RequestHandlerComponent
  44. */
  45. public $RequestHandler;
  46. /**
  47. * @var ServerRequest
  48. */
  49. public $request;
  50. /**
  51. * Backup of $_SERVER
  52. *
  53. * @var array
  54. */
  55. protected $server = [];
  56. /**
  57. * setUp method
  58. *
  59. * @return void
  60. */
  61. public function setUp(): void
  62. {
  63. parent::setUp();
  64. $this->server = $_SERVER;
  65. static::setAppNamespace();
  66. $this->_init();
  67. }
  68. /**
  69. * init method
  70. *
  71. * @return void
  72. */
  73. protected function _init(): void
  74. {
  75. $request = new ServerRequest(['url' => 'controller_posts/index']);
  76. $response = $this->getMockBuilder('Cake\Http\Response')
  77. ->setMethods(['_sendHeader', 'stop'])
  78. ->getMock();
  79. $this->Controller = new RequestHandlerTestController($request, $response);
  80. $this->RequestHandler = $this->Controller->components()->load('RequestHandler');
  81. $this->request = $request;
  82. Router::scope('/', function ($routes): void {
  83. $routes->setExtensions('json');
  84. $routes->fallbacks('InflectedRoute');
  85. });
  86. }
  87. /**
  88. * tearDown method
  89. *
  90. * @return void
  91. */
  92. public function tearDown(): void
  93. {
  94. parent::tearDown();
  95. Router::reload();
  96. $_SERVER = $this->server;
  97. unset($this->RequestHandler, $this->Controller);
  98. }
  99. /**
  100. * Test that the constructor sets the config.
  101. *
  102. * @return void
  103. */
  104. public function testConstructorConfig(): void
  105. {
  106. $config = [
  107. 'viewClassMap' => ['json' => 'MyPlugin.MyJson']
  108. ];
  109. $controller = $this->getMockBuilder('Cake\Controller\Controller')
  110. ->setMethods(['redirect'])
  111. ->getMock();
  112. $collection = new ComponentRegistry($controller);
  113. $requestHandler = new RequestHandlerComponent($collection, $config);
  114. $this->assertEquals(['json' => 'MyPlugin.MyJson'], $requestHandler->getConfig('viewClassMap'));
  115. }
  116. /**
  117. * testInitializeCallback method
  118. *
  119. * @return void
  120. */
  121. public function testInitializeCallback(): void
  122. {
  123. $this->assertNull($this->RequestHandler->ext);
  124. $this->Controller->setRequest($this->Controller->getRequest()->withParam('_ext', 'rss'));
  125. $this->RequestHandler->startup(new Event('Controller.startup', $this->Controller));
  126. $this->assertEquals('rss', $this->RequestHandler->ext);
  127. }
  128. /**
  129. * test that a mapped Accept-type header will set $this->ext correctly.
  130. *
  131. * @return void
  132. */
  133. public function testInitializeContentTypeSettingExt(): void
  134. {
  135. Router::reload();
  136. $this->Controller->setRequest($this->request->withHeader('Accept', 'application/json'));
  137. $this->RequestHandler->ext = null;
  138. $this->RequestHandler->startup(new Event('Controller.startup', $this->Controller));
  139. $this->assertEquals('json', $this->RequestHandler->ext);
  140. }
  141. /**
  142. * Test that RequestHandler sets $this->ext when jQuery sends its wonky-ish headers.
  143. *
  144. * @return void
  145. */
  146. public function testInitializeContentTypeWithjQueryAccept(): void
  147. {
  148. Router::reload();
  149. $this->Controller->setRequest($this->request
  150. ->withHeader('Accept', 'application/json, application/javascript, */*; q=0.01')
  151. ->withHeader('X-Requested-With', 'XMLHttpRequest'));
  152. $this->RequestHandler->ext = null;
  153. Router::extensions('json', false);
  154. $this->RequestHandler->startup(new Event('Controller.startup', $this->Controller));
  155. $this->assertEquals('json', $this->RequestHandler->ext);
  156. }
  157. /**
  158. * Test that RequestHandler does not set extension to csv for text/plain mimetype
  159. *
  160. * @return void
  161. */
  162. public function testInitializeContentTypeWithjQueryTextPlainAccept(): void
  163. {
  164. Router::reload();
  165. $this->Controller->setRequest($this->request->withHeader('Accept', 'text/plain, */*; q=0.01'));
  166. $this->RequestHandler->startup(new Event('Controller.startup', $this->Controller));
  167. $this->assertNull($this->RequestHandler->ext);
  168. }
  169. /**
  170. * Test that RequestHandler sets $this->ext when jQuery sends its wonky-ish headers
  171. * and the application is configured to handle multiple extensions
  172. *
  173. * @return void
  174. */
  175. public function testInitializeContentTypeWithjQueryAcceptAndMultiplesExtensions(): void
  176. {
  177. Router::reload();
  178. $this->Controller->setRequest($this->request->withHeader('Accept', 'application/json, application/javascript, */*; q=0.01'));
  179. $this->RequestHandler->ext = null;
  180. Router::extensions(['rss', 'json'], false);
  181. $this->RequestHandler->startup(new Event('Controller.startup', $this->Controller));
  182. $this->assertEquals('json', $this->RequestHandler->ext);
  183. }
  184. /**
  185. * Test that RequestHandler does not set $this->ext when multiple accepts are sent.
  186. *
  187. * @return void
  188. */
  189. public function testInitializeNoContentTypeWithSingleAccept(): void
  190. {
  191. Router::reload();
  192. $_SERVER['HTTP_ACCEPT'] = 'application/json, text/html, */*; q=0.01';
  193. $this->assertNull($this->RequestHandler->ext);
  194. $this->RequestHandler->startup(new Event('Controller.startup', $this->Controller));
  195. $this->assertNull($this->RequestHandler->ext);
  196. }
  197. /**
  198. * Test that ext is set to the first listed extension with multiple accepted
  199. * content types.
  200. * Having multiple types accepted with same weight, means the client lets the
  201. * server choose the returned content type.
  202. *
  203. * @return void
  204. */
  205. public function testInitializeNoContentTypeWithMultipleAcceptedTypes(): void
  206. {
  207. $this->Controller->setRequest($this->request->withHeader(
  208. 'Accept',
  209. 'application/json, application/javascript, application/xml, */*; q=0.01'
  210. ));
  211. $this->RequestHandler->ext = null;
  212. Router::extensions(['xml', 'json'], false);
  213. $this->RequestHandler->startup(new Event('Controller.startup', $this->Controller));
  214. $this->assertEquals('xml', $this->RequestHandler->ext);
  215. $this->RequestHandler->ext = null;
  216. Router::extensions(['json', 'xml'], false);
  217. $this->RequestHandler->startup(new Event('Controller.startup', $this->Controller));
  218. $this->assertEquals('json', $this->RequestHandler->ext);
  219. }
  220. /**
  221. * Test that ext is set to type with highest weight
  222. *
  223. * @return void
  224. */
  225. public function testInitializeContentTypeWithMultipleAcceptedTypes(): void
  226. {
  227. Router::reload();
  228. $this->Controller->setRequest($this->request->withHeader(
  229. 'Accept',
  230. 'text/csv;q=1.0, application/json;q=0.8, application/xml;q=0.7'
  231. ));
  232. $this->RequestHandler->ext = null;
  233. $this->RequestHandler->startup(new Event('Controller.startup', $this->Controller));
  234. $this->assertEquals('json', $this->RequestHandler->ext);
  235. }
  236. /**
  237. * Test that ext is not set with confusing android accepts headers.
  238. *
  239. * @return void
  240. */
  241. public function testInitializeAmbiguousAndroidAccepts(): void
  242. {
  243. Router::reload();
  244. $this->request = $this->request->withEnv(
  245. 'HTTP_ACCEPT',
  246. 'application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5'
  247. );
  248. $this->RequestHandler->ext = null;
  249. $this->RequestHandler->startup(new Event('Controller.startup', $this->Controller));
  250. $this->assertNull($this->RequestHandler->ext);
  251. }
  252. /**
  253. * Test that the headers sent by firefox are not treated as XML requests.
  254. *
  255. * @return void
  256. */
  257. public function testInititalizeFirefoxHeaderNotXml(): void
  258. {
  259. $_SERVER['HTTP_ACCEPT'] = 'text/html,application/xhtml+xml,application/xml;image/png,image/jpeg,image/*;q=0.9,*/*;q=0.8';
  260. Router::extensions(['xml', 'json'], false);
  261. $this->RequestHandler->startup(new Event('Controller.startup', $this->Controller));
  262. $this->assertNull($this->RequestHandler->ext);
  263. }
  264. /**
  265. * Test that a type mismatch doesn't incorrectly set the ext
  266. *
  267. * @return void
  268. */
  269. public function testInitializeContentTypeAndExtensionMismatch(): void
  270. {
  271. $this->assertNull($this->RequestHandler->ext);
  272. $extensions = Router::extensions();
  273. Router::extensions('xml', false);
  274. $this->Controller->setRequest($this->getMockBuilder('Cake\Http\ServerRequest')
  275. ->setMethods(['accepts'])
  276. ->getMock());
  277. $this->Controller->getRequest()->expects($this->any())
  278. ->method('accepts')
  279. ->will($this->returnValue(['application/json']));
  280. $this->RequestHandler->startup(new Event('Controller.startup', $this->Controller));
  281. $this->assertNull($this->RequestHandler->ext);
  282. Router::extensions($extensions, false);
  283. }
  284. /**
  285. * testViewClassMap
  286. *
  287. * @return void
  288. */
  289. public function testViewClassMap(): void
  290. {
  291. $this->RequestHandler->setConfig(['viewClassMap' => ['json' => 'CustomJson']]);
  292. $result = $this->RequestHandler->getConfig('viewClassMap');
  293. $expected = [
  294. 'json' => 'CustomJson',
  295. 'xml' => 'Xml',
  296. 'ajax' => 'Ajax'
  297. ];
  298. $this->assertEquals($expected, $result);
  299. $this->RequestHandler->setConfig(['viewClassMap' => ['xls' => 'Excel.Excel']]);
  300. $result = $this->RequestHandler->getConfig('viewClassMap');
  301. $expected = [
  302. 'json' => 'CustomJson',
  303. 'xml' => 'Xml',
  304. 'ajax' => 'Ajax',
  305. 'xls' => 'Excel.Excel'
  306. ];
  307. $this->assertEquals($expected, $result);
  308. $this->RequestHandler->renderAs($this->Controller, 'json');
  309. $this->assertEquals('TestApp\View\CustomJsonView', $this->Controller->viewBuilder()->getClassName());
  310. }
  311. /**
  312. * Verify that isAjax is set on the request params for ajax requests
  313. *
  314. * @return void
  315. * @triggers Controller.startup $this->Controller
  316. */
  317. public function testIsAjaxParams(): void
  318. {
  319. $this->Controller->setRequest($this->request->withHeader('X-Requested-With', 'XMLHttpRequest'));
  320. $event = new Event('Controller.startup', $this->Controller);
  321. $this->RequestHandler->initialize([]);
  322. $this->Controller->beforeFilter($event);
  323. $this->RequestHandler->startup($event);
  324. $this->assertTrue($this->Controller->getRequest()->getParam('isAjax'));
  325. }
  326. /**
  327. * testAutoAjaxLayout method
  328. *
  329. * @return void
  330. * @triggers Controller.startup $this->Controller
  331. */
  332. public function testAutoAjaxLayout(): void
  333. {
  334. $event = new Event('Controller.startup', $this->Controller);
  335. $this->Controller->setRequest($this->request->withHeader('X-Requested-With', 'XMLHttpRequest'));
  336. $this->RequestHandler->initialize([]);
  337. $this->RequestHandler->startup($event);
  338. $event = new Event('Controller.beforeRender', $this->Controller);
  339. $this->RequestHandler->beforeRender($event);
  340. $view = $this->Controller->createView();
  341. $this->assertInstanceOf(AjaxView::class, $view);
  342. $this->assertEquals('ajax', $view->getLayout());
  343. $this->_init();
  344. $this->Controller->setRequest($this->Controller->getRequest()->withParam('_ext', 'js'));
  345. $this->RequestHandler->initialize([]);
  346. $this->RequestHandler->startup($event);
  347. $this->assertNotEquals(AjaxView::class, $this->Controller->viewBuilder()->getClassName());
  348. }
  349. /**
  350. * test custom JsonView class is loaded and correct.
  351. *
  352. * @return void
  353. * @triggers Controller.startup $this->Controller
  354. */
  355. public function testJsonViewLoaded(): void
  356. {
  357. Router::extensions(['json', 'xml', 'ajax'], false);
  358. $this->Controller->setRequest($this->Controller->getRequest()->withParam('_ext', 'json'));
  359. $event = new Event('Controller.startup', $this->Controller);
  360. $this->RequestHandler->initialize([]);
  361. $this->RequestHandler->startup($event);
  362. $event = new Event('Controller.beforeRender', $this->Controller);
  363. $this->RequestHandler->beforeRender($event);
  364. $view = $this->Controller->createView();
  365. $this->assertInstanceOf(JsonView::class, $view);
  366. $this->assertEquals('json', $view->getLayoutPath());
  367. $this->assertEquals('json', $view->getSubDir());
  368. }
  369. /**
  370. * test custom XmlView class is loaded and correct.
  371. *
  372. * @return void
  373. * @triggers Controller.startup $this->Controller
  374. */
  375. public function testXmlViewLoaded(): void
  376. {
  377. Router::extensions(['json', 'xml', 'ajax'], false);
  378. $this->Controller->setRequest($this->Controller->getRequest()->withParam('_ext', 'xml'));
  379. $event = new Event('Controller.startup', $this->Controller);
  380. $this->RequestHandler->initialize([]);
  381. $this->RequestHandler->startup($event);
  382. $event = new Event('Controller.beforeRender', $this->Controller);
  383. $this->RequestHandler->beforeRender($event);
  384. $view = $this->Controller->createView();
  385. $this->assertInstanceOf(XmlView::class, $view);
  386. $this->assertEquals('xml', $view->getLayoutPath());
  387. $this->assertEquals('xml', $view->getSubDir());
  388. }
  389. /**
  390. * test custom AjaxView class is loaded and correct.
  391. *
  392. * @return void
  393. * @triggers Controller.startup $this->Controller
  394. */
  395. public function testAjaxViewLoaded(): void
  396. {
  397. Router::extensions(['json', 'xml', 'ajax'], false);
  398. $this->Controller->setRequest($this->Controller->getRequest()->withParam('_ext', 'ajax'));
  399. $event = new Event('Controller.startup', $this->Controller);
  400. $this->RequestHandler->initialize([]);
  401. $this->RequestHandler->startup($event);
  402. $event = new Event('Controller.beforeRender', $this->Controller);
  403. $this->RequestHandler->beforeRender($event);
  404. $view = $this->Controller->createView();
  405. $this->assertInstanceOf(AjaxView::class, $view);
  406. $this->assertEquals('ajax', $view->getLayout());
  407. }
  408. /**
  409. * test configured extension but no view class set.
  410. *
  411. * @return void
  412. * @triggers Controller.beforeRender $this->Controller
  413. */
  414. public function testNoViewClassExtension(): void
  415. {
  416. Router::extensions(['json', 'xml', 'ajax', 'csv'], false);
  417. $this->Controller->setRequest($this->Controller->getRequest()->withParam('_ext', 'csv'));
  418. $event = new Event('Controller.startup', $this->Controller);
  419. $this->RequestHandler->initialize([]);
  420. $this->RequestHandler->startup($event);
  421. $this->Controller->getEventManager()->on('Controller.beforeRender', function () {
  422. return $this->Controller->response;
  423. });
  424. $this->Controller->render();
  425. $this->assertEquals('RequestHandlerTest' . DS . 'csv', $this->Controller->viewBuilder()->getTemplatePath());
  426. $this->assertEquals('csv', $this->Controller->viewBuilder()->getLayoutPath());
  427. }
  428. /**
  429. * testStartupCallback method
  430. *
  431. * @return void
  432. * @triggers Controller.beforeRender $this->Controller
  433. */
  434. public function testStartupCallback(): void
  435. {
  436. $event = new Event('Controller.beforeRender', $this->Controller);
  437. $_SERVER['REQUEST_METHOD'] = 'PUT';
  438. $_SERVER['CONTENT_TYPE'] = 'application/xml';
  439. $this->Controller->setRequest(new ServerRequest());
  440. $this->RequestHandler->beforeRender($event);
  441. $this->assertInternalType('array', $this->Controller->getRequest()->getData());
  442. $this->assertNotInternalType('object', $this->Controller->getRequest()->getData());
  443. }
  444. /**
  445. * testStartupCallback with charset.
  446. *
  447. * @return void
  448. * @triggers Controller.startup $this->Controller
  449. */
  450. public function testStartupCallbackCharset(): void
  451. {
  452. $event = new Event('Controller.startup', $this->Controller);
  453. $_SERVER['REQUEST_METHOD'] = 'PUT';
  454. $_SERVER['CONTENT_TYPE'] = 'application/xml; charset=UTF-8';
  455. $this->Controller->setRequest(new ServerRequest());
  456. $this->RequestHandler->startup($event);
  457. $this->assertInternalType('array', $this->Controller->getRequest()->getData());
  458. $this->assertNotInternalType('object', $this->Controller->getRequest()->getData());
  459. }
  460. /**
  461. * Test that processing data results in an array.
  462. *
  463. * @return void
  464. * @triggers Controller.startup $this->Controller
  465. */
  466. public function testStartupProcessDataInvalid(): void
  467. {
  468. $this->Controller->setRequest(new ServerRequest([
  469. 'environment' => [
  470. 'REQUEST_METHOD' => 'POST',
  471. 'CONTENT_TYPE' => 'application/json'
  472. ]
  473. ]));
  474. $event = new Event('Controller.startup', $this->Controller);
  475. $this->RequestHandler->startup($event);
  476. $this->assertEquals([], $this->Controller->getRequest()->getData());
  477. $stream = new Stream('php://memory', 'w');
  478. $stream->write('"invalid"');
  479. $this->Controller->setRequest($this->Controller->getRequest()->withBody($stream));
  480. $this->RequestHandler->startup($event);
  481. $this->assertEquals(['invalid'], $this->Controller->getRequest()->getData());
  482. }
  483. /**
  484. * Test that processing data results in an array.
  485. *
  486. * @return void
  487. * @triggers Controller.startup $this->Controller
  488. */
  489. public function testStartupProcessData(): void
  490. {
  491. $this->Controller->setRequest(new ServerRequest([
  492. 'environment' => [
  493. 'REQUEST_METHOD' => 'POST',
  494. 'CONTENT_TYPE' => 'application/json'
  495. ]
  496. ]));
  497. $stream = new Stream('php://memory', 'w');
  498. $stream->write('{"valid":true}');
  499. $this->Controller->setRequest($this->Controller->getRequest()->withBody($stream));
  500. $event = new Event('Controller.startup', $this->Controller);
  501. $this->RequestHandler->startup($event);
  502. $this->assertEquals(['valid' => true], $this->Controller->getRequest()->getData());
  503. }
  504. /**
  505. * Test that file handles are ignored as XML data.
  506. *
  507. * @return void
  508. * @triggers Controller.startup $this->Controller
  509. */
  510. public function testStartupIgnoreFileAsXml(): void
  511. {
  512. $this->Controller->setRequest(new ServerRequest([
  513. 'input' => '/dev/random',
  514. 'environment' => [
  515. 'REQUEST_METHOD' => 'POST',
  516. 'CONTENT_TYPE' => 'application/xml'
  517. ]
  518. ]));
  519. $event = new Event('Controller.startup', $this->Controller);
  520. $this->RequestHandler->startup($event);
  521. $this->assertEquals([], $this->Controller->getRequest()->getData());
  522. }
  523. /**
  524. * Test that input xml is parsed
  525. *
  526. * @return void
  527. */
  528. public function testStartupConvertXmlDataWrapper(): void
  529. {
  530. $xml = <<<XML
  531. <?xml version="1.0" encoding="utf-8"?>
  532. <data>
  533. <article id="1" title="first"></article>
  534. </data>
  535. XML;
  536. $this->Controller->setRequest((new ServerRequest(['input' => $xml]))
  537. ->withEnv('REQUEST_METHOD', 'POST')
  538. ->withEnv('CONTENT_TYPE', 'application/xml'));
  539. $event = new Event('Controller.startup', $this->Controller);
  540. $this->RequestHandler->startup($event);
  541. $expected = [
  542. 'data' => [
  543. 'article' => [
  544. '@id' => 1,
  545. '@title' => 'first'
  546. ]
  547. ]
  548. ];
  549. $this->assertEquals($expected, $this->Controller->getRequest()->getData());
  550. }
  551. /**
  552. * Test that input xml is parsed
  553. *
  554. * @return void
  555. */
  556. public function testStartupConvertXmlElements(): void
  557. {
  558. $xml = <<<XML
  559. <?xml version="1.0" encoding="utf-8"?>
  560. <article>
  561. <id>1</id>
  562. <title><![CDATA[first]]></title>
  563. </article>
  564. XML;
  565. $this->Controller->setRequest((new ServerRequest(['input' => $xml]))
  566. ->withEnv('REQUEST_METHOD', 'POST')
  567. ->withEnv('CONTENT_TYPE', 'application/xml'));
  568. $event = new Event('Controller.startup', $this->Controller);
  569. $this->RequestHandler->startup($event);
  570. $expected = [
  571. 'article' => [
  572. 'id' => 1,
  573. 'title' => 'first'
  574. ]
  575. ];
  576. $this->assertEquals($expected, $this->Controller->getRequest()->getData());
  577. }
  578. /**
  579. * Test that input xml is parsed
  580. *
  581. * @return void
  582. */
  583. public function testStartupConvertXmlIgnoreEntities(): void
  584. {
  585. $xml = <<<XML
  586. <?xml version="1.0" encoding="UTF-8"?>
  587. <!DOCTYPE item [
  588. <!ENTITY item "item">
  589. <!ENTITY item1 "&item;&item;&item;&item;&item;&item;">
  590. <!ENTITY item2 "&item1;&item1;&item1;&item1;&item1;&item1;&item1;&item1;&item1;">
  591. <!ENTITY item3 "&item2;&item2;&item2;&item2;&item2;&item2;&item2;&item2;&item2;">
  592. <!ENTITY item4 "&item3;&item3;&item3;&item3;&item3;&item3;&item3;&item3;&item3;">
  593. <!ENTITY item5 "&item4;&item4;&item4;&item4;&item4;&item4;&item4;&item4;&item4;">
  594. <!ENTITY item6 "&item5;&item5;&item5;&item5;&item5;&item5;&item5;&item5;&item5;">
  595. <!ENTITY item7 "&item6;&item6;&item6;&item6;&item6;&item6;&item6;&item6;&item6;">
  596. <!ENTITY item8 "&item7;&item7;&item7;&item7;&item7;&item7;&item7;&item7;&item7;">
  597. ]>
  598. <item>
  599. <description>&item8;</description>
  600. </item>
  601. XML;
  602. $this->Controller->setRequest((new ServerRequest(['input' => $xml]))
  603. ->withEnv('REQUEST_METHOD', 'POST')
  604. ->withEnv('CONTENT_TYPE', 'application/xml'));
  605. $event = new Event('Controller.startup', $this->Controller);
  606. $this->RequestHandler->startup($event);
  607. $this->assertEquals([], $this->Controller->getRequest()->getData());
  608. }
  609. /**
  610. * Test that data isn't processed when parsed data already exists.
  611. *
  612. * @return void
  613. * @triggers Controller.startup $this->Controller
  614. */
  615. public function testStartupSkipDataProcess(): void
  616. {
  617. $this->Controller->setRequest(new ServerRequest([
  618. 'environment' => [
  619. 'REQUEST_METHOD' => 'POST',
  620. 'CONTENT_TYPE' => 'application/json'
  621. ]
  622. ]));
  623. $event = new Event('Controller.startup', $this->Controller);
  624. $this->RequestHandler->startup($event);
  625. $this->assertEquals([], $this->Controller->getRequest()->getData());
  626. $stream = new Stream('php://memory', 'w');
  627. $stream->write('{"new": "data"}');
  628. $this->Controller->setRequest($this->Controller->getRequest()
  629. ->withBody($stream)
  630. ->withParsedBody(['old' => 'news']));
  631. $this->RequestHandler->startup($event);
  632. $this->assertEquals(['old' => 'news'], $this->Controller->getRequest()->getData());
  633. }
  634. /**
  635. * testRenderAs method
  636. *
  637. * @return void
  638. */
  639. public function testRenderAs(): void
  640. {
  641. $this->RequestHandler->renderAs($this->Controller, 'rss');
  642. $this->Controller->viewBuilder()->setTemplatePath('request_handler_test\\rss');
  643. $this->RequestHandler->renderAs($this->Controller, 'js');
  644. $this->assertEquals('request_handler_test' . DS . 'js', $this->Controller->viewBuilder()->getTemplatePath());
  645. }
  646. /**
  647. * test that attachment headers work with renderAs
  648. *
  649. * @return void
  650. */
  651. public function testRenderAsWithAttachment(): void
  652. {
  653. $this->Controller->setRequest($this->request->withHeader('Accept', 'application/xml;q=1.0'));
  654. $this->RequestHandler->renderAs($this->Controller, 'xml', ['attachment' => 'myfile.xml']);
  655. $this->assertEquals(XmlView::class, $this->Controller->viewBuilder()->getClassName());
  656. $this->assertEquals('application/xml', $this->Controller->getResponse()->getType());
  657. $this->assertEquals('UTF-8', $this->Controller->getResponse()->getCharset());
  658. $this->assertContains('myfile.xml', $this->Controller->getResponse()->getHeaderLine('Content-Disposition'));
  659. }
  660. /**
  661. * test that respondAs works as expected.
  662. *
  663. * @return void
  664. */
  665. public function testRespondAs(): void
  666. {
  667. $result = $this->RequestHandler->respondAs('json');
  668. $this->assertTrue($result);
  669. $this->assertEquals('application/json', $this->Controller->getResponse()->getType());
  670. $result = $this->RequestHandler->respondAs('text/xml');
  671. $this->assertTrue($result);
  672. $this->assertEquals('text/xml', $this->Controller->getResponse()->getType());
  673. }
  674. /**
  675. * test that attachment headers work with respondAs
  676. *
  677. * @return void
  678. */
  679. public function testRespondAsWithAttachment(): void
  680. {
  681. $result = $this->RequestHandler->respondAs('xml', ['attachment' => 'myfile.xml']);
  682. $this->assertTrue($result);
  683. $response = $this->Controller->getResponse();
  684. $this->assertContains('myfile.xml', $response->getHeaderLine('Content-Disposition'));
  685. $this->assertContains('application/xml', $response->getType());
  686. }
  687. /**
  688. * test that calling renderAs() more than once continues to work.
  689. *
  690. * @link #6466
  691. * @return void
  692. */
  693. public function testRenderAsCalledTwice(): void
  694. {
  695. $this->Controller->getEventManager()->on('Controller.beforeRender', function (\Cake\Event\EventInterface $e) {
  696. return $e->getSubject()->response;
  697. });
  698. $this->Controller->render();
  699. $this->RequestHandler->renderAs($this->Controller, 'print');
  700. $this->assertEquals('RequestHandlerTest' . DS . 'print', $this->Controller->viewBuilder()->getTemplatePath());
  701. $this->assertEquals('print', $this->Controller->viewBuilder()->getLayoutPath());
  702. $this->RequestHandler->renderAs($this->Controller, 'js');
  703. $this->assertEquals('RequestHandlerTest' . DS . 'js', $this->Controller->viewBuilder()->getTemplatePath());
  704. $this->assertEquals('js', $this->Controller->viewBuilder()->getLayoutPath());
  705. }
  706. /**
  707. * testRequestContentTypes method
  708. *
  709. * @return void
  710. */
  711. public function testRequestContentTypes(): void
  712. {
  713. $this->Controller->setRequest($this->request->withEnv('REQUEST_METHOD', 'GET'));
  714. $this->assertNull($this->RequestHandler->requestedWith());
  715. $this->Controller->setRequest($this->request->withEnv('REQUEST_METHOD', 'POST')
  716. ->withEnv('CONTENT_TYPE', 'application/json'));
  717. $this->assertEquals('json', $this->RequestHandler->requestedWith());
  718. $result = $this->RequestHandler->requestedWith(['json', 'xml']);
  719. $this->assertEquals('json', $result);
  720. $result = $this->RequestHandler->requestedWith(['rss', 'atom']);
  721. $this->assertFalse($result);
  722. $this->Controller->setRequest($this->request
  723. ->withEnv('REQUEST_METHOD', 'PATCH')
  724. ->withEnv('CONTENT_TYPE', 'application/json'));
  725. $this->assertEquals('json', $this->RequestHandler->requestedWith());
  726. $this->Controller->setRequest($this->request
  727. ->withEnv('REQUEST_METHOD', 'DELETE')
  728. ->withEnv('CONTENT_TYPE', 'application/json'));
  729. $this->assertEquals('json', $this->RequestHandler->requestedWith());
  730. $this->Controller->setRequest($this->request
  731. ->withEnv('REQUEST_METHOD', 'POST')
  732. ->withEnv('CONTENT_TYPE', 'application/json'));
  733. $result = $this->RequestHandler->requestedWith(['json', 'xml']);
  734. $this->assertEquals('json', $result);
  735. $result = $this->RequestHandler->requestedWith(['rss', 'atom']);
  736. $this->assertFalse($result);
  737. $this->Controller->setRequest($this->request->withHeader(
  738. 'Accept',
  739. 'text/xml,application/xml,application/xhtml+xml,text/html,text/plain,image/png,*/*'
  740. ));
  741. $this->assertTrue($this->RequestHandler->isXml());
  742. $this->assertFalse($this->RequestHandler->isAtom());
  743. $this->assertFalse($this->RequestHandler->isRSS());
  744. $this->Controller->setRequest($this->request->withHeader(
  745. 'Accept',
  746. 'application/atom+xml,text/xml,application/xml,application/xhtml+xml,text/html,text/plain,image/png,*/*'
  747. ));
  748. $this->assertTrue($this->RequestHandler->isAtom());
  749. $this->assertFalse($this->RequestHandler->isRSS());
  750. $this->Controller->setRequest($this->request->withHeader(
  751. 'Accept',
  752. 'application/rss+xml,text/xml,application/xml,application/xhtml+xml,text/html,text/plain,image/png,*/*'
  753. ));
  754. $this->assertFalse($this->RequestHandler->isAtom());
  755. $this->assertTrue($this->RequestHandler->isRSS());
  756. $this->assertFalse($this->RequestHandler->isWap());
  757. $this->Controller->setRequest($this->request->withHeader(
  758. 'Accept',
  759. 'text/vnd.wap.wml,text/html,text/plain,image/png,*/*'
  760. ));
  761. $this->assertTrue($this->RequestHandler->isWap());
  762. }
  763. /**
  764. * testResponseContentType method
  765. *
  766. * @return void
  767. */
  768. public function testResponseContentType(): void
  769. {
  770. $this->assertEquals('html', $this->RequestHandler->responseType());
  771. $this->assertTrue($this->RequestHandler->respondAs('atom'));
  772. $this->assertEquals('atom', $this->RequestHandler->responseType());
  773. }
  774. /**
  775. * testMobileDeviceDetection method
  776. *
  777. * @return void
  778. */
  779. public function testMobileDeviceDetection(): void
  780. {
  781. $request = $this->getMockBuilder('Cake\Http\ServerRequest')
  782. ->setMethods(['is'])
  783. ->getMock();
  784. $request->expects($this->once())->method('is')
  785. ->with('mobile')
  786. ->will($this->returnValue(true));
  787. $this->Controller->setRequest($request);
  788. $this->assertTrue($this->RequestHandler->isMobile());
  789. }
  790. /**
  791. * test that map alias converts aliases to content types.
  792. *
  793. * @return void
  794. */
  795. public function testMapAlias(): void
  796. {
  797. $result = $this->RequestHandler->mapAlias('xml');
  798. $this->assertEquals('application/xml', $result);
  799. $result = $this->RequestHandler->mapAlias('text/html');
  800. $this->assertNull($result);
  801. $result = $this->RequestHandler->mapAlias('wap');
  802. $this->assertEquals('text/vnd.wap.wml', $result);
  803. $result = $this->RequestHandler->mapAlias(['xml', 'js', 'json']);
  804. $expected = ['application/xml', 'application/javascript', 'application/json'];
  805. $this->assertEquals($expected, $result);
  806. }
  807. /**
  808. * test accepts() on the component
  809. *
  810. * @return void
  811. */
  812. public function testAccepts(): void
  813. {
  814. $this->Controller->setRequest($this->request->withHeader(
  815. 'Accept',
  816. 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5'
  817. ));
  818. $this->assertTrue($this->RequestHandler->accepts(['js', 'xml', 'html']));
  819. $this->assertFalse($this->RequestHandler->accepts(['gif', 'jpeg', 'foo']));
  820. $this->Controller->setRequest($this->request->withHeader('Accept', '*/*;q=0.5'));
  821. $this->assertFalse($this->RequestHandler->accepts('rss'));
  822. }
  823. /**
  824. * test accepts and prefers methods.
  825. *
  826. * @return void
  827. */
  828. public function testPrefers(): void
  829. {
  830. $this->Controller->setRequest($this->request->withHeader(
  831. 'Accept',
  832. 'text/xml,application/xml,application/xhtml+xml,text/html,text/plain,image/png,*/*'
  833. ));
  834. $this->assertNotEquals('rss', $this->RequestHandler->prefers());
  835. $this->RequestHandler->ext = 'rss';
  836. $this->assertEquals('rss', $this->RequestHandler->prefers());
  837. $this->assertFalse($this->RequestHandler->prefers('xml'));
  838. $this->assertEquals('xml', $this->RequestHandler->prefers(['js', 'xml', 'xhtml']));
  839. $this->assertFalse($this->RequestHandler->prefers(['red', 'blue']));
  840. $this->assertEquals('xhtml', $this->RequestHandler->prefers(['js', 'json', 'xhtml']));
  841. $this->assertTrue($this->RequestHandler->prefers(['rss']), 'Should return true if input matches ext.');
  842. $this->assertFalse($this->RequestHandler->prefers(['html']), 'No match with ext, return false.');
  843. $this->_init();
  844. $this->Controller->setRequest($this->request->withHeader(
  845. 'Accept',
  846. 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5'
  847. ));
  848. $this->assertEquals('xml', $this->RequestHandler->prefers());
  849. $this->Controller->setRequest($this->request->withHeader('Accept', '*/*;q=0.5'));
  850. $this->assertEquals('html', $this->RequestHandler->prefers());
  851. $this->assertFalse($this->RequestHandler->prefers('rss'));
  852. $this->Controller->setRequest($this->request->withEnv('HTTP_ACCEPT', null));
  853. $this->RequestHandler->ext = 'json';
  854. $this->assertFalse($this->RequestHandler->prefers('xml'));
  855. }
  856. /**
  857. * Test checkNotModified method
  858. *
  859. * @return void
  860. * @triggers Controller.beforeRender $this->Controller
  861. */
  862. public function testCheckNotModifiedByEtagStar(): void
  863. {
  864. $response = new Response();
  865. $response = $response->withEtag('something')
  866. ->withHeader('Content-Type', 'text/plain')
  867. ->withStringBody('keeper');
  868. $this->Controller->setResponse($response);
  869. $this->Controller->setRequest($this->request->withHeader('If-None-Match', '*'));
  870. $event = new Event('Controller.beforeRender', $this->Controller);
  871. $requestHandler = new RequestHandlerComponent($this->Controller->components());
  872. $this->assertFalse($requestHandler->beforeRender($event));
  873. $this->assertEquals(304, $this->Controller->getResponse()->getStatusCode());
  874. $this->assertEquals('', (string)$this->Controller->getResponse()->getBody());
  875. $this->assertFalse($this->Controller->getResponse()->hasHeader('Content-Type'), 'header should not be removed.');
  876. }
  877. /**
  878. * Test checkNotModified method
  879. *
  880. * @return void
  881. * @triggers Controller.beforeRender
  882. */
  883. public function testCheckNotModifiedByEtagExact(): void
  884. {
  885. $response = new Response();
  886. $response = $response->withEtag('something', true)
  887. ->withHeader('Content-Type', 'text/plain')
  888. ->withStringBody('keeper');
  889. $this->Controller->setResponse($response);
  890. $this->Controller->setRequest($this->request->withHeader('If-None-Match', 'W/"something", "other"'));
  891. $event = new Event('Controller.beforeRender', $this->Controller);
  892. $requestHandler = new RequestHandlerComponent($this->Controller->components());
  893. $this->assertFalse($requestHandler->beforeRender($event));
  894. $this->assertEquals(304, $this->Controller->getResponse()->getStatusCode());
  895. $this->assertEquals('', (string)$this->Controller->getResponse()->getBody());
  896. $this->assertFalse($this->Controller->getResponse()->hasHeader('Content-Type'));
  897. }
  898. /**
  899. * Test checkNotModified method
  900. *
  901. * @return void
  902. * @triggers Controller.beforeRender $this->Controller
  903. */
  904. public function testCheckNotModifiedByEtagAndTime(): void
  905. {
  906. $this->Controller->setRequest($this->request
  907. ->withHeader('If-None-Match', 'W/"something", "other"')
  908. ->withHeader('If-Modified-Since', '2012-01-01 00:00:00'));
  909. $response = new Response();
  910. $response = $response->withEtag('something', true)
  911. ->withHeader('Content-type', 'text/plain')
  912. ->withStringBody('should be removed')
  913. ->withModified('2012-01-01 00:00:00');
  914. $this->Controller->setResponse($response);
  915. $event = new Event('Controller.beforeRender', $this->Controller);
  916. $requestHandler = new RequestHandlerComponent($this->Controller->components());
  917. $this->assertFalse($requestHandler->beforeRender($event));
  918. $this->assertEquals(304, $this->Controller->getResponse()->getStatusCode());
  919. $this->assertEquals('', (string)$this->Controller->getResponse()->getBody());
  920. $this->assertFalse($this->Controller->getResponse()->hasHeader('Content-type'));
  921. }
  922. /**
  923. * Test checkNotModified method
  924. *
  925. * @return void
  926. * @triggers Controller.beforeRender $this->Controller
  927. */
  928. public function testCheckNotModifiedNoInfo(): void
  929. {
  930. $response = $this->getMockBuilder('Cake\Http\Response')
  931. ->setMethods(['notModified', 'stop'])
  932. ->getMock();
  933. $response->expects($this->never())->method('notModified');
  934. $this->Controller->setResponse($response);
  935. $event = new Event('Controller.beforeRender', $this->Controller);
  936. $requestHandler = new RequestHandlerComponent($this->Controller->components());
  937. $this->assertNull($requestHandler->beforeRender($event));
  938. }
  939. /**
  940. * Test default options in construction
  941. *
  942. * @return void
  943. */
  944. public function testConstructDefaultOptions(): void
  945. {
  946. $requestHandler = new RequestHandlerComponent($this->Controller->components());
  947. $viewClass = $requestHandler->getConfig('viewClassMap');
  948. $expected = [
  949. 'json' => 'Json',
  950. 'xml' => 'Xml',
  951. 'ajax' => 'Ajax',
  952. ];
  953. $this->assertEquals($expected, $viewClass);
  954. $inputs = $requestHandler->getConfig('inputTypeMap');
  955. $this->assertArrayHasKey('json', $inputs);
  956. $this->assertArrayHasKey('xml', $inputs);
  957. }
  958. /**
  959. * Test options in constructor replace defaults
  960. *
  961. * @return void
  962. */
  963. public function testConstructReplaceOptions(): void
  964. {
  965. $requestHandler = new RequestHandlerComponent(
  966. $this->Controller->components(),
  967. [
  968. 'viewClassMap' => ['json' => 'Json'],
  969. 'inputTypeMap' => ['json' => ['json_decode', true]]
  970. ]
  971. );
  972. $viewClass = $requestHandler->getConfig('viewClassMap');
  973. $expected = [
  974. 'json' => 'Json',
  975. ];
  976. $this->assertEquals($expected, $viewClass);
  977. $inputs = $requestHandler->getConfig('inputTypeMap');
  978. $this->assertArrayHasKey('json', $inputs);
  979. $this->assertCount(1, $inputs);
  980. }
  981. /**
  982. * test beforeRender() doesn't override response type set in controller action
  983. *
  984. * @return void
  985. */
  986. public function testBeforeRender(): void
  987. {
  988. $this->Controller->set_response_type();
  989. $event = new Event('Controller.beforeRender', $this->Controller);
  990. $this->RequestHandler->beforeRender($event);
  991. $this->assertEquals('text/plain', $this->Controller->getResponse()->getType());
  992. }
  993. /**
  994. * tests beforeRender automatically uses renderAs when a supported extension is found
  995. *
  996. * @return void
  997. */
  998. public function testBeforeRenderAutoRenderAs(): void
  999. {
  1000. $this->Controller->setRequest($this->request->withParam('_ext', 'csv'));
  1001. $this->RequestHandler->startup(new Event('Controller.startup', $this->Controller));
  1002. $event = new Event('Controller.beforeRender', $this->Controller);
  1003. $this->RequestHandler->beforeRender($event);
  1004. $this->assertEquals('text/csv', $this->Controller->getResponse()->getType());
  1005. }
  1006. }