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