AbstractController.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. /**
  7. * Abstract class for the controller tests
  8. */
  9. namespace Magento\TestFramework\TestCase;
  10. use Magento\Framework\Data\Form\FormKey;
  11. use Magento\Framework\Message\MessageInterface;
  12. use Magento\Framework\Stdlib\CookieManagerInterface;
  13. use Magento\Framework\View\Element\Message\InterpretationStrategyInterface;
  14. use Magento\Theme\Controller\Result\MessagePlugin;
  15. use Magento\Framework\App\Request\Http as HttpRequest;
  16. use Magento\Framework\App\Response\Http as HttpResponse;
  17. /**
  18. * @SuppressWarnings(PHPMD.NumberOfChildren)
  19. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  20. */
  21. abstract class AbstractController extends \PHPUnit\Framework\TestCase
  22. {
  23. protected $_runCode = '';
  24. protected $_runScope = 'store';
  25. protected $_runOptions = [];
  26. /**
  27. * @var \Magento\Framework\App\RequestInterface
  28. */
  29. protected $_request;
  30. /**
  31. * @var \Magento\Framework\App\ResponseInterface
  32. */
  33. protected $_response;
  34. /**
  35. * @var \Magento\TestFramework\ObjectManager
  36. */
  37. protected $_objectManager;
  38. /**
  39. * Whether absence of session error messages has to be asserted automatically upon a test completion
  40. *
  41. * @var bool
  42. */
  43. protected $_assertSessionErrors = false;
  44. /**
  45. * Bootstrap instance getter
  46. *
  47. * @return \Magento\TestFramework\Helper\Bootstrap
  48. */
  49. protected function _getBootstrap()
  50. {
  51. return \Magento\TestFramework\Helper\Bootstrap::getInstance();
  52. }
  53. /**
  54. * Bootstrap application before any test
  55. */
  56. protected function setUp()
  57. {
  58. $this->_assertSessionErrors = false;
  59. $this->_objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
  60. $this->_objectManager->removeSharedInstance(\Magento\Framework\App\ResponseInterface::class);
  61. $this->_objectManager->removeSharedInstance(\Magento\Framework\App\RequestInterface::class);
  62. }
  63. /**
  64. * @inheritDoc
  65. */
  66. protected function tearDown()
  67. {
  68. $this->_request = null;
  69. $this->_response = null;
  70. $this->_objectManager = null;
  71. }
  72. /**
  73. * Ensure that there were no error messages displayed on the admin panel
  74. */
  75. protected function assertPostConditions()
  76. {
  77. if ($this->_assertSessionErrors) {
  78. // equalTo() is intentionally used instead of isEmpty() to provide the informative diff
  79. $this->assertSessionMessages(
  80. $this->equalTo([]),
  81. \Magento\Framework\Message\MessageInterface::TYPE_ERROR
  82. );
  83. }
  84. }
  85. /**
  86. * Run request
  87. *
  88. * @param string $uri
  89. */
  90. public function dispatch($uri)
  91. {
  92. /** @var HttpRequest $request */
  93. $request = $this->getRequest();
  94. $request->setRequestUri($uri);
  95. if ($request->isPost()
  96. && !array_key_exists('form_key', $request->getPost())
  97. ) {
  98. /** @var FormKey $formKey */
  99. $formKey = $this->_objectManager->get(FormKey::class);
  100. $request->setPostValue('form_key', $formKey->getFormKey());
  101. }
  102. $this->_getBootstrap()->runApp();
  103. }
  104. /**
  105. * Request getter
  106. *
  107. * @return \Magento\Framework\App\RequestInterface|HttpRequest
  108. */
  109. public function getRequest()
  110. {
  111. if (!$this->_request) {
  112. $this->_request = $this->_objectManager->get(\Magento\Framework\App\RequestInterface::class);
  113. }
  114. return $this->_request;
  115. }
  116. /**
  117. * Response getter
  118. *
  119. * @return \Magento\Framework\App\ResponseInterface|HttpResponse
  120. */
  121. public function getResponse()
  122. {
  123. if (!$this->_response) {
  124. $this->_response = $this->_objectManager->get(\Magento\Framework\App\ResponseInterface::class);
  125. }
  126. return $this->_response;
  127. }
  128. /**
  129. * Assert that response is '404 Not Found'
  130. */
  131. public function assert404NotFound()
  132. {
  133. $this->assertEquals('noroute', $this->getRequest()->getControllerName());
  134. $this->assertContains('404 Not Found', $this->getResponse()->getBody());
  135. }
  136. /**
  137. * Analyze response object and look for header with specified name, and assert a regex towards its value
  138. *
  139. * @param string $headerName
  140. * @param string $valueRegex
  141. * @throws \PHPUnit\Framework\AssertionFailedError when header not found
  142. */
  143. public function assertHeaderPcre($headerName, $valueRegex)
  144. {
  145. $headerFound = false;
  146. $headers = $this->getResponse()->getHeaders();
  147. foreach ($headers as $header) {
  148. if ($header->getFieldName() === $headerName) {
  149. $headerFound = true;
  150. $this->assertRegExp($valueRegex, $header->getFieldValue());
  151. }
  152. }
  153. if (!$headerFound) {
  154. $this->fail("Header '{$headerName}' was not found. Headers dump:\n" . var_export($headers, 1));
  155. }
  156. }
  157. /**
  158. * Assert that there is a redirect to expected URL.
  159. * Omit expected URL to check that redirect to wherever has been occurred.
  160. * Examples of usage:
  161. * $this->assertRedirect($this->equalTo($expectedUrl));
  162. * $this->assertRedirect($this->stringStartsWith($expectedUrlPrefix));
  163. * $this->assertRedirect($this->stringEndsWith($expectedUrlSuffix));
  164. * $this->assertRedirect($this->stringContains($expectedUrlSubstring));
  165. *
  166. * @param \PHPUnit\Framework\Constraint\Constraint|null $urlConstraint
  167. */
  168. public function assertRedirect(\PHPUnit\Framework\Constraint\Constraint $urlConstraint = null)
  169. {
  170. $this->assertTrue($this->getResponse()->isRedirect(), 'Redirect was expected, but none was performed.');
  171. if ($urlConstraint) {
  172. $actualUrl = '';
  173. foreach ($this->getResponse()->getHeaders() as $header) {
  174. if ($header->getFieldName() == 'Location') {
  175. $actualUrl = $header->getFieldValue();
  176. break;
  177. }
  178. }
  179. $this->assertThat($actualUrl, $urlConstraint, 'Redirection URL does not match expectations');
  180. }
  181. }
  182. /**
  183. * Assert that actual session messages meet expectations:
  184. * Usage examples:
  185. * $this->assertSessionMessages($this->isEmpty(), \Magento\Framework\Message\MessageInterface::TYPE_ERROR);
  186. * $this->assertSessionMessages($this->equalTo(['Entity has been saved.'],
  187. * \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS);
  188. *
  189. * @param \PHPUnit\Framework\Constraint\Constraint $constraint Constraint to compare actual messages against
  190. * @param string|null $messageType Message type filter,
  191. * one of the constants \Magento\Framework\Message\MessageInterface::*
  192. * @param string $messageManagerClass Class of the session model that manages messages
  193. */
  194. public function assertSessionMessages(
  195. \PHPUnit\Framework\Constraint\Constraint $constraint,
  196. $messageType = null,
  197. $messageManagerClass = \Magento\Framework\Message\Manager::class
  198. ) {
  199. $this->_assertSessionErrors = false;
  200. /** @var MessageInterface[]|string[] $messageObjects */
  201. $messages = $this->getMessages($messageType, $messageManagerClass);
  202. /** @var string[] $messages */
  203. $messagesFiltered = array_map(
  204. function ($message) {
  205. /** @var MessageInterface|string $message */
  206. return ($message instanceof MessageInterface) ? $message->toString() : $message;
  207. },
  208. $messages
  209. );
  210. $this->assertThat(
  211. $messagesFiltered,
  212. $constraint,
  213. 'Session messages do not meet expectations ' . var_export($messagesFiltered, true)
  214. );
  215. }
  216. /**
  217. * Return all stored messages
  218. *
  219. * @param string|null $messageType
  220. * @param string $messageManagerClass
  221. * @return array
  222. */
  223. protected function getMessages(
  224. $messageType = null,
  225. $messageManagerClass = \Magento\Framework\Message\Manager::class
  226. ) {
  227. return array_merge(
  228. $this->getSessionMessages($messageType, $messageManagerClass),
  229. $this->getCookieMessages($messageType)
  230. );
  231. }
  232. /**
  233. * Return messages stored in session
  234. *
  235. * @param string|null $messageType
  236. * @param string $messageManagerClass
  237. * @return array
  238. */
  239. protected function getSessionMessages(
  240. $messageType = null,
  241. $messageManagerClass = \Magento\Framework\Message\Manager::class
  242. ) {
  243. /** @var $messageManager \Magento\Framework\Message\ManagerInterface */
  244. $messageManager = $this->_objectManager->get($messageManagerClass);
  245. /** @var $messages \Magento\Framework\Message\AbstractMessage[] */
  246. if ($messageType === null) {
  247. $messages = $messageManager->getMessages()->getItems();
  248. } else {
  249. $messages = $messageManager->getMessages()->getItemsByType($messageType);
  250. }
  251. /** @var $messageManager InterpretationStrategyInterface */
  252. $interpretationStrategy = $this->_objectManager->get(InterpretationStrategyInterface::class);
  253. $actualMessages = [];
  254. foreach ($messages as $message) {
  255. $actualMessages[] = $interpretationStrategy->interpret($message);
  256. }
  257. return $actualMessages;
  258. }
  259. /**
  260. * Return messages stored in cookies by type
  261. *
  262. * @param string|null $messageType
  263. * @return array
  264. */
  265. protected function getCookieMessages($messageType = null)
  266. {
  267. /** @var $cookieManager CookieManagerInterface */
  268. $cookieManager = $this->_objectManager->get(CookieManagerInterface::class);
  269. /** @var $jsonSerializer \Magento\Framework\Serialize\Serializer\Json */
  270. $jsonSerializer = $this->_objectManager->get(\Magento\Framework\Serialize\Serializer\Json::class);
  271. try {
  272. $messages = $jsonSerializer->unserialize(
  273. $cookieManager->getCookie(
  274. MessagePlugin::MESSAGES_COOKIES_NAME,
  275. $jsonSerializer->serialize([])
  276. )
  277. );
  278. if (!is_array($messages)) {
  279. $messages = [];
  280. }
  281. } catch (\InvalidArgumentException $e) {
  282. $messages = [];
  283. }
  284. $actualMessages = [];
  285. foreach ($messages as $message) {
  286. if ($messageType === null || $message['type'] == $messageType) {
  287. $actualMessages[] = $message['text'];
  288. }
  289. }
  290. return $actualMessages;
  291. }
  292. }