IntegrationTestTrait.php 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  11. * @since 3.7.0
  12. * @license https://opensource.org/licenses/mit-license.php MIT License
  13. */
  14. namespace Cake\TestSuite;
  15. use Cake\Core\Configure;
  16. use Cake\Database\Exception as DatabaseException;
  17. use Cake\Http\ServerRequest;
  18. use Cake\Http\Session;
  19. use Cake\Routing\Router;
  20. use Cake\TestSuite\Constraint\Response\BodyContains;
  21. use Cake\TestSuite\Constraint\Response\BodyEmpty;
  22. use Cake\TestSuite\Constraint\Response\BodyEquals;
  23. use Cake\TestSuite\Constraint\Response\BodyNotContains;
  24. use Cake\TestSuite\Constraint\Response\BodyNotEmpty;
  25. use Cake\TestSuite\Constraint\Response\BodyNotEquals;
  26. use Cake\TestSuite\Constraint\Response\BodyNotRegExp;
  27. use Cake\TestSuite\Constraint\Response\BodyRegExp;
  28. use Cake\TestSuite\Constraint\Response\ContentType;
  29. use Cake\TestSuite\Constraint\Response\CookieEncryptedEquals;
  30. use Cake\TestSuite\Constraint\Response\CookieEquals;
  31. use Cake\TestSuite\Constraint\Response\CookieNotSet;
  32. use Cake\TestSuite\Constraint\Response\CookieSet;
  33. use Cake\TestSuite\Constraint\Response\FileSent;
  34. use Cake\TestSuite\Constraint\Response\FileSentAs;
  35. use Cake\TestSuite\Constraint\Response\HeaderContains;
  36. use Cake\TestSuite\Constraint\Response\HeaderEquals;
  37. use Cake\TestSuite\Constraint\Response\HeaderNotContains;
  38. use Cake\TestSuite\Constraint\Response\HeaderNotSet;
  39. use Cake\TestSuite\Constraint\Response\HeaderSet;
  40. use Cake\TestSuite\Constraint\Response\StatusCode;
  41. use Cake\TestSuite\Constraint\Response\StatusError;
  42. use Cake\TestSuite\Constraint\Response\StatusFailure;
  43. use Cake\TestSuite\Constraint\Response\StatusOk;
  44. use Cake\TestSuite\Constraint\Response\StatusSuccess;
  45. use Cake\TestSuite\Constraint\Session\FlashParamEquals;
  46. use Cake\TestSuite\Constraint\Session\SessionEquals;
  47. use Cake\TestSuite\Constraint\View\LayoutFileEquals;
  48. use Cake\TestSuite\Constraint\View\TemplateFileEquals;
  49. use Cake\TestSuite\Stub\TestExceptionRenderer;
  50. use Cake\Utility\CookieCryptTrait;
  51. use Cake\Utility\Hash;
  52. use Cake\Utility\Security;
  53. use Cake\Utility\Text;
  54. use Cake\View\Helper\SecureFieldTokenTrait;
  55. use Zend\Diactoros\Uri;
  56. use Exception;
  57. use LogicException;
  58. use PHPUnit\Exception as PhpunitException;
  59. /**
  60. * A trait intended to make integration tests of your controllers easier.
  61. *
  62. * This test class provides a number of helper methods and features
  63. * that make dispatching requests and checking their responses simpler.
  64. * It favours full integration tests over mock objects as you can test
  65. * more of your code easily and avoid some of the maintenance pitfalls
  66. * that mock objects create.
  67. */
  68. trait IntegrationTestTrait
  69. {
  70. use CookieCryptTrait;
  71. use SecureFieldTokenTrait;
  72. /**
  73. * Track whether or not tests are run against
  74. * the PSR7 HTTP stack.
  75. *
  76. * @var bool
  77. */
  78. protected $_useHttpServer = false;
  79. /**
  80. * The customized application class name.
  81. *
  82. * @var string|null
  83. */
  84. protected $_appClass;
  85. /**
  86. * The customized application constructor arguments.
  87. *
  88. * @var array|null
  89. */
  90. protected $_appArgs;
  91. /**
  92. * The data used to build the next request.
  93. *
  94. * @var array
  95. */
  96. protected $_request = [];
  97. /**
  98. * The response for the most recent request.
  99. *
  100. * @var \Cake\Http\Response|null
  101. */
  102. protected $_response;
  103. /**
  104. * The exception being thrown if the case.
  105. *
  106. * @var \Exception|null
  107. */
  108. protected $_exception;
  109. /**
  110. * Session data to use in the next request.
  111. *
  112. * @var array
  113. */
  114. protected $_session = [];
  115. /**
  116. * Cookie data to use in the next request.
  117. *
  118. * @var array
  119. */
  120. protected $_cookie = [];
  121. /**
  122. * The controller used in the last request.
  123. *
  124. * @var \Cake\Controller\Controller|null
  125. */
  126. protected $_controller;
  127. /**
  128. * The last rendered view
  129. *
  130. * @var string|null
  131. */
  132. protected $_viewName;
  133. /**
  134. * The last rendered layout
  135. *
  136. * @var string|null
  137. */
  138. protected $_layoutName;
  139. /**
  140. * The session instance from the last request
  141. *
  142. * @var \Cake\Http\Session|null
  143. */
  144. protected $_requestSession;
  145. /**
  146. * Boolean flag for whether or not the request should have
  147. * a SecurityComponent token added.
  148. *
  149. * @var bool
  150. */
  151. protected $_securityToken = false;
  152. /**
  153. * Boolean flag for whether or not the request should have
  154. * a CSRF token added.
  155. *
  156. * @var bool
  157. */
  158. protected $_csrfToken = false;
  159. /**
  160. * Boolean flag for whether or not the request should re-store
  161. * flash messages
  162. *
  163. * @var bool
  164. */
  165. protected $_retainFlashMessages = false;
  166. /**
  167. * Stored flash messages before render
  168. *
  169. * @var null|array
  170. */
  171. protected $_flashMessages;
  172. /**
  173. *
  174. * @var null|string
  175. */
  176. protected $_cookieEncryptionKey;
  177. /**
  178. * Auto-detect if the HTTP middleware stack should be used.
  179. *
  180. * @before
  181. * @return void
  182. */
  183. public function setupServer()
  184. {
  185. $namespace = Configure::read('App.namespace');
  186. $this->_useHttpServer = class_exists($namespace . '\Application');
  187. }
  188. /**
  189. * Clears the state used for requests.
  190. *
  191. * @after
  192. * @return void
  193. */
  194. public function cleanup()
  195. {
  196. $this->_request = [];
  197. $this->_session = [];
  198. $this->_cookie = [];
  199. $this->_response = null;
  200. $this->_exception = null;
  201. $this->_controller = null;
  202. $this->_viewName = null;
  203. $this->_layoutName = null;
  204. $this->_requestSession = null;
  205. $this->_appClass = null;
  206. $this->_appArgs = null;
  207. $this->_securityToken = false;
  208. $this->_csrfToken = false;
  209. $this->_retainFlashMessages = false;
  210. $this->_useHttpServer = false;
  211. }
  212. /**
  213. * Toggle whether or not you want to use the HTTP Server stack.
  214. *
  215. * @param bool $enable Enable/disable the usage of the HTTP Stack.
  216. * @return void
  217. */
  218. public function useHttpServer($enable)
  219. {
  220. $this->_useHttpServer = (bool)$enable;
  221. }
  222. /**
  223. * Configure the application class to use in integration tests.
  224. *
  225. * Combined with `useHttpServer()` to customize the class name and constructor arguments
  226. * of your application class.
  227. *
  228. * @param string $class The application class name.
  229. * @param array|null $constructorArgs The constructor arguments for your application class.
  230. * @return void
  231. */
  232. public function configApplication($class, $constructorArgs)
  233. {
  234. $this->_appClass = $class;
  235. $this->_appArgs = $constructorArgs;
  236. }
  237. /**
  238. * Calling this method will enable a SecurityComponent
  239. * compatible token to be added to request data. This
  240. * lets you easily test actions protected by SecurityComponent.
  241. *
  242. * @return void
  243. */
  244. public function enableSecurityToken()
  245. {
  246. $this->_securityToken = true;
  247. }
  248. /**
  249. * Calling this method will add a CSRF token to the request.
  250. *
  251. * Both the POST data and cookie will be populated when this option
  252. * is enabled. The default parameter names will be used.
  253. *
  254. * @return void
  255. */
  256. public function enableCsrfToken()
  257. {
  258. $this->_csrfToken = true;
  259. }
  260. /**
  261. * Calling this method will re-store flash messages into the test session
  262. * after being removed by the FlashHelper
  263. *
  264. * @return void
  265. */
  266. public function enableRetainFlashMessages()
  267. {
  268. $this->_retainFlashMessages = true;
  269. }
  270. /**
  271. * Configures the data for the *next* request.
  272. *
  273. * This data is cleared in the tearDown() method.
  274. *
  275. * You can call this method multiple times to append into
  276. * the current state.
  277. *
  278. * @param array $data The request data to use.
  279. * @return void
  280. */
  281. public function configRequest(array $data)
  282. {
  283. $this->_request = $data + $this->_request;
  284. }
  285. /**
  286. * Sets session data.
  287. *
  288. * This method lets you configure the session data
  289. * you want to be used for requests that follow. The session
  290. * state is reset in each tearDown().
  291. *
  292. * You can call this method multiple times to append into
  293. * the current state.
  294. *
  295. * @param array $data The session data to use.
  296. * @return void
  297. */
  298. public function session(array $data)
  299. {
  300. $this->_session = $data + $this->_session;
  301. }
  302. /**
  303. * Sets a request cookie for future requests.
  304. *
  305. * This method lets you configure the session data
  306. * you want to be used for requests that follow. The session
  307. * state is reset in each tearDown().
  308. *
  309. * You can call this method multiple times to append into
  310. * the current state.
  311. *
  312. * @param string $name The cookie name to use.
  313. * @param mixed $value The value of the cookie.
  314. * @return void
  315. */
  316. public function cookie($name, $value)
  317. {
  318. $this->_cookie[$name] = $value;
  319. }
  320. /**
  321. * Returns the encryption key to be used.
  322. *
  323. * @return string
  324. */
  325. protected function _getCookieEncryptionKey()
  326. {
  327. if (isset($this->_cookieEncryptionKey)) {
  328. return $this->_cookieEncryptionKey;
  329. }
  330. return Security::getSalt();
  331. }
  332. /**
  333. * Sets a encrypted request cookie for future requests.
  334. *
  335. * The difference from cookie() is this encrypts the cookie
  336. * value like the CookieComponent.
  337. *
  338. * @param string $name The cookie name to use.
  339. * @param mixed $value The value of the cookie.
  340. * @param string|bool $encrypt Encryption mode to use.
  341. * @param string|null $key Encryption key used. Defaults
  342. * to Security.salt.
  343. * @return void
  344. * @see \Cake\Utility\CookieCryptTrait::_encrypt()
  345. */
  346. public function cookieEncrypted($name, $value, $encrypt = 'aes', $key = null)
  347. {
  348. $this->_cookieEncryptionKey = $key;
  349. $this->_cookie[$name] = $this->_encrypt($value, $encrypt);
  350. }
  351. /**
  352. * Performs a GET request using the current request data.
  353. *
  354. * The response of the dispatched request will be stored as
  355. * a property. You can use various assert methods to check the
  356. * response.
  357. *
  358. * @param string|array $url The URL to request.
  359. * @return void
  360. * @throws \PHPUnit\Exception
  361. */
  362. public function get($url)
  363. {
  364. $this->_sendRequest($url, 'GET');
  365. }
  366. /**
  367. * Performs a POST request using the current request data.
  368. *
  369. * The response of the dispatched request will be stored as
  370. * a property. You can use various assert methods to check the
  371. * response.
  372. *
  373. * @param string|array $url The URL to request.
  374. * @param string|array|null $data The data for the request.
  375. * @return void
  376. * @throws \PHPUnit\Exception
  377. */
  378. public function post($url, $data = [])
  379. {
  380. $this->_sendRequest($url, 'POST', $data);
  381. }
  382. /**
  383. * Performs a PATCH request using the current request data.
  384. *
  385. * The response of the dispatched request will be stored as
  386. * a property. You can use various assert methods to check the
  387. * response.
  388. *
  389. * @param string|array $url The URL to request.
  390. * @param string|array|null $data The data for the request.
  391. * @return void
  392. * @throws \PHPUnit\Exception
  393. */
  394. public function patch($url, $data = [])
  395. {
  396. $this->_sendRequest($url, 'PATCH', $data);
  397. }
  398. /**
  399. * Performs a PUT request using the current request data.
  400. *
  401. * The response of the dispatched request will be stored as
  402. * a property. You can use various assert methods to check the
  403. * response.
  404. *
  405. * @param string|array $url The URL to request.
  406. * @param string|array|null $data The data for the request.
  407. * @return void
  408. * @throws \PHPUnit\Exception
  409. */
  410. public function put($url, $data = [])
  411. {
  412. $this->_sendRequest($url, 'PUT', $data);
  413. }
  414. /**
  415. * Performs a DELETE request using the current request data.
  416. *
  417. * The response of the dispatched request will be stored as
  418. * a property. You can use various assert methods to check the
  419. * response.
  420. *
  421. * @param string|array $url The URL to request.
  422. * @return void
  423. * @throws \PHPUnit\Exception
  424. */
  425. public function delete($url)
  426. {
  427. $this->_sendRequest($url, 'DELETE');
  428. }
  429. /**
  430. * Performs a HEAD request using the current request data.
  431. *
  432. * The response of the dispatched request will be stored as
  433. * a property. You can use various assert methods to check the
  434. * response.
  435. *
  436. * @param string|array $url The URL to request.
  437. * @return void
  438. * @throws \PHPUnit\Exception
  439. */
  440. public function head($url)
  441. {
  442. $this->_sendRequest($url, 'HEAD');
  443. }
  444. /**
  445. * Performs an OPTIONS request using the current request data.
  446. *
  447. * The response of the dispatched request will be stored as
  448. * a property. You can use various assert methods to check the
  449. * response.
  450. *
  451. * @param string|array $url The URL to request.
  452. * @return void
  453. * @throws \PHPUnit\Exception
  454. */
  455. public function options($url)
  456. {
  457. $this->_sendRequest($url, 'OPTIONS');
  458. }
  459. /**
  460. * Creates and send the request into a Dispatcher instance.
  461. *
  462. * Receives and stores the response for future inspection.
  463. *
  464. * @param string|array $url The URL
  465. * @param string $method The HTTP method
  466. * @param string|array|null $data The request data.
  467. * @return void
  468. * @throws \PHPUnit\Exception
  469. */
  470. protected function _sendRequest($url, $method, $data = [])
  471. {
  472. $dispatcher = $this->_makeDispatcher();
  473. $url = $dispatcher->resolveUrl($url);
  474. try {
  475. $request = $this->_buildRequest($url, $method, $data);
  476. $response = $dispatcher->execute($request);
  477. $this->_requestSession = $request['session'];
  478. if ($this->_retainFlashMessages && $this->_flashMessages) {
  479. $this->_requestSession->write('Flash', $this->_flashMessages);
  480. }
  481. $this->_response = $response;
  482. } catch (PhpUnitException $e) {
  483. throw $e;
  484. } catch (DatabaseException $e) {
  485. throw $e;
  486. } catch (LogicException $e) {
  487. throw $e;
  488. } catch (Exception $e) {
  489. $this->_exception = $e;
  490. // Simulate the global exception handler being invoked.
  491. $this->_handleError($e);
  492. }
  493. }
  494. /**
  495. * Get the correct dispatcher instance.
  496. *
  497. * @return \Cake\TestSuite\MiddlewareDispatcher|\Cake\TestSuite\LegacyRequestDispatcher A dispatcher instance
  498. */
  499. protected function _makeDispatcher()
  500. {
  501. if ($this->_useHttpServer) {
  502. return new MiddlewareDispatcher($this, $this->_appClass, $this->_appArgs);
  503. }
  504. return new LegacyRequestDispatcher($this);
  505. }
  506. /**
  507. * Adds additional event spies to the controller/view event manager.
  508. *
  509. * @param \Cake\Event\Event $event A dispatcher event.
  510. * @param \Cake\Controller\Controller|null $controller Controller instance.
  511. * @return void
  512. */
  513. public function controllerSpy($event, $controller = null)
  514. {
  515. if (!$controller) {
  516. /** @var \Cake\Controller\Controller $controller */
  517. $controller = $event->getSubject();
  518. }
  519. $this->_controller = $controller;
  520. $events = $controller->getEventManager();
  521. $events->on('View.beforeRender', function ($event, $viewFile) use ($controller) {
  522. if (!$this->_viewName) {
  523. $this->_viewName = $viewFile;
  524. }
  525. if ($this->_retainFlashMessages) {
  526. $this->_flashMessages = $controller->getRequest()->getSession()->read('Flash');
  527. }
  528. });
  529. $events->on('View.beforeLayout', function ($event, $viewFile) {
  530. $this->_layoutName = $viewFile;
  531. });
  532. }
  533. /**
  534. * Attempts to render an error response for a given exception.
  535. *
  536. * This method will attempt to use the configured exception renderer.
  537. * If that class does not exist, the built-in renderer will be used.
  538. *
  539. * @param \Exception $exception Exception to handle.
  540. * @return void
  541. * @throws \Exception
  542. */
  543. protected function _handleError($exception)
  544. {
  545. $class = Configure::read('Error.exceptionRenderer');
  546. if (empty($class) || !class_exists($class)) {
  547. $class = 'Cake\Error\ExceptionRenderer';
  548. }
  549. /** @var \Cake\Error\ExceptionRenderer $instance */
  550. $instance = new $class($exception);
  551. $this->_response = $instance->render();
  552. }
  553. /**
  554. * Creates a request object with the configured options and parameters.
  555. *
  556. * @param string|array $url The URL
  557. * @param string $method The HTTP method
  558. * @param string|array|null $data The request data.
  559. * @return array The request context
  560. */
  561. protected function _buildRequest($url, $method, $data)
  562. {
  563. $sessionConfig = (array)Configure::read('Session') + [
  564. 'defaults' => 'php',
  565. ];
  566. $session = Session::create($sessionConfig);
  567. $session->write($this->_session);
  568. list($url, $query, $hostInfo) = $this->_url($url);
  569. $tokenUrl = $url;
  570. if ($query) {
  571. $tokenUrl .= '?' . $query;
  572. }
  573. parse_str($query, $queryData);
  574. $props = [
  575. 'url' => $url,
  576. 'session' => $session,
  577. 'query' => $queryData,
  578. 'files' => [],
  579. ];
  580. if (is_string($data)) {
  581. $props['input'] = $data;
  582. }
  583. if (!isset($props['input'])) {
  584. $data = $this->_addTokens($tokenUrl, $data);
  585. $props['post'] = $this->_castToString($data);
  586. }
  587. $props['cookies'] = $this->_cookie;
  588. $env = [
  589. 'REQUEST_METHOD' => $method,
  590. 'QUERY_STRING' => $query,
  591. 'REQUEST_URI' => $url,
  592. ];
  593. if (!empty($hostInfo['ssl'])) {
  594. $env['HTTPS'] = 'on';
  595. }
  596. if (isset($hostInfo['host'])) {
  597. $env['HTTP_HOST'] = $hostInfo['host'];
  598. }
  599. if (isset($this->_request['headers'])) {
  600. foreach ($this->_request['headers'] as $k => $v) {
  601. $name = strtoupper(str_replace('-', '_', $k));
  602. if (!in_array($name, ['CONTENT_LENGTH', 'CONTENT_TYPE'])) {
  603. $name = 'HTTP_' . $name;
  604. }
  605. $env[$name] = $v;
  606. }
  607. unset($this->_request['headers']);
  608. }
  609. $props['environment'] = $env;
  610. $props = Hash::merge($props, $this->_request);
  611. return $props;
  612. }
  613. /**
  614. * Add the CSRF and Security Component tokens if necessary.
  615. *
  616. * @param string $url The URL the form is being submitted on.
  617. * @param array $data The request body data.
  618. * @return array The request body with tokens added.
  619. */
  620. protected function _addTokens($url, $data)
  621. {
  622. if ($this->_securityToken === true) {
  623. $keys = array_map(function ($field) {
  624. return preg_replace('/(\.\d+)+$/', '', $field);
  625. }, array_keys(Hash::flatten($data)));
  626. $tokenData = $this->_buildFieldToken($url, array_unique($keys));
  627. $data['_Token'] = $tokenData;
  628. $data['_Token']['debug'] = 'SecurityComponent debug data would be added here';
  629. }
  630. if ($this->_csrfToken === true) {
  631. if (!isset($this->_cookie['csrfToken'])) {
  632. $this->_cookie['csrfToken'] = Text::uuid();
  633. }
  634. if (!isset($data['_csrfToken'])) {
  635. $data['_csrfToken'] = $this->_cookie['csrfToken'];
  636. }
  637. }
  638. return $data;
  639. }
  640. /**
  641. * Recursively casts all data to string as that is how data would be POSTed in
  642. * the real world
  643. *
  644. * @param array $data POST data
  645. * @return array
  646. */
  647. protected function _castToString($data)
  648. {
  649. foreach ($data as $key => $value) {
  650. if (is_scalar($value)) {
  651. $data[$key] = $value === false ? '0' : (string)$value;
  652. continue;
  653. }
  654. if (is_array($value)) {
  655. $looksLikeFile = isset($value['error'], $value['tmp_name'], $value['size']);
  656. if ($looksLikeFile) {
  657. continue;
  658. }
  659. $data[$key] = $this->_castToString($value);
  660. }
  661. }
  662. return $data;
  663. }
  664. /**
  665. * Creates a valid request url and parameter array more like Request::_url()
  666. *
  667. * @param string|array $url The URL
  668. * @return array Qualified URL, the query parameters, and host data
  669. */
  670. protected function _url($url)
  671. {
  672. $uri = new Uri($url);
  673. $path = $uri->getPath();
  674. $query = $uri->getQuery();
  675. $hostData = [];
  676. if ($uri->getHost()) {
  677. $hostData['host'] = $uri->getHost();
  678. }
  679. if ($uri->getScheme()) {
  680. $hostData['ssl'] = $uri->getScheme() === 'https';
  681. }
  682. return [$path, $query, $hostData];
  683. }
  684. /**
  685. * Get the response body as string
  686. *
  687. * @return string The response body.
  688. */
  689. protected function _getBodyAsString()
  690. {
  691. if (!$this->_response) {
  692. $this->fail('No response set, cannot assert content.');
  693. }
  694. return (string)$this->_response->getBody();
  695. }
  696. /**
  697. * Fetches a view variable by name.
  698. *
  699. * If the view variable does not exist, null will be returned.
  700. *
  701. * @param string $name The view variable to get.
  702. * @return mixed The view variable if set.
  703. */
  704. public function viewVariable($name)
  705. {
  706. if (empty($this->_controller->viewVars)) {
  707. $this->fail('There are no view variables, perhaps you need to run a request?');
  708. }
  709. if (isset($this->_controller->viewVars[$name])) {
  710. return $this->_controller->viewVars[$name];
  711. }
  712. return null;
  713. }
  714. /**
  715. * Asserts that the response status code is in the 2xx range.
  716. *
  717. * @param string $message Custom message for failure.
  718. * @return void
  719. */
  720. public function assertResponseOk($message = null)
  721. {
  722. $verboseMessage = $this->extractVerboseMessage($message);
  723. $this->assertThat(null, new StatusOk($this->_response), $verboseMessage);
  724. }
  725. /**
  726. * Asserts that the response status code is in the 2xx/3xx range.
  727. *
  728. * @param string $message Custom message for failure.
  729. * @return void
  730. */
  731. public function assertResponseSuccess($message = null)
  732. {
  733. $verboseMessage = $this->extractVerboseMessage($message);
  734. $this->assertThat(null, new StatusSuccess($this->_response), $verboseMessage);
  735. }
  736. /**
  737. * Asserts that the response status code is in the 4xx range.
  738. *
  739. * @param string $message Custom message for failure.
  740. * @return void
  741. */
  742. public function assertResponseError($message = null)
  743. {
  744. $this->assertThat(null, new StatusError($this->_response), $message);
  745. }
  746. /**
  747. * Asserts that the response status code is in the 5xx range.
  748. *
  749. * @param string $message Custom message for failure.
  750. * @return void
  751. */
  752. public function assertResponseFailure($message = null)
  753. {
  754. $this->assertThat(null, new StatusFailure($this->_response), $message);
  755. }
  756. /**
  757. * Asserts a specific response status code.
  758. *
  759. * @param int $code Status code to assert.
  760. * @param string $message Custom message for failure.
  761. * @return void
  762. */
  763. public function assertResponseCode($code, $message = null)
  764. {
  765. $this->assertThat($code, new StatusCode($this->_response), $message);
  766. }
  767. /**
  768. * Asserts that the Location header is correct.
  769. *
  770. * @param string|array|null $url The URL you expected the client to go to. This
  771. * can either be a string URL or an array compatible with Router::url(). Use null to
  772. * simply check for the existence of this header.
  773. * @param string $message The failure message that will be appended to the generated message.
  774. * @return void
  775. */
  776. public function assertRedirect($url = null, $message = '')
  777. {
  778. $verboseMessage = $this->extractVerboseMessage($message);
  779. $this->assertThat(null, new HeaderSet($this->_response, 'Location'), $verboseMessage);
  780. if ($url) {
  781. $this->assertThat(Router::url($url, ['_full' => true]), new HeaderEquals($this->_response, 'Location'), $verboseMessage);
  782. }
  783. }
  784. /**
  785. * Asserts that the Location header contains a substring
  786. *
  787. * @param string $url The URL you expected the client to go to.
  788. * @param string $message The failure message that will be appended to the generated message.
  789. * @return void
  790. */
  791. public function assertRedirectContains($url, $message = '')
  792. {
  793. $verboseMessage = $this->extractVerboseMessage($message);
  794. $this->assertThat(null, new HeaderSet($this->_response, 'Location'), $verboseMessage);
  795. $this->assertThat($url, new HeaderContains($this->_response, 'Location'), $verboseMessage);
  796. }
  797. /**
  798. * Asserts that the Location header does not contain a substring
  799. *
  800. * @param string $url The URL you expected the client to go to.
  801. * @param string $message The failure message that will be appended to the generated message.
  802. * @return void
  803. */
  804. public function assertRedirectNotContains($url, $message = '')
  805. {
  806. $verboseMessage = $this->extractVerboseMessage($message);
  807. $this->assertThat(null, new HeaderSet($this->_response, 'Location'), $verboseMessage);
  808. $this->assertThat($url, new HeaderNotContains($this->_response, 'Location'), $verboseMessage);
  809. }
  810. /**
  811. * Asserts that the Location header is not set.
  812. *
  813. * @param string $message The failure message that will be appended to the generated message.
  814. * @return void
  815. */
  816. public function assertNoRedirect($message = '')
  817. {
  818. $verboseMessage = $this->extractVerboseMessage($message);
  819. $this->assertThat(null, new HeaderNotSet($this->_response, 'Location'), $verboseMessage);
  820. }
  821. /**
  822. * Asserts response headers
  823. *
  824. * @param string $header The header to check
  825. * @param string $content The content to check for.
  826. * @param string $message The failure message that will be appended to the generated message.
  827. * @return void
  828. */
  829. public function assertHeader($header, $content, $message = '')
  830. {
  831. $verboseMessage = $this->extractVerboseMessage($message);
  832. $this->assertThat(null, new HeaderSet($this->_response, $header), $verboseMessage);
  833. $this->assertThat($content, new HeaderEquals($this->_response, $header), $verboseMessage);
  834. }
  835. /**
  836. * Asserts response header contains a string
  837. *
  838. * @param string $header The header to check
  839. * @param string $content The content to check for.
  840. * @param string $message The failure message that will be appended to the generated message.
  841. * @return void
  842. */
  843. public function assertHeaderContains($header, $content, $message = '')
  844. {
  845. $verboseMessage = $this->extractVerboseMessage($message);
  846. $this->assertThat(null, new HeaderSet($this->_response, $header), $verboseMessage);
  847. $this->assertThat($content, new HeaderContains($this->_response, $header), $verboseMessage);
  848. }
  849. /**
  850. * Asserts response header does not contain a string
  851. *
  852. * @param string $header The header to check
  853. * @param string $content The content to check for.
  854. * @param string $message The failure message that will be appended to the generated message.
  855. * @return void
  856. */
  857. public function assertHeaderNotContains($header, $content, $message = '')
  858. {
  859. $verboseMessage = $this->extractVerboseMessage($message);
  860. $this->assertThat(null, new HeaderSet($this->_response, $header), $verboseMessage);
  861. $this->assertThat($content, new HeaderNotContains($this->_response, $header), $verboseMessage);
  862. }
  863. /**
  864. * Asserts content type
  865. *
  866. * @param string $type The content-type to check for.
  867. * @param string $message The failure message that will be appended to the generated message.
  868. * @return void
  869. */
  870. public function assertContentType($type, $message = '')
  871. {
  872. $verboseMessage = $this->extractVerboseMessage($message);
  873. $this->assertThat($type, new ContentType($this->_response), $verboseMessage);
  874. }
  875. /**
  876. * Asserts content in the response body equals.
  877. *
  878. * @param mixed $content The content to check for.
  879. * @param string $message The failure message that will be appended to the generated message.
  880. * @return void
  881. */
  882. public function assertResponseEquals($content, $message = '')
  883. {
  884. $verboseMessage = $this->extractVerboseMessage($message);
  885. $this->assertThat($content, new BodyEquals($this->_response), $verboseMessage);
  886. }
  887. /**
  888. * Asserts content in the response body not equals.
  889. *
  890. * @param mixed $content The content to check for.
  891. * @param string $message The failure message that will be appended to the generated message.
  892. * @return void
  893. */
  894. public function assertResponseNotEquals($content, $message = '')
  895. {
  896. $verboseMessage = $this->extractVerboseMessage($message);
  897. $this->assertThat($content, new BodyNotEquals($this->_response), $verboseMessage);
  898. }
  899. /**
  900. * Asserts content exists in the response body.
  901. *
  902. * @param string $content The content to check for.
  903. * @param string $message The failure message that will be appended to the generated message.
  904. * @param bool $ignoreCase A flag to check whether we should ignore case or not.
  905. * @return void
  906. */
  907. public function assertResponseContains($content, $message = '', $ignoreCase = false)
  908. {
  909. $verboseMessage = $this->extractVerboseMessage($message);
  910. $this->assertThat($content, new BodyContains($this->_response, $ignoreCase), $verboseMessage);
  911. }
  912. /**
  913. * Asserts content does not exist in the response body.
  914. *
  915. * @param string $content The content to check for.
  916. * @param string $message The failure message that will be appended to the generated message.
  917. * @param bool $ignoreCase A flag to check whether we should ignore case or not.
  918. * @return void
  919. */
  920. public function assertResponseNotContains($content, $message = '', $ignoreCase = false)
  921. {
  922. $verboseMessage = $this->extractVerboseMessage($message);
  923. $this->assertThat($content, new BodyNotContains($this->_response, $ignoreCase), $verboseMessage);
  924. }
  925. /**
  926. * Asserts that the response body matches a given regular expression.
  927. *
  928. * @param string $pattern The pattern to compare against.
  929. * @param string $message The failure message that will be appended to the generated message.
  930. * @return void
  931. */
  932. public function assertResponseRegExp($pattern, $message = '')
  933. {
  934. $verboseMessage = $this->extractVerboseMessage($message);
  935. $this->assertThat($pattern, new BodyRegExp($this->_response), $verboseMessage);
  936. }
  937. /**
  938. * Asserts that the response body does not match a given regular expression.
  939. *
  940. * @param string $pattern The pattern to compare against.
  941. * @param string $message The failure message that will be appended to the generated message.
  942. * @return void
  943. */
  944. public function assertResponseNotRegExp($pattern, $message = '')
  945. {
  946. $verboseMessage = $this->extractVerboseMessage($message);
  947. $this->assertThat($pattern, new BodyNotRegExp($this->_response), $verboseMessage);
  948. }
  949. /**
  950. * Assert response content is not empty.
  951. *
  952. * @param string $message The failure message that will be appended to the generated message.
  953. * @return void
  954. */
  955. public function assertResponseNotEmpty($message = '')
  956. {
  957. $this->assertThat(null, new BodyNotEmpty($this->_response), $message);
  958. }
  959. /**
  960. * Assert response content is empty.
  961. *
  962. * @param string $message The failure message that will be appended to the generated message.
  963. * @return void
  964. */
  965. public function assertResponseEmpty($message = '')
  966. {
  967. $this->assertThat(null, new BodyEmpty($this->_response), $message);
  968. }
  969. /**
  970. * Asserts that the search string was in the template name.
  971. *
  972. * @param string $content The content to check for.
  973. * @param string $message The failure message that will be appended to the generated message.
  974. * @return void
  975. */
  976. public function assertTemplate($content, $message = '')
  977. {
  978. $verboseMessage = $this->extractVerboseMessage($message);
  979. $this->assertThat($content, new TemplateFileEquals($this->_viewName), $verboseMessage);
  980. }
  981. /**
  982. * Asserts that the search string was in the layout name.
  983. *
  984. * @param string $content The content to check for.
  985. * @param string $message The failure message that will be appended to the generated message.
  986. * @return void
  987. */
  988. public function assertLayout($content, $message = '')
  989. {
  990. $verboseMessage = $this->extractVerboseMessage($message);
  991. $this->assertThat($content, new LayoutFileEquals($this->_layoutName), $verboseMessage);
  992. }
  993. /**
  994. * Asserts session contents
  995. *
  996. * @param string $expected The expected contents.
  997. * @param string $path The session data path. Uses Hash::get() compatible notation
  998. * @param string $message The failure message that will be appended to the generated message.
  999. * @return void
  1000. */
  1001. public function assertSession($expected, $path, $message = '')
  1002. {
  1003. $verboseMessage = $this->extractVerboseMessage($message);
  1004. $this->assertThat($expected, new SessionEquals($this->_requestSession, $path), $verboseMessage);
  1005. }
  1006. /**
  1007. * Asserts a flash message was set
  1008. *
  1009. * @param string $expected Expected message
  1010. * @param string $key Flash key
  1011. * @param string $message Assertion failure message
  1012. * @return void
  1013. */
  1014. public function assertFlashMessage($expected, $key = 'flash', $message = '')
  1015. {
  1016. $verboseMessage = $this->extractVerboseMessage($message);
  1017. $this->assertThat($expected, new FlashParamEquals($this->_requestSession, $key, 'message'), $verboseMessage);
  1018. }
  1019. /**
  1020. * Asserts a flash message was set at a certain index
  1021. *
  1022. * @param int $at Flash index
  1023. * @param string $expected Expected message
  1024. * @param string $key Flash key
  1025. * @param string $message Assertion failure message
  1026. * @return void
  1027. */
  1028. public function assertFlashMessageAt($at, $expected, $key = 'flash', $message = '')
  1029. {
  1030. $verboseMessage = $this->extractVerboseMessage($message);
  1031. $this->assertThat($expected, new FlashParamEquals($this->_requestSession, $key, 'message', $at), $verboseMessage);
  1032. }
  1033. /**
  1034. * Asserts a flash element was set
  1035. *
  1036. * @param string $expected Expected element name
  1037. * @param string $key Flash key
  1038. * @param string $message Assertion failure message
  1039. * @return void
  1040. */
  1041. public function assertFlashElement($expected, $key = 'flash', $message = '')
  1042. {
  1043. $verboseMessage = $this->extractVerboseMessage($message);
  1044. $this->assertThat($expected, new FlashParamEquals($this->_requestSession, $key, 'element'), $verboseMessage);
  1045. }
  1046. /**
  1047. * Asserts a flash element was set at a certain index
  1048. *
  1049. * @param int $at Flash index
  1050. * @param string $expected Expected element name
  1051. * @param string $key Flash key
  1052. * @param string $message Assertion failure message
  1053. * @return void
  1054. */
  1055. public function assertFlashElementAt($at, $expected, $key = 'flash', $message = '')
  1056. {
  1057. $verboseMessage = $this->extractVerboseMessage($message);
  1058. $this->assertThat($expected, new FlashParamEquals($this->_requestSession, $key, 'element', $at), $verboseMessage);
  1059. }
  1060. /**
  1061. * Asserts cookie values
  1062. *
  1063. * @param string $expected The expected contents.
  1064. * @param string $name The cookie name.
  1065. * @param string $message The failure message that will be appended to the generated message.
  1066. * @return void
  1067. */
  1068. public function assertCookie($expected, $name, $message = '')
  1069. {
  1070. $verboseMessage = $this->extractVerboseMessage($message);
  1071. $this->assertThat($name, new CookieSet($this->_response), $verboseMessage);
  1072. $this->assertThat($expected, new CookieEquals($this->_response, $name), $verboseMessage);
  1073. }
  1074. /**
  1075. * Asserts a cookie has not been set in the response
  1076. *
  1077. * @param string $cookie The cookie name to check
  1078. * @param string $message The failure message that will be appended to the generated message.
  1079. * @return void
  1080. */
  1081. public function assertCookieNotSet($cookie, $message = '')
  1082. {
  1083. $verboseMessage = $this->extractVerboseMessage($message);
  1084. $this->assertThat($cookie, new CookieNotSet($this->_response), $verboseMessage);
  1085. }
  1086. /**
  1087. * Disable the error handler middleware.
  1088. *
  1089. * By using this function, exceptions are no longer caught by the ErrorHandlerMiddleware
  1090. * and are instead re-thrown by the TestExceptionRenderer. This can be helpful
  1091. * when trying to diagnose/debug unexpected failures in test cases.
  1092. *
  1093. * @return void
  1094. */
  1095. public function disableErrorHandlerMiddleware()
  1096. {
  1097. Configure::write('Error.exceptionRenderer', TestExceptionRenderer::class);
  1098. }
  1099. /**
  1100. * Asserts cookie values which are encrypted by the
  1101. * CookieComponent.
  1102. *
  1103. * The difference from assertCookie() is this decrypts the cookie
  1104. * value like the CookieComponent for this assertion.
  1105. *
  1106. * @param string $expected The expected contents.
  1107. * @param string $name The cookie name.
  1108. * @param string|bool $encrypt Encryption mode to use.
  1109. * @param string|null $key Encryption key used. Defaults
  1110. * to Security.salt.
  1111. * @param string $message The failure message that will be appended to the generated message.
  1112. * @return void
  1113. * @see \Cake\Utility\CookieCryptTrait::_encrypt()
  1114. */
  1115. public function assertCookieEncrypted($expected, $name, $encrypt = 'aes', $key = null, $message = '')
  1116. {
  1117. $verboseMessage = $this->extractVerboseMessage($message);
  1118. $this->assertThat($name, new CookieSet($this->_response), $verboseMessage);
  1119. $this->_cookieEncryptionKey = $key;
  1120. $this->assertThat($expected, new CookieEncryptedEquals($this->_response, $name, $encrypt, $this->_getCookieEncryptionKey()));
  1121. }
  1122. /**
  1123. * Asserts that a file with the given name was sent in the response
  1124. *
  1125. * @param string $expected The absolute file path that should be sent in the response.
  1126. * @param string $message The failure message that will be appended to the generated message.
  1127. * @return void
  1128. */
  1129. public function assertFileResponse($expected, $message = '')
  1130. {
  1131. $verboseMessage = $this->extractVerboseMessage($message);
  1132. $this->assertThat(null, new FileSent($this->_response), $verboseMessage);
  1133. $this->assertThat($expected, new FileSentAs($this->_response), $verboseMessage);
  1134. }
  1135. /**
  1136. * Inspect controller to extract possible causes of the failed assertion
  1137. *
  1138. * @param string $message Original message to use as a base
  1139. * @return null|string
  1140. */
  1141. protected function extractVerboseMessage($message = null)
  1142. {
  1143. if ($this->_exception instanceof \Exception) {
  1144. $message .= $this->extractExceptionMessage($this->_exception);
  1145. }
  1146. if ($this->_controller === null) {
  1147. return $message;
  1148. }
  1149. $error = Hash::get($this->_controller->viewVars, 'error');
  1150. if ($error instanceof \Exception) {
  1151. $message .= $this->extractExceptionMessage($this->viewVariable('error'));
  1152. }
  1153. return $message;
  1154. }
  1155. /**
  1156. * Extract verbose message for existing exception
  1157. *
  1158. * @param \Exception $exception Exception to extract
  1159. * @return string
  1160. */
  1161. protected function extractExceptionMessage(\Exception $exception)
  1162. {
  1163. return PHP_EOL .
  1164. sprintf('Possibly related to %s: "%s" ', get_class($exception), $exception->getMessage()) .
  1165. PHP_EOL .
  1166. $exception->getTraceAsString();
  1167. }
  1168. }