WhitelistDeclarationTest.php 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. declare(strict_types=1);
  7. namespace Magento\Setup\Declaration;
  8. use Magento\Framework\App\ResourceConnection;
  9. use Magento\Framework\App\Utility\Files;
  10. use Magento\Framework\Component\ComponentRegistrar;
  11. use Magento\Framework\Component\ComponentRegistrarInterface;
  12. use Magento\Framework\ObjectManagerInterface;
  13. use Magento\Framework\Setup\Declaration\Schema\Dto\Constraint;
  14. use Magento\Framework\Setup\Declaration\Schema\Dto\Index;
  15. use Magento\Framework\Setup\Declaration\Schema\SchemaConfigInterface;
  16. use Magento\TestFramework\Helper\Bootstrap;
  17. use Magento\TestFramework\ObjectManager;
  18. /**
  19. * Class WhitelistDeclarationTest
  20. */
  21. class WhitelistDeclarationTest extends \PHPUnit\Framework\TestCase
  22. {
  23. /**
  24. * @var ComponentRegistrarInterface
  25. */
  26. private $componentRegistrar;
  27. /**
  28. * @var SchemaConfigInterface
  29. */
  30. private $schemaConfig;
  31. public function setUp()
  32. {
  33. /** @var ObjectManagerInterface|ObjectManager $objectManager */
  34. $objectManager = Bootstrap::getObjectManager();
  35. $resourceConnection = $objectManager->create(ResourceConnection::class);
  36. $objectManager->removeSharedInstance(ResourceConnection::class);
  37. $objectManager->addSharedInstance($resourceConnection, ResourceConnection::class);
  38. $this->componentRegistrar = $objectManager->get(ComponentRegistrarInterface::class);
  39. $this->schemaConfig = $objectManager->create(SchemaConfigInterface::class);
  40. }
  41. /**
  42. * Checks that all declared table elements also declared into whitelist declaration.
  43. *
  44. * @magentoAppIsolation enabled
  45. * @throws \Exception
  46. */
  47. public function testConstraintsAndIndexesAreWhitelisted()
  48. {
  49. $undeclaredElements = [];
  50. $resultMessage = "New table elements that do not exist in the whitelist declaration:\n";
  51. $whitelistTables = $this->getWhiteListTables();
  52. $declarativeSchema = $this->schemaConfig->getDeclarationConfig();
  53. foreach ($declarativeSchema->getTables() as $schemaTable) {
  54. $tableNameWithoutPrefix = $schemaTable->getNameWithoutPrefix();
  55. foreach ($schemaTable->getConstraints() as $constraint) {
  56. $constraintNameWithoutPrefix = $constraint->getNameWithoutPrefix();
  57. if (isset($whitelistTables[$tableNameWithoutPrefix][Constraint::TYPE][$constraintNameWithoutPrefix])) {
  58. continue;
  59. }
  60. $undeclaredElements[$tableNameWithoutPrefix][Constraint::TYPE][] = $constraintNameWithoutPrefix;
  61. }
  62. foreach ($schemaTable->getIndexes() as $index) {
  63. $indexNameWithoutPrefix = $index->getNameWithoutPrefix();
  64. if (isset($whitelistTables[$tableNameWithoutPrefix][Index::TYPE][$indexNameWithoutPrefix])) {
  65. continue;
  66. }
  67. $undeclaredElements[$tableNameWithoutPrefix][Index::TYPE][] = $indexNameWithoutPrefix;
  68. }
  69. }
  70. $undeclaredElements = $this->filterUndeclaredElements($undeclaredElements);
  71. if (!empty($undeclaredElements)) {
  72. $resultMessage .= json_encode($undeclaredElements, JSON_PRETTY_PRINT);
  73. }
  74. $this->assertEmpty($undeclaredElements, $resultMessage);
  75. }
  76. /**
  77. * Excludes ignored elements from the list of undeclared table elements.
  78. *
  79. * @param array $undeclaredElements
  80. * @return array
  81. */
  82. private function filterUndeclaredElements(array $undeclaredElements): array
  83. {
  84. $files = Files::getFiles([__DIR__ . '/_files/ignore_whitelisting'], '*.json');
  85. $ignoredElements = [];
  86. foreach ($files as $filePath) {
  87. $ignoredElements = array_merge_recursive(
  88. $ignoredElements,
  89. json_decode(file_get_contents($filePath), true)
  90. );
  91. }
  92. return $this->arrayRecursiveDiff($undeclaredElements, $ignoredElements);
  93. }
  94. /**
  95. * Performs a recursive comparison of two arrays.
  96. *
  97. * @param array $array1
  98. * @param array $array2
  99. * @return array
  100. */
  101. private function arrayRecursiveDiff(array $array1, array $array2): array
  102. {
  103. $diffResult = [];
  104. foreach ($array1 as $key => $value) {
  105. if (array_key_exists($key, $array2)) {
  106. if (is_array($value)) {
  107. $recursiveDiffResult = $this->arrayRecursiveDiff($value, $array2[$key]);
  108. if (count($recursiveDiffResult)) {
  109. $diffResult[$key] = $recursiveDiffResult;
  110. }
  111. } else {
  112. if (!in_array($value, $array2)) {
  113. $diffResult[] = $value;
  114. }
  115. }
  116. } else {
  117. $diffResult[$key] = $value;
  118. }
  119. }
  120. return $diffResult;
  121. }
  122. /**
  123. * @return array
  124. */
  125. private function getWhiteListTables(): array
  126. {
  127. $whiteListTables = [];
  128. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $path) {
  129. $whiteListPath = $path . DIRECTORY_SEPARATOR . 'etc' .
  130. DIRECTORY_SEPARATOR . 'db_schema_whitelist.json';
  131. if (file_exists($whiteListPath)) {
  132. $whiteListTables = array_replace_recursive(
  133. $whiteListTables,
  134. json_decode(file_get_contents($whiteListPath), true)
  135. );
  136. }
  137. }
  138. return $whiteListTables;
  139. }
  140. }