IntegrationTestCase.php 37 KB

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