PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/internal/Magento/Framework/Webapi/Test/Unit/ErrorProcessorTest.php

https://gitlab.com/crazybutterfly815/magento2
PHP | 339 lines | 225 code | 25 blank | 89 comment | 0 complexity | b79e121c9202dedc07c21e5b4c64a2f1 MD5 | raw file
  1. <?php
  2. /**
  3. * Test Webapi Error Processor.
  4. *
  5. * Copyright © 2016 Magento. All rights reserved.
  6. * See COPYING.txt for license details.
  7. */
  8. namespace Magento\Framework\Webapi\Test\Unit;
  9. use \Magento\Framework\Webapi\ErrorProcessor;
  10. use Magento\Framework\Exception\AuthorizationException;
  11. use Magento\Framework\Exception\NoSuchEntityException;
  12. use Magento\Framework\Webapi\Exception as WebapiException;
  13. use Magento\Framework\Phrase;
  14. class ErrorProcessorTest extends \PHPUnit_Framework_TestCase
  15. {
  16. /** @var ErrorProcessor */
  17. protected $_errorProcessor;
  18. /** @var \Magento\Framework\Json\Encoder */
  19. protected $encoderMock;
  20. /** @var \PHPUnit_Framework_MockObject_MockObject */
  21. protected $_appStateMock;
  22. /** @var \Psr\Log\LoggerInterface */
  23. protected $_loggerMock;
  24. protected function setUp()
  25. {
  26. /** Set up mocks for SUT. */
  27. $this->encoderMock = $this->getMockBuilder(\Magento\Framework\Json\Encoder::class)
  28. ->disableOriginalConstructor()
  29. ->setMethods(['encode'])
  30. ->getMock();
  31. $this->_appStateMock = $this->getMockBuilder(\Magento\Framework\App\State::class)
  32. ->disableOriginalConstructor()
  33. ->getMock();
  34. $this->_loggerMock = $this->getMockBuilder(\Psr\Log\LoggerInterface::class)->getMock();
  35. $filesystemMock = $this->getMockBuilder(\Magento\Framework\Filesystem::class)
  36. ->disableOriginalConstructor()
  37. ->getMock();
  38. /** Initialize SUT. */
  39. $this->_errorProcessor = new ErrorProcessor(
  40. $this->encoderMock,
  41. $this->_appStateMock,
  42. $this->_loggerMock,
  43. $filesystemMock
  44. );
  45. parent::setUp();
  46. }
  47. protected function tearDown()
  48. {
  49. unset($this->_errorProcessor);
  50. unset($this->encoderMock);
  51. unset($this->_appStateMock);
  52. parent::tearDown();
  53. }
  54. /**
  55. * Test render method in JSON format.
  56. *
  57. * @return void
  58. */
  59. public function testRenderJson()
  60. {
  61. $_SERVER['HTTP_ACCEPT'] = 'json';
  62. /** Assert that jsonEncode method will be executed once. */
  63. $this->encoderMock->expects(
  64. $this->once()
  65. )->method(
  66. 'encode'
  67. )->will(
  68. $this->returnCallback([$this, 'callbackJsonEncode'], $this->returnArgument(0))
  69. );
  70. /** Init output buffering to catch output via echo function. */
  71. ob_start();
  72. $this->_errorProcessor->renderErrorMessage('Message');
  73. /** Get output buffer. */
  74. $actualResult = ob_get_contents();
  75. ob_end_clean();
  76. $expectedResult = '{"messages":{"error":[{"code":500,"message":"Message"}]}}';
  77. $this->assertEquals($expectedResult, $actualResult, 'Invalid rendering in JSON.');
  78. }
  79. /**
  80. * Callback function for RenderJson and RenderJsonInDeveloperMode tests.
  81. *
  82. * Method encodes data to JSON and returns it.
  83. *
  84. * @param array $data
  85. * @return string
  86. */
  87. public function callbackJsonEncode($data)
  88. {
  89. return json_encode($data);
  90. }
  91. /**
  92. * Test render method in JSON format with turned on developer mode.
  93. * @return void
  94. */
  95. public function testRenderJsonInDeveloperMode()
  96. {
  97. $_SERVER['HTTP_ACCEPT'] = 'json';
  98. /** Mock app to return enabled developer mode flag. */
  99. $this->_appStateMock->expects($this->any())->method('getMode')->will($this->returnValue('developer'));
  100. /** Assert that jsonEncode method will be executed once. */
  101. $this->encoderMock->expects(
  102. $this->once()
  103. )->method(
  104. 'encode'
  105. )->will(
  106. $this->returnCallback([$this, 'callbackJsonEncode'], $this->returnArgument(0))
  107. );
  108. ob_start();
  109. $this->_errorProcessor->renderErrorMessage('Message', 'Message trace.', 401);
  110. $actualResult = ob_get_contents();
  111. ob_end_clean();
  112. $expectedResult = '{"messages":{"error":[{"code":401,"message":"Message","trace":"Message trace."}]}}';
  113. $this->assertEquals($expectedResult, $actualResult, 'Invalid rendering in JSON.');
  114. }
  115. /**
  116. * Test render method in XML format.
  117. * @return void
  118. */
  119. public function testRenderXml()
  120. {
  121. $_SERVER['HTTP_ACCEPT'] = 'xml';
  122. /** Init output buffering to catch output via echo function. */
  123. ob_start();
  124. $this->_errorProcessor->renderErrorMessage('Message');
  125. /** Get output buffer. */
  126. $actualResult = ob_get_contents();
  127. ob_end_clean();
  128. $expectedResult = '<?xml version="1.0"?><error><messages><error><data_item><code>500</code>' .
  129. '<message><![CDATA[Message]]></message></data_item></error></messages></error>';
  130. $this->assertEquals($expectedResult, $actualResult, 'Invalid rendering in XML.');
  131. }
  132. /**
  133. * Test render method in XML format with turned on developer mode.
  134. * @return void
  135. */
  136. public function testRenderXmlInDeveloperMode()
  137. {
  138. $_SERVER['HTTP_ACCEPT'] = 'xml';
  139. /** Mock app to return enabled developer mode flag. */
  140. $this->_appStateMock->expects($this->any())->method('getMode')->will($this->returnValue('developer'));
  141. /** Init output buffering to catch output via echo function. */
  142. ob_start();
  143. $this->_errorProcessor->renderErrorMessage('Message', 'Trace message.', 401);
  144. /** Get output buffer. */
  145. $actualResult = ob_get_contents();
  146. ob_end_clean();
  147. $expectedResult = '<?xml version="1.0"?><error><messages><error><data_item><code>401</code><message>' .
  148. '<![CDATA[Message]]></message><trace><![CDATA[Trace message.]]></trace></data_item></error>' .
  149. '</messages></error>';
  150. $this->assertEquals($expectedResult, $actualResult, 'Invalid rendering in XML with turned on developer mode.');
  151. }
  152. /**
  153. * Test default render format is JSON.
  154. * @return void
  155. */
  156. public function testRenderDefaultFormat()
  157. {
  158. /** Set undefined rendering format. */
  159. $_SERVER['HTTP_ACCEPT'] = 'undefined';
  160. /** Assert that jsonEncode method will be executed at least once. */
  161. $this->encoderMock->expects($this->atLeastOnce())->method('encode');
  162. $this->_errorProcessor->renderErrorMessage('Message');
  163. }
  164. /**
  165. * Test maskException method with turned on developer mode.
  166. * @return void
  167. */
  168. public function testMaskExceptionInDeveloperMode()
  169. {
  170. /** Mock app isDeveloperMode to return true. */
  171. $this->_appStateMock->expects($this->once())->method('getMode')->will($this->returnValue('developer'));
  172. /** Init Logical exception. */
  173. $errorMessage = 'Error Message';
  174. $logicalException = new \LogicException($errorMessage);
  175. /** Assert that Logic exception is converted to WebapiException without message obfuscation. */
  176. $maskedException = $this->_errorProcessor->maskException($logicalException);
  177. $this->assertInstanceOf(\Magento\Framework\Webapi\Exception::class, $maskedException);
  178. $this->assertEquals(
  179. $errorMessage,
  180. $maskedException->getMessage(),
  181. 'Exception was masked incorrectly in developer mode.'
  182. );
  183. }
  184. /**
  185. * Test sendResponse method with various exceptions
  186. *
  187. * @param \Exception $exception
  188. * @param int $expectedHttpCode
  189. * @param string $expectedMessage
  190. * @param array $expectedDetails
  191. * @return void
  192. * @dataProvider dataProviderForSendResponseExceptions
  193. */
  194. public function testMaskException($exception, $expectedHttpCode, $expectedMessage, $expectedDetails)
  195. {
  196. /** Assert that exception was logged. */
  197. // TODO:MAGETWO-21077 $this->_loggerMock->expects($this->once())->method('critical');
  198. $maskedException = $this->_errorProcessor->maskException($exception);
  199. $this->assertMaskedException(
  200. $maskedException,
  201. $expectedHttpCode,
  202. $expectedMessage,
  203. $expectedDetails
  204. );
  205. }
  206. /**
  207. * Test logged exception is the same as the thrown one in production mode
  208. */
  209. public function testCriticalExceptionStackTrace()
  210. {
  211. $thrownException = new \Exception('', 0);
  212. $this->_loggerMock->expects($this->once())
  213. ->method('critical')
  214. ->will(
  215. $this->returnCallback(
  216. function (\Exception $loggedException) use ($thrownException) {
  217. $this->assertSame($thrownException, $loggedException->getPrevious());
  218. }
  219. )
  220. );
  221. $this->_errorProcessor->maskException($thrownException);
  222. }
  223. /**
  224. * @return array
  225. */
  226. public function dataProviderForSendResponseExceptions()
  227. {
  228. return [
  229. 'NoSuchEntityException' => [
  230. new NoSuchEntityException(
  231. new Phrase(
  232. 'No such entity with %fieldName = %fieldValue, %field2Name = %field2Value',
  233. [
  234. 'fieldName' => 'detail1',
  235. 'fieldValue' => 'value1',
  236. 'field2Name' => 'resource_id',
  237. 'field2Value' => 'resource10',
  238. ]
  239. )
  240. ),
  241. \Magento\Framework\Webapi\Exception::HTTP_NOT_FOUND,
  242. 'No such entity with %fieldName = %fieldValue, %field2Name = %field2Value',
  243. [
  244. 'fieldName' => 'detail1',
  245. 'fieldValue' => 'value1',
  246. 'field2Name' => 'resource_id',
  247. 'field2Value' => 'resource10',
  248. ],
  249. ],
  250. 'NoSuchEntityException (Empty message)' => [
  251. new NoSuchEntityException(),
  252. WebapiException::HTTP_NOT_FOUND,
  253. 'No such entity.',
  254. [],
  255. ],
  256. 'AuthorizationException' => [
  257. new AuthorizationException(
  258. new Phrase(
  259. 'Consumer %consumer_id is not authorized to access %resources',
  260. ['consumer_id' => '3', 'resources' => '4']
  261. )
  262. ),
  263. WebapiException::HTTP_UNAUTHORIZED,
  264. 'Consumer %consumer_id is not authorized to access %resources',
  265. ['consumer_id' => '3', 'resources' => '4'],
  266. ],
  267. 'Exception' => [
  268. new \Exception('Non service exception', 5678),
  269. WebapiException::HTTP_INTERNAL_ERROR,
  270. 'Internal Error. Details are available in Magento log file. Report ID:',
  271. [],
  272. ]
  273. ];
  274. }
  275. /**
  276. * Assert that masked exception contains expected data.
  277. *
  278. * @param \Exception $maskedException
  279. * @param int $expectedHttpCode
  280. * @param string $expectedMessage
  281. * @param array $expectedDetails
  282. * @return void
  283. */
  284. public function assertMaskedException(
  285. $maskedException,
  286. $expectedHttpCode,
  287. $expectedMessage,
  288. $expectedDetails
  289. ) {
  290. /** All masked exceptions must be WebapiException */
  291. $expectedType = \Magento\Framework\Webapi\Exception::class;
  292. $this->assertInstanceOf(
  293. $expectedType,
  294. $maskedException,
  295. "Masked exception type is invalid: expected '{$expectedType}', given '" . get_class(
  296. $maskedException
  297. ) . "'."
  298. );
  299. /** @var $maskedException WebapiException */
  300. $this->assertEquals(
  301. $expectedHttpCode,
  302. $maskedException->getHttpCode(),
  303. "Masked exception HTTP code is invalid: expected '{$expectedHttpCode}', " .
  304. "given '{$maskedException->getHttpCode()}'."
  305. );
  306. $this->assertContains(
  307. $expectedMessage,
  308. $maskedException->getMessage(),
  309. "Masked exception message is invalid: expected '{$expectedMessage}', " .
  310. "given '{$maskedException->getMessage()}'."
  311. );
  312. $this->assertEquals($expectedDetails, $maskedException->getDetails(), "Masked exception details are invalid.");
  313. }
  314. }