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