ConfigSetCommandTest.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Config\Console\Command;
  7. use Magento\Config\Model\Config\Backend\Admin\Custom;
  8. use Magento\Config\Model\Config\Structure\Converter;
  9. use Magento\Config\Model\Config\Structure\Data as StructureData;
  10. use Magento\Directory\Model\Currency;
  11. use Magento\Framework\App\Config\ConfigPathResolver;
  12. use Magento\Framework\App\Config\ScopeConfigInterface;
  13. use Magento\Framework\App\DeploymentConfig\FileReader;
  14. use Magento\Framework\App\DeploymentConfig\Writer;
  15. use Magento\Framework\App\Filesystem\DirectoryList;
  16. use Magento\Framework\Config\File\ConfigFilePool;
  17. use Magento\Framework\Console\Cli;
  18. use Magento\Framework\Filesystem;
  19. use Magento\Framework\ObjectManagerInterface;
  20. use Magento\Framework\Stdlib\ArrayManager;
  21. use Magento\Store\Model\ScopeInterface;
  22. use Magento\TestFramework\Helper\Bootstrap;
  23. use Magento\Framework\App\Config\ReinitableConfigInterface;
  24. use PHPUnit_Framework_MockObject_MockObject as Mock;
  25. use Symfony\Component\Console\Input\InputInterface;
  26. use Symfony\Component\Console\Output\OutputInterface;
  27. /**
  28. * Tests the different flows of config:set command.
  29. *
  30. * @inheritdoc
  31. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  32. * @magentoDbIsolation enabled
  33. */
  34. class ConfigSetCommandTest extends \PHPUnit\Framework\TestCase
  35. {
  36. /**
  37. * @var ObjectManagerInterface
  38. */
  39. private $objectManager;
  40. /**
  41. * @var InputInterface|Mock
  42. */
  43. private $inputMock;
  44. /**
  45. * @var OutputInterface|Mock
  46. */
  47. private $outputMock;
  48. /**
  49. * @var ScopeConfigInterface
  50. */
  51. private $scopeConfig;
  52. /**
  53. * @var FileReader
  54. */
  55. private $reader;
  56. /**
  57. * @var Filesystem
  58. */
  59. private $filesystem;
  60. /**
  61. * @var ConfigFilePool
  62. */
  63. private $configFilePool;
  64. /**
  65. * @var ArrayManager
  66. */
  67. private $arrayManager;
  68. /**
  69. * @var array
  70. */
  71. private $config;
  72. /**
  73. * @var ReinitableConfigInterface
  74. */
  75. private $appConfig;
  76. /**
  77. * @inheritdoc
  78. */
  79. protected function setUp()
  80. {
  81. Bootstrap::getInstance()->reinitialize();
  82. $this->objectManager = Bootstrap::getObjectManager();
  83. $this->extendSystemStructure();
  84. $this->scopeConfig = $this->objectManager->get(ScopeConfigInterface::class);
  85. $this->reader = $this->objectManager->get(FileReader::class);
  86. $this->filesystem = $this->objectManager->get(Filesystem::class);
  87. $this->configFilePool = $this->objectManager->get(ConfigFilePool::class);
  88. $this->arrayManager = $this->objectManager->get(ArrayManager::class);
  89. $this->appConfig = $this->objectManager->get(ReinitableConfigInterface::class);
  90. // Snapshot of configuration.
  91. $this->config = $this->loadConfig();
  92. // Mocks for objects.
  93. $this->inputMock = $this->getMockBuilder(InputInterface::class)
  94. ->getMockForAbstractClass();
  95. $this->outputMock = $this->getMockBuilder(OutputInterface::class)
  96. ->getMockForAbstractClass();
  97. }
  98. /**
  99. * @inheritdoc
  100. */
  101. protected function tearDown()
  102. {
  103. $this->filesystem->getDirectoryWrite(DirectoryList::CONFIG)->writeFile(
  104. $this->configFilePool->getPath(ConfigFilePool::APP_ENV),
  105. "<?php\n return array();\n"
  106. );
  107. /** @var Writer $writer */
  108. $writer = $this->objectManager->get(Writer::class);
  109. $writer->saveConfig([ConfigFilePool::APP_ENV => $this->config]);
  110. $this->appConfig->reinit();
  111. }
  112. /**
  113. * Add test system structure to main system structure
  114. *
  115. * @return void
  116. */
  117. private function extendSystemStructure()
  118. {
  119. $document = new \DOMDocument();
  120. $document->load(__DIR__ . '/../../_files/system.xml');
  121. $converter = $this->objectManager->get(Converter::class);
  122. $systemConfig = $converter->convert($document);
  123. $structureData = $this->objectManager->get(StructureData::class);
  124. $structureData->merge($systemConfig);
  125. }
  126. /**
  127. * @return array
  128. */
  129. private function loadConfig()
  130. {
  131. return $this->reader->load(ConfigFilePool::APP_ENV);
  132. }
  133. /**
  134. * Tests lockable flow.
  135. * Expects to save value and then error on saving duplicate value.
  136. *
  137. * @param string $path
  138. * @param string $value
  139. * @param string $scope
  140. * @param string $scopeCode
  141. * @magentoDbIsolation enabled
  142. * @dataProvider runLockDataProvider
  143. */
  144. public function testRunLockEnv($path, $value, $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, $scopeCode = null)
  145. {
  146. $this->inputMock->expects($this->any())
  147. ->method('getArgument')
  148. ->willReturnMap([
  149. [ConfigSetCommand::ARG_PATH, $path],
  150. [ConfigSetCommand::ARG_VALUE, $value]
  151. ]);
  152. $this->inputMock->expects($this->any())
  153. ->method('getOption')
  154. ->willReturnMap([
  155. [ConfigSetCommand::OPTION_LOCK_ENV, true],
  156. [ConfigSetCommand::OPTION_SCOPE, $scope],
  157. [ConfigSetCommand::OPTION_SCOPE_CODE, $scopeCode]
  158. ]);
  159. $this->outputMock->expects($this->exactly(2))
  160. ->method('writeln')
  161. ->withConsecutive(
  162. ['<info>Value was saved in app/etc/env.php and locked.</info>'],
  163. ['<info>Value was saved in app/etc/env.php and locked.</info>']
  164. );
  165. /** @var ConfigSetCommand $command */
  166. $command = $this->objectManager->create(ConfigSetCommand::class);
  167. /** @var ConfigPathResolver $resolver */
  168. $resolver = $this->objectManager->get(ConfigPathResolver::class);
  169. $status = $command->run($this->inputMock, $this->outputMock);
  170. $configPath = $resolver->resolve($path, $scope, $scopeCode, 'system');
  171. $this->assertSame(Cli::RETURN_SUCCESS, $status);
  172. $this->assertSame($value, $this->arrayManager->get($configPath, $this->loadConfig()));
  173. $status = $command->run($this->inputMock, $this->outputMock);
  174. $this->appConfig->reinit();
  175. $this->assertSame(Cli::RETURN_SUCCESS, $status);
  176. }
  177. /**
  178. * Retrieves variations with path, value, scope and scope code.
  179. *
  180. * @return array
  181. */
  182. public function runLockDataProvider()
  183. {
  184. return [
  185. ['general/region/display_all', '1'],
  186. ['general/region/state_required', 'BR,FR', ScopeInterface::SCOPE_WEBSITE, 'base'],
  187. ['admin/security/use_form_key', '0'],
  188. ['general/group/subgroup/field', 'default_value'],
  189. ['general/group/subgroup/field', 'website_value', ScopeInterface::SCOPE_WEBSITE, 'base'],
  190. ];
  191. }
  192. /**
  193. * Tests the extended flow.
  194. *
  195. * @param string $path
  196. * @param string $value
  197. * @param string $scope
  198. * @param string $scopeCode
  199. * @magentoDbIsolation enabled
  200. * @dataProvider runExtendedDataProvider
  201. */
  202. public function testRunExtended(
  203. $path,
  204. $value,
  205. $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
  206. $scopeCode = null
  207. ) {
  208. $arguments = [
  209. [ConfigSetCommand::ARG_PATH, $path],
  210. [ConfigSetCommand::ARG_VALUE, $value]
  211. ];
  212. $options = [
  213. [ConfigSetCommand::OPTION_SCOPE, $scope],
  214. [ConfigSetCommand::OPTION_SCOPE_CODE, $scopeCode]
  215. ];
  216. $optionsLock = array_merge($options, [[ConfigSetCommand::OPTION_LOCK_ENV, true]]);
  217. /** @var ConfigPathResolver $resolver */
  218. $resolver = $this->objectManager->get(ConfigPathResolver::class);
  219. /** @var array $configPath */
  220. $configPath = $resolver->resolve($path, $scope, $scopeCode, 'system');
  221. $this->runCommand($arguments, $options, '<info>Value was saved.</info>');
  222. $this->runCommand($arguments, $options, '<info>Value was saved.</info>');
  223. $this->assertSame(
  224. $value,
  225. $this->scopeConfig->getValue($path, $scope, $scopeCode)
  226. );
  227. $this->assertSame(null, $this->arrayManager->get($configPath, $this->loadConfig()));
  228. $this->runCommand($arguments, $optionsLock, '<info>Value was saved in app/etc/env.php and locked.</info>');
  229. $this->runCommand($arguments, $optionsLock, '<info>Value was saved in app/etc/env.php and locked.</info>');
  230. $this->assertSame($value, $this->arrayManager->get($configPath, $this->loadConfig()));
  231. }
  232. /**
  233. * Runs pre-configured command.
  234. *
  235. * @param array $arguments
  236. * @param array $options
  237. * @param string $expectedMessage
  238. * @param int $expectedCode
  239. */
  240. private function runCommand(
  241. array $arguments,
  242. array $options,
  243. $expectedMessage = '',
  244. $expectedCode = Cli::RETURN_SUCCESS
  245. ) {
  246. $input = clone $this->inputMock;
  247. $output = clone $this->outputMock;
  248. $input->expects($this->any())
  249. ->method('getArgument')
  250. ->willReturnMap($arguments);
  251. $input->expects($this->any())
  252. ->method('getOption')
  253. ->willReturnMap($options);
  254. $output->expects($this->once())
  255. ->method('writeln')
  256. ->with($expectedMessage);
  257. /** @var ConfigSetCommand $command */
  258. $command = $this->objectManager->create(ConfigSetCommand::class);
  259. $status = $command->run($input, $output);
  260. $this->appConfig->reinit();
  261. $this->assertSame($expectedCode, $status);
  262. }
  263. /**
  264. * Retrieves variations with path, value, scope and scope code.
  265. *
  266. * @return array
  267. */
  268. public function runExtendedDataProvider()
  269. {
  270. return $this->runLockDataProvider();
  271. }
  272. /**
  273. * @param string $path Config path
  274. * @param string $value Value of config is tried to be set
  275. * @param string $message Message command output
  276. * @param string $scope
  277. * @param $scopeCode string|null
  278. * @dataProvider configSetValidationErrorDataProvider
  279. * @magentoDbIsolation disabled
  280. */
  281. public function testConfigSetValidationError(
  282. $path,
  283. $value,
  284. $message,
  285. $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
  286. $scopeCode = null
  287. ) {
  288. $this->setConfigFailure($path, $value, $message, $scope, $scopeCode);
  289. }
  290. /**
  291. * Data provider for testConfigSetValidationError
  292. *
  293. * @return array
  294. */
  295. public function configSetValidationErrorDataProvider()
  296. {
  297. return [
  298. //wrong value for URL - checked by backend model of URL field
  299. [
  300. Custom::XML_PATH_UNSECURE_BASE_URL,
  301. 'value',
  302. 'Invalid Base URL. Value must be a URL or one of placeholders: {{base_url}}'
  303. ],
  304. //set not existed field path
  305. [
  306. 'test/test/test',
  307. 'value',
  308. 'The "test/test/test" path doesn\'t exist. Verify and try again.'
  309. ],
  310. //wrong scope or scope code
  311. [
  312. Custom::XML_PATH_GENERAL_LOCALE_CODE,
  313. 'en_UK',
  314. 'A scope is missing. Enter a scope and try again.',
  315. ''
  316. ],
  317. [
  318. Custom::XML_PATH_GENERAL_LOCALE_CODE,
  319. 'en_UK',
  320. 'A scope code is missing. Enter a code and try again.',
  321. ScopeInterface::SCOPE_WEBSITE
  322. ],
  323. [
  324. Custom::XML_PATH_GENERAL_LOCALE_CODE,
  325. 'en_UK',
  326. 'A scope code is missing. Enter a code and try again.',
  327. ScopeInterface::SCOPE_STORE
  328. ],
  329. [
  330. Custom::XML_PATH_GENERAL_LOCALE_CODE,
  331. 'en_UK',
  332. 'The "wrong_scope" value doesn\'t exist. Enter another value and try again.',
  333. 'wrong_scope',
  334. 'base'
  335. ],
  336. [
  337. Custom::XML_PATH_GENERAL_LOCALE_CODE,
  338. 'en_UK',
  339. 'The "wrong_website_code" value doesn\'t exist. Enter another value and try again.',
  340. ScopeInterface::SCOPE_WEBSITE,
  341. 'wrong_website_code'
  342. ],
  343. [
  344. Custom::XML_PATH_GENERAL_LOCALE_CODE,
  345. 'en_UK',
  346. 'The "wrong_store_code" value doesn\'t exist. Enter another value and try again.',
  347. ScopeInterface::SCOPE_STORE,
  348. 'wrong_store_code'
  349. ],
  350. [
  351. Currency::XML_PATH_CURRENCY_DEFAULT,
  352. 'GBP',
  353. 'Sorry, the default display currency you selected is not available in allowed currencies.'
  354. ],
  355. [
  356. Currency::XML_PATH_CURRENCY_ALLOW,
  357. 'GBP',
  358. 'Default display currency "US Dollar" is not available in allowed currencies.'
  359. ]
  360. ];
  361. }
  362. /**
  363. * Saving values with successful validation
  364. *
  365. * @magentoDbIsolation enabled
  366. */
  367. public function testConfigSetCurrency()
  368. {
  369. /**
  370. * Checking saving currency as they are depend on each other.
  371. * Default currency can not be changed to new value if this value does not exist in allowed currency
  372. * that is why allowed currency is changed first by adding necessary value,
  373. * then old value is removed after changing default currency
  374. */
  375. $this->setConfigSuccess(Currency::XML_PATH_CURRENCY_ALLOW, 'USD,GBP');
  376. $this->setConfigSuccess(Currency::XML_PATH_CURRENCY_DEFAULT, 'GBP');
  377. $this->setConfigSuccess(Currency::XML_PATH_CURRENCY_ALLOW, 'GBP');
  378. }
  379. /**
  380. * Saving values with successful validation
  381. *
  382. * @dataProvider configSetValidDataProvider
  383. * @magentoDbIsolation enabled
  384. */
  385. public function testConfigSetValid()
  386. {
  387. $this->setConfigSuccess(Custom::XML_PATH_UNSECURE_BASE_URL, 'http://magento2.local/');
  388. $this->setConfigSuccess(Custom::XML_PATH_GENERAL_LOCALE_CODE, 'en_UK', ScopeInterface::SCOPE_WEBSITE, 'base');
  389. $this->setConfigSuccess(Custom::XML_PATH_GENERAL_LOCALE_CODE, 'en_AU', ScopeInterface::SCOPE_STORE, 'default');
  390. }
  391. /**
  392. * Data provider for testConfigSetValid
  393. *
  394. * @return array
  395. */
  396. public function configSetValidDataProvider()
  397. {
  398. return [
  399. [Custom::XML_PATH_UNSECURE_BASE_URL, 'http://magento2.local/'],
  400. [Custom::XML_PATH_GENERAL_LOCALE_CODE, 'en_UK', ScopeInterface::SCOPE_WEBSITE, 'base'],
  401. [Custom::XML_PATH_GENERAL_LOCALE_CODE, 'en_AU', ScopeInterface::SCOPE_STORE, 'default'],
  402. [Custom::XML_PATH_ADMIN_SECURITY_USEFORMKEY, '0']
  403. ];
  404. }
  405. /**
  406. * Set configuration and check this value from DB with success message this command should display
  407. *
  408. * @param string $path Config path
  409. * @param string $value Value of config is tried to be set
  410. * @param string $scope
  411. * @param string|null $scopeCode
  412. */
  413. private function setConfigSuccess(
  414. $path,
  415. $value,
  416. $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
  417. $scopeCode = null
  418. ) {
  419. $status = $this->setConfig($path, $value, '<info>Value was saved.</info>', $scope, $scopeCode);
  420. $this->assertSame(Cli::RETURN_SUCCESS, $status);
  421. $this->assertSame(
  422. $value,
  423. $this->scopeConfig->getValue($path, $scope, $scopeCode)
  424. );
  425. }
  426. /**
  427. * Set configuration value with some error
  428. * and check that this value was not saved to DB and appropriate error message was displayed
  429. *
  430. * @param string $path Config path
  431. * @param string $value Value of config is tried to be set
  432. * @param string $message Message command output
  433. * @param string $scope
  434. * @param string|null $scopeCode
  435. */
  436. private function setConfigFailure(
  437. $path,
  438. $value,
  439. $message,
  440. $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
  441. $scopeCode = null
  442. ) {
  443. $status = $this->setConfig($path, $value, '<error>' . $message . '</error>', $scope, $scopeCode);
  444. $this->assertSame(Cli::RETURN_FAILURE, $status);
  445. $this->assertNotSame(
  446. $value,
  447. $this->scopeConfig->getValue($path),
  448. "Values are the same '$value' and '{$this->scopeConfig->getValue($path)}' for $path"
  449. );
  450. }
  451. /**
  452. * @param string $path Config path
  453. * @param string $value Value of config is tried to be set
  454. * @param string $message Message command output
  455. * @param string $scope
  456. * @param string|null $scopeCode
  457. * @return int Status that command returned
  458. */
  459. private function setConfig($path, $value, $message, $scope, $scopeCode)
  460. {
  461. $input = clone $this->inputMock;
  462. $output = clone $this->outputMock;
  463. $input->expects($this->any())
  464. ->method('getArgument')
  465. ->willReturnMap([
  466. [ConfigSetCommand::ARG_PATH, $path],
  467. [ConfigSetCommand::ARG_VALUE, $value]
  468. ]);
  469. $input->expects($this->any())
  470. ->method('getOption')
  471. ->willReturnMap([
  472. [ConfigSetCommand::OPTION_SCOPE, $scope],
  473. [ConfigSetCommand::OPTION_SCOPE_CODE, $scopeCode]
  474. ]);
  475. $output->expects($this->once())
  476. ->method('writeln')
  477. ->with($message);
  478. /** @var ConfigSetCommand $command */
  479. $command = $this->objectManager->create(ConfigSetCommand::class);
  480. $status = $command->run($input, $output);
  481. return $status;
  482. }
  483. }