From 953b4ebfce0d661bbf338cf11055399752890a9b Mon Sep 17 00:00:00 2001 From: "Karpenko, Oleksandr" Date: Wed, 19 Jul 2017 12:58:47 +0300 Subject: [PATCH 01/82] MAGETWO-70571: There is no possibility to activate DEBUG logging in production mode --- .../Deploy/App/Mode/ConfigProvider.php | 58 ++++++++ app/code/Magento/Deploy/Model/Mode.php | 63 +++++++- .../Test/Unit/App/Mode/ConfigProviderTest.php | 27 ++++ .../Deploy/Test/Unit/Model/ModeTest.php | 81 ++++++++++- app/code/Magento/Deploy/etc/di.xml | 11 ++ .../Developer/Model/Logger/Handler/Debug.php | 1 - .../Unit/Model/Logger/Handler/DebugTest.php | 17 +-- .../Model/Logger/Handler/DebugTest.php | 136 ++++++++++++++++++ 8 files changed, 381 insertions(+), 13 deletions(-) create mode 100644 app/code/Magento/Deploy/App/Mode/ConfigProvider.php create mode 100644 app/code/Magento/Deploy/Test/Unit/App/Mode/ConfigProviderTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Developer/Model/Logger/Handler/DebugTest.php diff --git a/app/code/Magento/Deploy/App/Mode/ConfigProvider.php b/app/code/Magento/Deploy/App/Mode/ConfigProvider.php new file mode 100644 index 0000000000000..142e3fe819438 --- /dev/null +++ b/app/code/Magento/Deploy/App/Mode/ConfigProvider.php @@ -0,0 +1,58 @@ + [ + * 'production' => [ + * {{setting_path}} => {{setting_value}} + * ] + * ] + * ] + * + * @var array + */ + private $config; + + /** + * @param array $config + */ + public function __construct(array $config = []) + { + $this->config = $config; + } + + /** + * Provide configuration while switching from $currentMode to $targetMode + * This method used in \Magento\Deploy\Model\Mode::setStoreMode + * + * For example: while switching from developer mode to production mode + * need to turn off 'dev/debug/debug_logging' setting in this case method + * will return array + * [ + * {{setting_path}} => {{setting_value}} + * ] + * + * @param string $currentMode + * @param string $targetMode + * @return array + */ + public function getConfigs($currentMode, $targetMode) + { + if (isset($this->config[$currentMode][$targetMode])) { + return $this->config[$currentMode][$targetMode]; + } + return []; + } +} diff --git a/app/code/Magento/Deploy/Model/Mode.php b/app/code/Magento/Deploy/Model/Mode.php index 1e8b03585d2b8..fa8fb06d32b64 100644 --- a/app/code/Magento/Deploy/Model/Mode.php +++ b/app/code/Magento/Deploy/Model/Mode.php @@ -6,6 +6,8 @@ namespace Magento\Deploy\Model; +use Magento\Deploy\App\Mode\ConfigProvider; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\DeploymentConfig\Reader; use Magento\Framework\App\DeploymentConfig\Writer; use Magento\Framework\App\Filesystem\DirectoryList; @@ -14,6 +16,8 @@ use Magento\Framework\Config\File\ConfigFilePool; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Magento\Config\Console\Command\ConfigSet\ProcessorFacadeFactory; +use Magento\Config\Console\Command\EmulatedAdminhtmlAreaProcessor; /** * A class to manage Magento modes @@ -43,11 +47,35 @@ class Mode */ private $reader; + /** + * @var MaintenanceMode + */ + private $maintenanceMode; + /** * @var Filesystem */ private $filesystem; + /** + * @var ConfigProvider + */ + private $configProvider; + + /** + * The factory for processor facade. + * + * @var ProcessorFacadeFactory + */ + private $processorFacadeFactory; + + /** + * Emulator adminhtml area for CLI command. + * + * @var EmulatedAdminhtmlAreaProcessor + */ + private $emulatedAreaProcessor; + /** * @param InputInterface $input * @param OutputInterface $output @@ -55,6 +83,9 @@ class Mode * @param Reader $reader * @param MaintenanceMode $maintenanceMode * @param Filesystem $filesystem + * @param ConfigProvider $configProvider + * @param ProcessorFacadeFactory $processorFacadeFactory + * @param EmulatedAdminhtmlAreaProcessor $emulatedAreaProcessor */ public function __construct( InputInterface $input, @@ -62,7 +93,10 @@ public function __construct( Writer $writer, Reader $reader, MaintenanceMode $maintenanceMode, - Filesystem $filesystem + Filesystem $filesystem, + ConfigProvider $configProvider, + ProcessorFacadeFactory $processorFacadeFactory, + EmulatedAdminhtmlAreaProcessor $emulatedAreaProcessor ) { $this->input = $input; $this->output = $output; @@ -70,6 +104,9 @@ public function __construct( $this->reader = $reader; $this->maintenanceMode = $maintenanceMode; $this->filesystem = $filesystem; + $this->configProvider = $configProvider; + $this->processorFacadeFactory = $processorFacadeFactory; + $this->emulatedAreaProcessor = $emulatedAreaProcessor; } /** @@ -134,6 +171,7 @@ public function getMode() */ protected function setStoreMode($mode) { + $this->saveAppConfigs($mode); $data = [ ConfigFilePool::APP_ENV => [ State::PARAM_MODE => $mode @@ -142,6 +180,29 @@ protected function setStoreMode($mode) $this->writer->saveConfig($data); } + /** + * Save application configs while switching mode + * + * @param string $mode + * @return void + */ + private function saveAppConfigs($mode) + { + $configs = $this->configProvider->getConfigs($this->getMode(), $mode); + foreach ($configs as $path => $value) { + $this->emulatedAreaProcessor->process(function () use ($path, $value) { + $this->processorFacadeFactory->create()->process( + $path, + $value, + ScopeConfigInterface::SCOPE_TYPE_DEFAULT, + null, + true + ); + }); + $this->output->writeln('Config "' . $path . ' = ' . $value . '" has been saved.'); + } + } + /** * Enable maintenance mode * diff --git a/app/code/Magento/Deploy/Test/Unit/App/Mode/ConfigProviderTest.php b/app/code/Magento/Deploy/Test/Unit/App/Mode/ConfigProviderTest.php new file mode 100644 index 0000000000000..4c8e592978b7d --- /dev/null +++ b/app/code/Magento/Deploy/Test/Unit/App/Mode/ConfigProviderTest.php @@ -0,0 +1,27 @@ + '{{setting_value}}' + ]; + $configProvider = new ConfigProvider( + [ + 'developer' => [ + 'production' => $expectedValue + ] + ] + ); + $this->assertEquals($expectedValue, $configProvider->getConfigs('developer', 'production')); + $this->assertEquals([], $configProvider->getConfigs('undefined', 'production')); + } +} diff --git a/app/code/Magento/Deploy/Test/Unit/Model/ModeTest.php b/app/code/Magento/Deploy/Test/Unit/Model/ModeTest.php index e77e3c58ffba1..efe5d8166a051 100644 --- a/app/code/Magento/Deploy/Test/Unit/Model/ModeTest.php +++ b/app/code/Magento/Deploy/Test/Unit/Model/ModeTest.php @@ -5,8 +5,13 @@ */ namespace Magento\Deploy\Test\Unit\Model; +use Magento\Config\Console\Command\ConfigSet\ProcessorFacadeFactory; +use Magento\Config\Console\Command\ConfigSet\ProcessorFacade; +use Magento\Config\Console\Command\EmulatedAdminhtmlAreaProcessor; +use Magento\Deploy\App\Mode\ConfigProvider; use Magento\Deploy\Model\Filesystem; use Magento\Deploy\Model\Mode; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\DeploymentConfig\Reader; use Magento\Framework\App\DeploymentConfig\Writer; use Magento\Framework\App\MaintenanceMode; @@ -17,6 +22,7 @@ /** * @inheritdoc + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ModeTest extends \PHPUnit_Framework_TestCase { @@ -55,6 +61,26 @@ class ModeTest extends \PHPUnit_Framework_TestCase */ private $filesystemMock; + /** + * @var ConfigProvider|Mock + */ + private $configProvider; + + /** + * @var ProcessorFacadeFactory|Mock + */ + private $processorFacadeFactory; + + /** + * @var ProcessorFacade|Mock + */ + private $processorFacade; + + /** + * @var EmulatedAdminhtmlAreaProcessor|Mock + */ + private $emulatedAreaProcessor; + protected function setUp() { $this->inputMock = $this->getMockBuilder(InputInterface::class) @@ -73,6 +99,19 @@ protected function setUp() $this->filesystemMock = $this->getMockBuilder(Filesystem::class) ->disableOriginalConstructor() ->getMock(); + $this->configProvider = $this->getMockBuilder(ConfigProvider::class) + ->disableOriginalConstructor() + ->getMock(); + $this->processorFacadeFactory = $this->getMockBuilder(ProcessorFacadeFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMockForAbstractClass(); + $this->processorFacade = $this->getMockBuilder(ProcessorFacade::class) + ->disableOriginalConstructor() + ->getMock(); + $this->emulatedAreaProcessor = $this->getMockBuilder(EmulatedAdminhtmlAreaProcessor::class) + ->disableOriginalConstructor() + ->getMock(); $this->model = new Mode( $this->inputMock, @@ -80,7 +119,10 @@ protected function setUp() $this->writerMock, $this->readerMock, $this->maintenanceMock, - $this->filesystemMock + $this->filesystemMock, + $this->configProvider, + $this->processorFacadeFactory, + $this->emulatedAreaProcessor ); } @@ -96,4 +138,41 @@ public function testGetMode() $this->assertSame(null, $this->model->getMode()); $this->assertSame(State::MODE_DEVELOPER, $this->model->getMode()); } + + public function testEnableProductionModeMinimal() + { + $this->readerMock->expects($this->once()) + ->method('load') + ->willReturn([State::PARAM_MODE => State::MODE_DEVELOPER]); + $this->configProvider->expects($this->once()) + ->method('getConfigs') + ->with('developer', 'production') + ->willReturn([ + 'dev/debug/debug_logging' => 0 + ]); + $this->emulatedAreaProcessor->expects($this->once()) + ->method('process') + ->willReturnCallback(function (\Closure $closure) { + return $closure->call($this->model); + }); + + $this->processorFacadeFactory->expects($this->once()) + ->method('create') + ->willReturn($this->processorFacade); + $this->processorFacade + ->expects($this->once()) + ->method('process') + ->with( + 'dev/debug/debug_logging', + 0, + ScopeConfigInterface::SCOPE_TYPE_DEFAULT, + null, + true + ); + $this->outputMock->expects($this->once()) + ->method('writeln') + ->with('Config "dev/debug/debug_logging = 0" has been saved.'); + + $this->model->enableProductionModeMinimal(); + } } diff --git a/app/code/Magento/Deploy/etc/di.xml b/app/code/Magento/Deploy/etc/di.xml index 52a2503a13678..e47fca3a6b946 100644 --- a/app/code/Magento/Deploy/etc/di.xml +++ b/app/code/Magento/Deploy/etc/di.xml @@ -70,4 +70,15 @@ + + + + + + 0 + + + + + diff --git a/app/code/Magento/Developer/Model/Logger/Handler/Debug.php b/app/code/Magento/Developer/Model/Logger/Handler/Debug.php index ec5aff6891c11..9bfee42fa6a83 100644 --- a/app/code/Magento/Developer/Model/Logger/Handler/Debug.php +++ b/app/code/Magento/Developer/Model/Logger/Handler/Debug.php @@ -60,7 +60,6 @@ public function isHandling(array $record) if ($this->deploymentConfig->isAvailable()) { return parent::isHandling($record) - && $this->state->getMode() !== State::MODE_PRODUCTION && $this->scopeConfig->getValue('dev/debug/debug_logging', ScopeInterface::SCOPE_STORE); } diff --git a/app/code/Magento/Developer/Test/Unit/Model/Logger/Handler/DebugTest.php b/app/code/Magento/Developer/Test/Unit/Model/Logger/Handler/DebugTest.php index 20bc4e1f1b85e..1a3b470ec907c 100644 --- a/app/code/Magento/Developer/Test/Unit/Model/Logger/Handler/DebugTest.php +++ b/app/code/Magento/Developer/Test/Unit/Model/Logger/Handler/DebugTest.php @@ -85,9 +85,8 @@ public function testHandle() $this->deploymentConfigMock->expects($this->once()) ->method('isAvailable') ->willReturn(true); - $this->stateMock->expects($this->once()) - ->method('getMode') - ->willReturn(State::MODE_DEVELOPER); + $this->stateMock->expects($this->never()) + ->method('getMode'); $this->scopeConfigMock->expects($this->once()) ->method('getValue') ->with('dev/debug/debug_logging', ScopeInterface::SCOPE_STORE, null) @@ -101,10 +100,9 @@ public function testHandleDisabledByProduction() $this->deploymentConfigMock->expects($this->once()) ->method('isAvailable') ->willReturn(true); - $this->stateMock->expects($this->once()) - ->method('getMode') - ->willReturn(State::MODE_PRODUCTION); - $this->scopeConfigMock->expects($this->never()) + $this->stateMock->expects($this->never()) + ->method('getMode'); + $this->scopeConfigMock->expects($this->once()) ->method('getValue'); $this->assertFalse($this->model->isHandling(['formatted' => false, 'level' => Logger::DEBUG])); @@ -115,9 +113,8 @@ public function testHandleDisabledByConfig() $this->deploymentConfigMock->expects($this->once()) ->method('isAvailable') ->willReturn(true); - $this->stateMock->expects($this->once()) - ->method('getMode') - ->willReturn(State::MODE_DEVELOPER); + $this->stateMock->expects($this->never()) + ->method('getMode'); $this->scopeConfigMock->expects($this->once()) ->method('getValue') ->with('dev/debug/debug_logging', ScopeInterface::SCOPE_STORE, null) diff --git a/dev/tests/integration/testsuite/Magento/Developer/Model/Logger/Handler/DebugTest.php b/dev/tests/integration/testsuite/Magento/Developer/Model/Logger/Handler/DebugTest.php new file mode 100644 index 0000000000000..c80dde4bd0806 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Developer/Model/Logger/Handler/DebugTest.php @@ -0,0 +1,136 @@ +create(Filesystem::class); + $this->etcDirectory = $filesystem->getDirectoryWrite(DirectoryList::CONFIG); + $this->etcDirectory->copyFile('env.php', 'env.base.php'); + + $this->inputMock = $this->getMockBuilder(InputInterface::class) + ->getMockForAbstractClass(); + $this->outputMock = $this->getMockBuilder(OutputInterface::class) + ->getMockForAbstractClass(); + $this->logger = Bootstrap::getObjectManager()->get(Monolog::class); + $this->mode = Bootstrap::getObjectManager()->create( + Mode::class, + [ + 'input' => $this->inputMock, + 'output' => $this->outputMock + ] + ); + $this->configSetCommand = Bootstrap::getObjectManager()->create(ConfigSetCommand::class); + + // Preconditions + $this->mode->enableDeveloperMode(); + if (file_exists($this->getDebuggerLogPath())) { + unlink($this->getDebuggerLogPath()); + } + } + + public function tearDown() + { + $this->etcDirectory->delete('env.php'); + $this->etcDirectory->renameFile('env.base.php', 'env.php'); + } + + public function testDebugInProductionMode() + { + $message = 'test message'; + + $this->mode->enableProductionModeMinimal(); + + $this->logger->debug($message); + $this->assertFileNotExists($this->getDebuggerLogPath()); + + $this->inputMock = $this->getMockBuilder(InputInterface::class) + ->getMockForAbstractClass(); + $this->outputMock = $this->getMockBuilder(OutputInterface::class) + ->getMockForAbstractClass(); + $this->inputMock->expects($this->exactly(2)) + ->method('getArgument') + ->withConsecutive([ConfigSetCommand::ARG_PATH], [ConfigSetCommand::ARG_VALUE]) + ->willReturnOnConsecutiveCalls('dev/debug/debug_logging', 1); + $this->inputMock->expects($this->exactly(3)) + ->method('getOption') + ->withConsecutive( + [ConfigSetCommand::OPTION_SCOPE], + [ConfigSetCommand::OPTION_SCOPE_CODE], + [ConfigSetCommand::OPTION_LOCK] + ) + ->willReturnOnConsecutiveCalls( + ScopeConfigInterface::SCOPE_TYPE_DEFAULT, + null, + true + ); + $this->outputMock->expects($this->once()) + ->method('writeln') + ->with('Value was saved and locked.'); + $this->assertFalse((bool)$this->configSetCommand->run($this->inputMock, $this->outputMock)); + $this->logger->debug($message); + + $this->assertFileExists($this->getDebuggerLogPath()); + $this->assertContains($message, file_get_contents($this->getDebuggerLogPath())); + } + + /** + * @return bool|string + */ + private function getDebuggerLogPath() + { + foreach ($this->logger->getHandlers() as $handler) { + if ($handler instanceof Debug) { + return $handler->getUrl(); + } + } + return false; + } +} From 409997bfb8f179636a593b56c18d3525619c395b Mon Sep 17 00:00:00 2001 From: "Karpenko, Oleksandr" Date: Wed, 19 Jul 2017 17:02:38 +0300 Subject: [PATCH 02/82] MAGETWO-70571: There is no possibility to activate DEBUG logging in production mode --- .../Model/Logger/Handler/DebugTest.php | 46 +++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Developer/Model/Logger/Handler/DebugTest.php b/dev/tests/integration/testsuite/Magento/Developer/Model/Logger/Handler/DebugTest.php index c80dde4bd0806..4e51c24646d8e 100644 --- a/dev/tests/integration/testsuite/Magento/Developer/Model/Logger/Handler/DebugTest.php +++ b/dev/tests/integration/testsuite/Magento/Developer/Model/Logger/Handler/DebugTest.php @@ -6,6 +6,7 @@ namespace Magento\Developer\Model\Logger\Handler; use Magento\Config\Console\Command\ConfigSetCommand; +use Magento\Framework\App\Config; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Filesystem; @@ -16,6 +17,23 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +/** + * Preconditions + * - Developer mode enabled + * - Log file isn't exists + * - 'Log to file' setting are enabled + * + * Test steps + * - Enable production mode without compilation + * - Try to log message into log file + * - Assert that log file isn't exists + * - Assert that 'Log to file' setting are disabled + * + * - Enable 'Log to file' setting + * - Try to log message into debug file + * - Assert that log file is exists + * - Assert that log file contain logged message + */ class DebugTest extends \PHPUnit_Framework_TestCase { /** @@ -48,6 +66,11 @@ class DebugTest extends \PHPUnit_Framework_TestCase */ private $etcDirectory; + /** + * @var Config + */ + private $appConfig; + public function setUp() { /** @var Filesystem $filesystem */ @@ -68,9 +91,11 @@ public function setUp() ] ); $this->configSetCommand = Bootstrap::getObjectManager()->create(ConfigSetCommand::class); + $this->appConfig = Bootstrap::getObjectManager()->create(Config::class); // Preconditions $this->mode->enableDeveloperMode(); + $this->enableDebugging(); if (file_exists($this->getDebuggerLogPath())) { unlink($this->getDebuggerLogPath()); } @@ -82,15 +107,8 @@ public function tearDown() $this->etcDirectory->renameFile('env.base.php', 'env.php'); } - public function testDebugInProductionMode() + private function enableDebugging() { - $message = 'test message'; - - $this->mode->enableProductionModeMinimal(); - - $this->logger->debug($message); - $this->assertFileNotExists($this->getDebuggerLogPath()); - $this->inputMock = $this->getMockBuilder(InputInterface::class) ->getMockForAbstractClass(); $this->outputMock = $this->getMockBuilder(OutputInterface::class) @@ -115,6 +133,18 @@ public function testDebugInProductionMode() ->method('writeln') ->with('Value was saved and locked.'); $this->assertFalse((bool)$this->configSetCommand->run($this->inputMock, $this->outputMock)); + } + + public function testDebugInProductionMode() + { + $message = 'test message'; + + $this->mode->enableProductionModeMinimal(); + $this->logger->debug($message); + $this->assertFileNotExists($this->getDebuggerLogPath()); + $this->assertFalse((bool)$this->appConfig->getValue('dev/debug/debug_logging')); + + $this->enableDebugging(); $this->logger->debug($message); $this->assertFileExists($this->getDebuggerLogPath()); From 37716e728b605f4f6d3ef39aacf4d3846f7825de Mon Sep 17 00:00:00 2001 From: Ievgen Kolesov Date: Fri, 21 Jul 2017 17:19:05 +0300 Subject: [PATCH 03/82] MAGETWO-70806: The Recently Viewed widget UI issue --- .../frontend/templates/product/widget/viewed/sidebar.phtml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/widget/viewed/sidebar.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/widget/viewed/sidebar.phtml index 456032ebcbc24..1c4ad3105a2b5 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/widget/viewed/sidebar.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/widget/viewed/sidebar.phtml @@ -17,10 +17,8 @@ 'listing' => [ 'displayMode' => 'grid' ], - 'column' => [ - 'image' => [ - 'imageCode' => 'recently_viewed_products_images_names_widget' - ] + 'image' => [ + 'imageCode' => 'recently_viewed_products_images_names_widget' ] ] ); From 3cee8536ac776f5e996fcdba737a7785f4e80565 Mon Sep 17 00:00:00 2001 From: Alex Paliarush Date: Mon, 24 Jul 2017 14:16:53 -0500 Subject: [PATCH 04/82] MAGETWO-70816: [Cloud] Unable to get WSDL listing --- .../Api/Data/DesignConfigDataInterface.php | 4 +- .../ServiceDataAttributesGenerator.php | 4 +- .../Module/Di/Code/Scanner/PhpScanner.php | 76 +++++++++++++------ .../Module/Di/Code/Scanner/PhpScannerTest.php | 23 ++++-- .../SomeModule/Api/Data/SomeInterface.php | 15 ++++ 5 files changed, 88 insertions(+), 34 deletions(-) create mode 100644 setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/code/Magento/SomeModule/Api/Data/SomeInterface.php diff --git a/app/code/Magento/Theme/Api/Data/DesignConfigDataInterface.php b/app/code/Magento/Theme/Api/Data/DesignConfigDataInterface.php index 54dce9803a4a6..97dfe37eeaf79 100644 --- a/app/code/Magento/Theme/Api/Data/DesignConfigDataInterface.php +++ b/app/code/Magento/Theme/Api/Data/DesignConfigDataInterface.php @@ -57,14 +57,14 @@ public function setFieldConfig(array $config); /** * Retrieve existing extension attributes object or create a new one. * - * @return DesignConfigDataExtensionInterface|null + * @return \Magento\Theme\Api\Data\DesignConfigDataExtensionInterface|null */ public function getExtensionAttributes(); /** * Set an extension attributes object. * - * @param DesignConfigDataExtensionInterface $extensionAttributes + * @param \Magento\Theme\Api\Data\DesignConfigDataExtensionInterface $extensionAttributes * @return $this */ public function setExtensionAttributes(DesignConfigDataExtensionInterface $extensionAttributes); diff --git a/setup/src/Magento/Setup/Module/Di/App/Task/Operation/ServiceDataAttributesGenerator.php b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/ServiceDataAttributesGenerator.php index 21cf8797f8340..e2bd543ccc77c 100644 --- a/setup/src/Magento/Setup/Module/Di/App/Task/Operation/ServiceDataAttributesGenerator.php +++ b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/ServiceDataAttributesGenerator.php @@ -54,8 +54,8 @@ public function __construct( public function doOperation() { $files = $this->configurationScanner->scan('extension_attributes.xml'); - $repositories = $this->serviceDataAttributesScanner->collectEntities($files); - foreach ($repositories as $entityName) { + $entities = $this->serviceDataAttributesScanner->collectEntities($files); + foreach ($entities as $entityName) { class_exists($entityName); } } diff --git a/setup/src/Magento/Setup/Module/Di/Code/Scanner/PhpScanner.php b/setup/src/Magento/Setup/Module/Di/Code/Scanner/PhpScanner.php index 8c1d6565cd668..ab66a63e24498 100644 --- a/setup/src/Magento/Setup/Module/Di/Code/Scanner/PhpScanner.php +++ b/setup/src/Magento/Setup/Module/Di/Code/Scanner/PhpScanner.php @@ -9,6 +9,7 @@ use Magento\Framework\Api\Code\Generator\ExtensionAttributesInterfaceGenerator; use Magento\Framework\ObjectManager\Code\Generator\Factory as FactoryGenerator; use Magento\Setup\Module\Di\Compiler\Log\Log; +use \Magento\Framework\Reflection\TypeProcessor; class PhpScanner implements ScannerInterface { @@ -18,11 +19,21 @@ class PhpScanner implements ScannerInterface protected $_log; /** + * @var TypeProcessor + */ + private $typeProcessor; + + /** + * Initialize dependencies. + * * @param Log $log + * @param TypeProcessor|null $typeProcessor */ - public function __construct(Log $log) + public function __construct(Log $log, TypeProcessor $typeProcessor = null) { $this->_log = $log; + $this->typeProcessor = $typeProcessor + ?: \Magento\Framework\App\ObjectManager::getInstance()->get(TypeProcessor::class); } /** @@ -45,22 +56,9 @@ protected function _findMissingClasses($file, $classReflection, $methodName, $en preg_match('/\[\s\<\w+?>\s([\w\\\\]+)/s', $parameter->__toString(), $matches); if (isset($matches[1]) && substr($matches[1], -strlen($entityType)) == $entityType) { $missingClassName = $matches[1]; - try { - if (class_exists($missingClassName)) { - continue; - } - } catch (\RuntimeException $e) { - } - $sourceClassName = $this->getSourceClassName($missingClassName, $entityType); - if (!class_exists($sourceClassName) && !interface_exists($sourceClassName)) { - $this->_log->add( - Log::CONFIGURATION_ERROR, - $missingClassName, - "Invalid {$entityType} for nonexistent class {$sourceClassName} in file {$file}" - ); - continue; + if ($this->shouldGenerateClass($missingClassName, $entityType, $file)) { + $missingClasses[] = $missingClassName; } - $missingClasses[] = $missingClassName; } } } @@ -137,12 +135,18 @@ protected function _fetchFactories($reflectionClass, $file) */ protected function _fetchMissingExtensionAttributesClasses($reflectionClass, $file) { - $missingExtensionInterfaces = $this->_findMissingClasses( - $file, - $reflectionClass, - 'setExtensionAttributes', - ucfirst(\Magento\Framework\Api\Code\Generator\ExtensionAttributesInterfaceGenerator::ENTITY_TYPE) - ); + $missingExtensionInterfaces = []; + $methodName = 'getExtensionAttributes'; + $entityType = ucfirst(\Magento\Framework\Api\Code\Generator\ExtensionAttributesInterfaceGenerator::ENTITY_TYPE); + if ($reflectionClass->hasMethod($methodName) && $reflectionClass->isInterface()) { + $returnType = $this->typeProcessor->getGetterReturnType( + (new \Zend\Code\Reflection\ClassReflection($reflectionClass->getName()))->getMethod($methodName) + ); + $missingClassName = $returnType['type']; + if ($this->shouldGenerateClass($missingClassName, $entityType, $file)) { + $missingExtensionInterfaces[] = $missingClassName; + } + } $missingExtensionClasses = []; $missingExtensionFactories = []; foreach ($missingExtensionInterfaces as $missingExtensionInterface) { @@ -244,4 +248,32 @@ protected function _getDeclaredClasses($file) } return array_unique($classes); } + + /** + * Check if specified class is missing and if it can be generated. + * + * @param string $missingClassName + * @param string $entityType + * @param string $file + * @return bool + */ + private function shouldGenerateClass($missingClassName, $entityType, $file) + { + try { + if (class_exists($missingClassName)) { + return false; + } + } catch (\RuntimeException $e) { + } + $sourceClassName = $this->getSourceClassName($missingClassName, $entityType); + if (!class_exists($sourceClassName) && !interface_exists($sourceClassName)) { + $this->_log->add( + Log::CONFIGURATION_ERROR, + $missingClassName, + "Invalid {$entityType} for nonexistent class {$sourceClassName} in file {$file}" + ); + return false; + } + return true; + } } diff --git a/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Scanner/PhpScannerTest.php b/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Scanner/PhpScannerTest.php index d526ead41c2b0..64f8d83565450 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Scanner/PhpScannerTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Scanner/PhpScannerTest.php @@ -8,6 +8,9 @@ require_once __DIR__ . '/../../_files/app/code/Magento/SomeModule/Helper/Test.php'; require_once __DIR__ . '/../../_files/app/code/Magento/SomeModule/ElementFactory.php'; require_once __DIR__ . '/../../_files/app/code/Magento/SomeModule/Model/DoubleColon.php'; +require_once __DIR__ . '/../../_files/app/code/Magento/SomeModule/Api/Data/SomeInterface.php'; + +use Magento\Framework\Reflection\TypeProcessor; class PhpScannerTest extends \PHPUnit_Framework_TestCase { @@ -33,18 +36,19 @@ class PhpScannerTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $this->_model = new \Magento\Setup\Module\Di\Code\Scanner\PhpScanner( - $this->_logMock = $this->getMock(\Magento\Setup\Module\Di\Compiler\Log\Log::class, [], [], '', false) - ); + $this->_logMock = $this->getMock(\Magento\Setup\Module\Di\Compiler\Log\Log::class, [], [], '', false); + $this->_model = new \Magento\Setup\Module\Di\Code\Scanner\PhpScanner($this->_logMock, new TypeProcessor()); $this->_testDir = str_replace('\\', '/', realpath(__DIR__ . '/../../') . '/_files'); - $this->_testFiles = [ - $this->_testDir . '/app/code/Magento/SomeModule/Helper/Test.php', - $this->_testDir . '/app/code/Magento/SomeModule/Model/DoubleColon.php' - ]; } public function testCollectEntities() { + $this->_testFiles = [ + $this->_testDir . '/app/code/Magento/SomeModule/Helper/Test.php', + $this->_testDir . '/app/code/Magento/SomeModule/Model/DoubleColon.php', + $this->_testDir . '/app/code/Magento/SomeModule/Api/Data/SomeInterface.php' + ]; + $this->_logMock->expects( $this->at(0) )->method( @@ -64,6 +68,9 @@ public function testCollectEntities() 'Invalid Factory declaration for class Magento\SomeModule\Element in file ' . $this->_testFiles[0] ); - $this->assertEquals([], $this->_model->collectEntities($this->_testFiles)); + $this->assertEquals( + ['\Magento\Eav\Api\Data\AttributeExtensionInterface'], + $this->_model->collectEntities($this->_testFiles) + ); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/code/Magento/SomeModule/Api/Data/SomeInterface.php b/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/code/Magento/SomeModule/Api/Data/SomeInterface.php new file mode 100644 index 0000000000000..0e2cbcfb8d827 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/code/Magento/SomeModule/Api/Data/SomeInterface.php @@ -0,0 +1,15 @@ + Date: Fri, 28 Jul 2017 13:54:59 +0300 Subject: [PATCH 05/82] MAGETWO-70846: Whitespace is missing after address 1 field in customer address in checkout --- .../web/template/shipping-address/address-renderer/default.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html b/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html index f9400227fae09..4f864ae2567a3 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html @@ -7,7 +7,7 @@

-
+ ,

From b769265599bbddcecc8324827611d42c2fe39f1a Mon Sep 17 00:00:00 2001 From: mmikhnevych Date: Fri, 28 Jul 2017 14:47:33 +0300 Subject: [PATCH 06/82] MAGETWO-70846: Whitespace is missing after address 1 field in customer address in checkout --- .../web/template/shipping-address/address-renderer/default.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html b/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html index 4f864ae2567a3..2e268461d1eea 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html @@ -7,7 +7,7 @@

- +
,

From 4656654a53c42f1f618d3c0c708e09bb1f99ccf9 Mon Sep 17 00:00:00 2001 From: serhii balko Date: Tue, 1 Aug 2017 18:44:56 +0300 Subject: [PATCH 07/82] MAGETWO-54456: Random FAT test fails CatalogRuleStaging_Create_ForNewCustomerGroup_ApplyAsPercentage --- .../Promo/Catalog/Edit/PromoForm.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Block/Adminhtml/Promo/Catalog/Edit/PromoForm.php b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Block/Adminhtml/Promo/Catalog/Edit/PromoForm.php index 51ab302cdcc72..01e918b3ebb4a 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Block/Adminhtml/Promo/Catalog/Edit/PromoForm.php +++ b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Block/Adminhtml/Promo/Catalog/Edit/PromoForm.php @@ -15,6 +15,13 @@ */ class PromoForm extends FormSections { + /** + * Magento form loader. + * + * @var string + */ + protected $spinner = '[data-role="spinner"]'; + /** * Fill form with tabs. * @@ -25,6 +32,7 @@ class PromoForm extends FormSections */ public function fill(FixtureInterface $fixture, SimpleElement $element = null, array $replace = null) { + $this->waitPageToLoad(); $sections = $this->getFixtureFieldsByContainers($fixture); if ($replace) { $sections = $this->prepareData($sections, $replace); @@ -55,4 +63,15 @@ protected function prepareData(array $tabs, array $replace) return $tabs; } + + /** + * Wait page to load. + * + * @return void + */ + protected function waitPageToLoad() + { + $this->waitForElementVisible($this->header); + $this->waitForElementNotVisible($this->spinner); + } } From 99bebb5c46444ad950d075061492c78ba8160f5e Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky Date: Wed, 2 Aug 2017 15:46:49 +0300 Subject: [PATCH 08/82] MAGETWO-69967: Double re-indexation of configurable product --- .../CatalogInventory/Model/Stock/StockItemRepository.php | 2 -- .../Test/Unit/Model/Stock/StockItemRepositoryTest.php | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php b/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php index 04e15cf07c2ce..a3903a45c06b6 100644 --- a/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php +++ b/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php @@ -181,8 +181,6 @@ public function save(\Magento\CatalogInventory\Api\Data\StockItemInterface $stoc $stockItem->setStockId($stockItem->getStockId()); $this->resource->save($stockItem); - - $this->indexProcessor->reindexRow($stockItem->getProductId()); } catch (\Exception $exception) { throw new CouldNotSaveException(__('Unable to save Stock Item'), $exception); } diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php index 5a667d8760d10..750d83f1d2748 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php @@ -314,7 +314,7 @@ public function testSave() ->method('save') ->with($this->stockItemMock) ->willReturnSelf(); - $this->indexProcessorMock->expects($this->once())->method('reindexRow')->with($productId); + $this->indexProcessorMock->expects($this->never())->method('reindexRow')->with($productId); $this->assertEquals($this->stockItemMock, $this->model->save($this->stockItemMock)); } From e019a50681ce42aa21cd17b1c1fdf7a362650fce Mon Sep 17 00:00:00 2001 From: Max Lesechko Date: Wed, 2 Aug 2017 15:53:12 +0300 Subject: [PATCH 09/82] MAGETWO-69559: Declaration of dependencies for indexers --- .../Command/AbstractIndexerCommand.php | 16 +- .../Command/AbstractIndexerManageCommand.php | 69 +-- .../Console/Command/IndexerReindexCommand.php | 170 +++++-- .../Indexer/Cron/ReindexAllInvalid.php | 2 - app/code/Magento/Indexer/Model/Indexer.php | 1 - .../Indexer/Model/Indexer/Collection.php | 155 +++++- .../Model/Indexer/DependencyDecorator.php | 271 +++++++++++ app/code/Magento/Indexer/Model/Processor.php | 7 +- .../Magento/Indexer/Setup/RecurringData.php | 8 +- .../AbstractIndexerCommandCommonSetup.php | 58 ++- .../Command/IndexerInfoCommandTest.php | 12 +- .../Command/IndexerReindexCommandTest.php | 454 ++++++++++++++---- .../Command/IndexerResetStateCommandTest.php | 19 +- .../Command/IndexerSetModeCommandTest.php | 48 +- .../Command/IndexerShowModeCommandTest.php | 85 +++- .../Command/IndexerStatusCommandTest.php | 82 ++-- .../Unit/Model/Indexer/CollectionTest.php | 368 +++++++++++--- .../Model/Indexer/DependencyDecoratorTest.php | 263 ++++++++++ .../Indexer/Test/Unit/Model/ProcessorTest.php | 14 +- .../Indexer/DataCollectionTest.php | 171 +++++++ .../DataProvider/Indexer/DataCollection.php | 61 +++ app/code/Magento/Indexer/etc/di.xml | 8 +- .../layout/indexer_indexer_list_grid.xml | 7 +- .../Indexer/Model/Config/ConverterTest.php | 31 ++ .../dependency_on_not_existing_indexer.xml | 19 + .../Indexer/Model/Config/_files/indexer.xml | 16 +- .../indexer_with_circular_dependency.xml | 51 ++ .../Indexer/Model/Config/_files/result.php | 34 ++ .../Legacy/_files/blacklist/obsolete_mage.php | 1 + .../Framework/Indexer/Config/Converter.php | 113 +++++ .../Indexer/Config/DependencyInfoProvider.php | 85 ++++ .../DependencyInfoProviderInterface.php | 33 ++ .../Test/Unit/Config/ConverterTest.php | 69 +++ .../Config/DependencyInfoProviderTest.php | 292 +++++++++++ .../Test/Unit/_files/indexer_config.php | 49 +- .../Magento/Framework/Indexer/etc/indexer.xsd | 56 ++- .../Framework/Indexer/etc/indexer_merged.xsd | 67 ++- .../Mview/Test/Unit/View/CollectionTest.php | 314 +++++++++--- .../Framework/Mview/View/Collection.php | 37 +- 39 files changed, 3134 insertions(+), 482 deletions(-) create mode 100644 app/code/Magento/Indexer/Model/Indexer/DependencyDecorator.php create mode 100644 app/code/Magento/Indexer/Test/Unit/Model/Indexer/DependencyDecoratorTest.php create mode 100644 app/code/Magento/Indexer/Test/Unit/Ui/DataProvider/Indexer/DataCollectionTest.php create mode 100644 app/code/Magento/Indexer/Ui/DataProvider/Indexer/DataCollection.php create mode 100644 dev/tests/integration/testsuite/Magento/Indexer/Model/Config/_files/dependency_on_not_existing_indexer.xml create mode 100644 dev/tests/integration/testsuite/Magento/Indexer/Model/Config/_files/indexer_with_circular_dependency.xml create mode 100644 lib/internal/Magento/Framework/Indexer/Config/DependencyInfoProvider.php create mode 100644 lib/internal/Magento/Framework/Indexer/Config/DependencyInfoProviderInterface.php create mode 100644 lib/internal/Magento/Framework/Indexer/Test/Unit/Config/DependencyInfoProviderTest.php diff --git a/app/code/Magento/Indexer/Console/Command/AbstractIndexerCommand.php b/app/code/Magento/Indexer/Console/Command/AbstractIndexerCommand.php index 82dca47784701..230a5507d1df9 100644 --- a/app/code/Magento/Indexer/Console/Command/AbstractIndexerCommand.php +++ b/app/code/Magento/Indexer/Console/Command/AbstractIndexerCommand.php @@ -6,8 +6,6 @@ namespace Magento\Indexer\Console\Command; use Magento\Backend\App\Area\FrontNameResolver; -use Magento\Framework\App\ObjectManager; -use Magento\Framework\App\ObjectManager\ConfigLoader; use Magento\Framework\ObjectManagerInterface; use Symfony\Component\Console\Command\Command; use Magento\Framework\Indexer\IndexerInterface; @@ -54,14 +52,24 @@ public function __construct( } /** - * Get all indexers + * Return the array of all indexers with keys as indexer ids. * * @return IndexerInterface[] * @since 2.0.0 */ protected function getAllIndexers() { - return $this->getCollectionFactory()->create()->getItems(); + $indexers = $this->getCollectionFactory()->create()->getItems(); + return array_combine( + array_map( + function ($item) { + /** @var IndexerInterface $item */ + return $item->getId(); + }, + $indexers + ), + $indexers + ); } /** diff --git a/app/code/Magento/Indexer/Console/Command/AbstractIndexerManageCommand.php b/app/code/Magento/Indexer/Console/Command/AbstractIndexerManageCommand.php index 28ec3101498c4..0530bf54ff1cf 100644 --- a/app/code/Magento/Indexer/Console/Command/AbstractIndexerManageCommand.php +++ b/app/code/Magento/Indexer/Console/Command/AbstractIndexerManageCommand.php @@ -5,13 +5,9 @@ */ namespace Magento\Indexer\Console\Command; +use Magento\Framework\Indexer\IndexerInterface; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; -use Magento\Framework\Indexer\IndexerInterface; -use Magento\Framework\App\ObjectManagerFactory; -use Magento\Indexer\Model\IndexerFactory; /** * An Abstract class for all Indexer related commands. @@ -25,28 +21,7 @@ abstract class AbstractIndexerManageCommand extends AbstractIndexerCommand const INPUT_KEY_INDEXERS = 'index'; /** - * @var IndexerFactory - * @since 2.2.0 - */ - private $indexerFactory; - - /** - * Constructor - * - * @param ObjectManagerFactory $objectManagerFactory - * @param IndexerFactory|null $indexerFactory - * @since 2.2.0 - */ - public function __construct( - ObjectManagerFactory $objectManagerFactory, - IndexerFactory $indexerFactory = null - ) { - parent::__construct($objectManagerFactory); - $this->indexerFactory = $indexerFactory; - } - - /** - * Gets list of indexers + * Returns the ordered list of indexers. * * @param InputInterface $input * @return IndexerInterface[] @@ -60,32 +35,21 @@ protected function getIndexers(InputInterface $input) $requestedTypes = $input->getArgument(self::INPUT_KEY_INDEXERS); $requestedTypes = array_filter(array_map('trim', $requestedTypes), 'strlen'); } + if (empty($requestedTypes)) { - return $this->getAllIndexers(); + $indexers = $this->getAllIndexers(); } else { - $indexers = []; - $unsupportedTypes = []; - foreach ($requestedTypes as $code) { - $indexer = $this->getIndexerFactory()->create(); - try { - $indexer->load($code); - $indexers[] = $indexer; - } catch (\Exception $e) { - $unsupportedTypes[] = $code; - } - } + $availableIndexers = $this->getAllIndexers(); + $unsupportedTypes = array_diff($requestedTypes, array_keys($availableIndexers)); if ($unsupportedTypes) { - $availableTypes = []; - $indexers = $this->getAllIndexers(); - foreach ($indexers as $indexer) { - $availableTypes[] = $indexer->getId(); - } throw new \InvalidArgumentException( "The following requested index types are not supported: '" . join("', '", $unsupportedTypes) - . "'." . PHP_EOL . 'Supported types: ' . join(", ", $availableTypes) + . "'." . PHP_EOL . 'Supported types: ' . join(", ", array_keys($availableIndexers)) ); } + $indexers = array_intersect_key($availableIndexers, array_flip($requestedTypes)); } + return $indexers; } @@ -105,19 +69,4 @@ public function getInputList() ), ]; } - - /** - * Get indexer factory - * - * @return IndexerFactory - * @deprecated 2.2.0 - * @since 2.2.0 - */ - private function getIndexerFactory() - { - if (null === $this->indexerFactory) { - $this->indexerFactory = $this->getObjectManager()->get(IndexerFactory::class); - } - return $this->indexerFactory; - } } diff --git a/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php index 3330ad9b26b67..9213add4cfb8c 100644 --- a/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php +++ b/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php @@ -5,13 +5,16 @@ */ namespace Magento\Indexer\Console\Command; +use Magento\Framework\Console\Cli; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Indexer\Config\DependencyInfoProvider; +use Magento\Framework\Indexer\IndexerInterface; +use Magento\Framework\Indexer\IndexerRegistry; use Magento\Framework\Indexer\StateInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Magento\Framework\Indexer\ConfigInterface; use Magento\Framework\App\ObjectManagerFactory; -use Magento\Indexer\Model\IndexerFactory; /** * Command to run indexers @@ -26,30 +29,37 @@ class IndexerReindexCommand extends AbstractIndexerManageCommand private $sharedIndexesComplete = []; /** - * @var \Magento\Framework\Indexer\ConfigInterface + * @var ConfigInterface * @since 2.1.0 */ private $config; /** - * @var IndexerFactory + * @var IndexerRegistry * @since 2.2.0 */ - private $indexerFactory; + private $indexerRegistry; + + /** + * @var DependencyInfoProvider|null + * @since 2.2.0 + */ + private $dependencyInfoProvider; /** - * Constructor - * * @param ObjectManagerFactory $objectManagerFactory - * @param IndexerFactory|null $indexerFactory + * @param IndexerRegistry|null $indexerRegistry + * @param DependencyInfoProvider|null $dependencyInfoProvider * @since 2.2.0 */ public function __construct( ObjectManagerFactory $objectManagerFactory, - IndexerFactory $indexerFactory = null + IndexerRegistry $indexerRegistry = null, + DependencyInfoProvider $dependencyInfoProvider = null ) { - parent::__construct($objectManagerFactory, $indexerFactory); - $this->indexerFactory = $indexerFactory; + $this->indexerRegistry = $indexerRegistry; + $this->dependencyInfoProvider = $dependencyInfoProvider; + parent::__construct($objectManagerFactory); } /** @@ -71,9 +81,8 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $indexers = $this->getIndexers($input); - $returnValue = \Magento\Framework\Console\Cli::RETURN_SUCCESS; - foreach ($indexers as $indexer) { + $returnValue = Cli::RETURN_FAILURE; + foreach ($this->getIndexers($input) as $indexer) { try { $this->validateIndexerStatus($indexer); $startTime = microtime(true); @@ -91,31 +100,119 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln( $indexer->getTitle() . ' index has been rebuilt successfully in ' . gmdate('H:i:s', $resultTime) ); + $returnValue = Cli::RETURN_SUCCESS; } catch (LocalizedException $e) { $output->writeln($e->getMessage()); - // we must have an exit code higher than zero to indicate something was wrong - $returnValue = \Magento\Framework\Console\Cli::RETURN_FAILURE; } catch (\Exception $e) { $output->writeln($indexer->getTitle() . ' indexer process unknown error:'); $output->writeln($e->getMessage()); - // we must have an exit code higher than zero to indicate something was wrong - $returnValue = \Magento\Framework\Console\Cli::RETURN_FAILURE; } } return $returnValue; } + /** + * {@inheritdoc} Returns the ordered list of specified indexers and related indexers. + * @since 2.2.0 + */ + protected function getIndexers(InputInterface $input) + { + $indexers = parent::getIndexers($input); + $allIndexers = $this->getAllIndexers(); + if (!array_diff_key($allIndexers, $indexers)) { + return $indexers; + } + + $relatedIndexers = []; + $dependentIndexers = []; + foreach ($indexers as $indexer) { + $relatedIndexers = array_merge( + $relatedIndexers, + $this->getRelatedIndexerIds($indexer->getId()) + ); + $dependentIndexers = array_merge( + $dependentIndexers, + $this->getDependentIndexerIds($indexer->getId()) + ); + } + + $invalidRelatedIndexers = []; + foreach (array_unique($relatedIndexers) as $relatedIndexer) { + if ($allIndexers[$relatedIndexer]->isInvalid()) { + $invalidRelatedIndexers[] = $relatedIndexer; + } + } + + return array_intersect_key( + $allIndexers, + array_flip( + array_unique( + array_merge( + array_keys($indexers), + $invalidRelatedIndexers, + $dependentIndexers + ) + ) + ) + ); + } + + /** + * Return all indexer Ids on which the current indexer depends (directly or indirectly). + * + * @param string $indexerId + * @return array + * @since 2.2.0 + */ + private function getRelatedIndexerIds(string $indexerId) + { + $relatedIndexerIds = []; + foreach ($this->getDependencyInfoProvider()->getIndexerIdsToRunBefore($indexerId) as $relatedIndexerId) { + $relatedIndexerIds = array_merge( + $relatedIndexerIds, + [$relatedIndexerId], + $this->getRelatedIndexerIds($relatedIndexerId) + ); + } + + return array_unique($relatedIndexerIds); + } + + /** + * Return all indexer Ids which depend on the current indexer (directly or indirectly). + * + * @param string $indexerId + * @return array + * @since 2.2.0 + */ + private function getDependentIndexerIds(string $indexerId) + { + $dependentIndexerIds = []; + foreach (array_keys($this->getConfig()->getIndexers()) as $id) { + $dependencies = $this->getDependencyInfoProvider()->getIndexerIdsToRunBefore($id); + if (array_search($indexerId, $dependencies) !== false) { + $dependentIndexerIds = array_merge( + $dependentIndexerIds, + [$id], + $this->getDependentIndexerIds($id) + ); + } + }; + + return array_unique($dependentIndexerIds); + } + /** * Validate that indexer is not locked * - * @param \Magento\Framework\Indexer\IndexerInterface $indexer + * @param IndexerInterface $indexer * @return void * @throws LocalizedException * @since 2.1.0 */ - private function validateIndexerStatus(\Magento\Framework\Indexer\IndexerInterface $indexer) + private function validateIndexerStatus(IndexerInterface $indexer) { - if ($indexer->getStatus() == \Magento\Framework\Indexer\StateInterface::STATUS_WORKING) { + if ($indexer->getStatus() == StateInterface::STATUS_WORKING) { throw new LocalizedException( __( '%1 index is locked by another reindex process. Skipping.', @@ -161,12 +258,10 @@ private function validateSharedIndex($sharedIndex) return $this; } foreach ($indexerIds as $indexerId) { - /** @var \Magento\Indexer\Model\Indexer $indexer */ - $indexer = $this->getIndexerFactory()->create(); - $indexer->load($indexerId); + $indexer = $this->getIndexerRegistry()->get($indexerId); /** @var \Magento\Indexer\Model\Indexer\State $state */ $state = $indexer->getState(); - $state->setStatus(\Magento\Framework\Indexer\StateInterface::STATUS_VALID); + $state->setStatus(StateInterface::STATUS_VALID); $state->save(); } $this->sharedIndexesComplete[] = $sharedIndex; @@ -176,7 +271,7 @@ private function validateSharedIndex($sharedIndex) /** * Get config * - * @return \Magento\Framework\Indexer\ConfigInterface + * @return ConfigInterface * @deprecated 2.1.0 * @since 2.1.0 */ @@ -189,17 +284,30 @@ private function getConfig() } /** - * Get indexer factory + * Get indexer registry. * - * @return IndexerFactory + * @return IndexerRegistry + * @deprecated 2.2.0 + * @since 2.2.0 + */ + private function getIndexerRegistry() + { + if (!$this->indexerRegistry) { + $this->indexerRegistry = $this->getObjectManager()->get(IndexerRegistry::class); + } + return $this->indexerRegistry; + } + + /** + * @return DependencyInfoProvider * @deprecated 2.2.0 * @since 2.2.0 */ - private function getIndexerFactory() + private function getDependencyInfoProvider() { - if (null === $this->indexerFactory) { - $this->indexerFactory = $this->getObjectManager()->get(IndexerFactory::class); + if (!$this->dependencyInfoProvider) { + $this->dependencyInfoProvider = $this->getObjectManager()->get(DependencyInfoProvider::class); } - return $this->indexerFactory; + return $this->dependencyInfoProvider; } } diff --git a/app/code/Magento/Indexer/Cron/ReindexAllInvalid.php b/app/code/Magento/Indexer/Cron/ReindexAllInvalid.php index 959546a2bf44f..57108e84b09bb 100644 --- a/app/code/Magento/Indexer/Cron/ReindexAllInvalid.php +++ b/app/code/Magento/Indexer/Cron/ReindexAllInvalid.php @@ -5,8 +5,6 @@ */ namespace Magento\Indexer\Cron; -use Magento\Indexer\Model\Indexer; - /** * Class \Magento\Indexer\Cron\ReindexAllInvalid * diff --git a/app/code/Magento/Indexer/Model/Indexer.php b/app/code/Magento/Indexer/Model/Indexer.php index 4b13b307f4ba7..6e5260c72077b 100644 --- a/app/code/Magento/Indexer/Model/Indexer.php +++ b/app/code/Magento/Indexer/Model/Indexer.php @@ -68,7 +68,6 @@ class Indexer extends \Magento\Framework\DataObject implements IdxInterface protected $indexersFactory; /** - * Indexer constructor. * @param ConfigInterface $config * @param ActionFactory $actionFactory * @param StructureFactory $structureFactory diff --git a/app/code/Magento/Indexer/Model/Indexer/Collection.php b/app/code/Magento/Indexer/Model/Indexer/Collection.php index 6ff1e69599d20..c3287edcc3d14 100644 --- a/app/code/Magento/Indexer/Model/Indexer/Collection.php +++ b/app/code/Magento/Indexer/Model/Indexer/Collection.php @@ -5,6 +5,8 @@ */ namespace Magento\Indexer\Model\Indexer; +use Magento\Framework\Indexer\IndexerInterface; + /** * Class \Magento\Indexer\Model\Indexer\Collection * @@ -18,7 +20,15 @@ class Collection extends \Magento\Framework\Data\Collection * @var string * @since 2.0.0 */ - protected $_itemObjectClass = \Magento\Framework\Indexer\IndexerInterface::class; + protected $_itemObjectClass = IndexerInterface::class; + + /** + * Collection items + * + * @var IndexerInterface[] + * @since 2.2.0 + */ + protected $_items = []; /** * @var \Magento\Framework\Indexer\ConfigInterface @@ -63,7 +73,7 @@ public function loadData($printQuery = false, $logQuery = false) if (!$this->isLoaded()) { $states = $this->statesFactory->create(); foreach (array_keys($this->config->getIndexers()) as $indexerId) { - /** @var \Magento\Framework\Indexer\IndexerInterface $indexer */ + /** @var IndexerInterface $indexer */ $indexer = $this->getNewEmptyItem(); $indexer->load($indexerId); foreach ($states->getItems() as $state) { @@ -79,4 +89,145 @@ public function loadData($printQuery = false, $logQuery = false) } return $this; } + + /** + * {@inheritdoc} Prevents handle collection items as DataObject class instances. + * @since 2.2.0 + */ + public function getAllIds() + { + $ids = []; + foreach ($this->getItems() as $item) { + $ids[] = $item->getId(); + } + return $ids; + } + + /** + * @inheritdoc + * @return IndexerInterface[] + * @since 2.2.0 + */ + public function getItems() + { + return parent::getItems(); + } + + /** + * {@inheritdoc} Prevents handle collection items as DataObject class instances. + * @deprecated + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @since 2.2.0 + */ + public function getColumnValues($colName) + { + return []; + } + + /** + * {@inheritdoc} Prevents handle collection items as DataObject class instances. + * @deprecated + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @since 2.2.0 + */ + public function getItemsByColumnValue($column, $value) + { + return []; + } + + /** + * {@inheritdoc} Prevents handle collection items as DataObject class instances. + * @deprecated + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @since 2.2.0 + */ + public function getItemByColumnValue($column, $value) + { + return null; + } + + /** + * {@inheritdoc} Prevents handle collection items as DataObject class instances. + * @deprecated + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @since 2.2.0 + */ + public function setDataToAll($key, $value = null) + { + return $this; + } + + /** + * {@inheritdoc} Prevents handle collection items as DataObject class instances. + * @deprecated + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @since 2.2.0 + */ + public function setItemObjectClass($className) + { + return $this; + } + + /** + * {@inheritdoc} Prevents handle collection items as DataObject class instances. + * @deprecated + * @since 2.2.0 + */ + public function toXml() + { + return ''; + } + + /** + * {@inheritdoc} Prevents handle collection items as DataObject class instances. + * @deprecated + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @since 2.2.0 + */ + public function toArray($arrRequiredFields = []) + { + return []; + } + + /** + * {@inheritdoc} Prevents handle collection items as DataObject class instances. + * @deprecated + * @since 2.2.0 + */ + public function toOptionArray() + { + return []; + } + + /** + * {@inheritdoc} Prevents handle collection items as DataObject class instances. + * @deprecated + * @since 2.2.0 + */ + public function toOptionHash() + { + return []; + } + + /** + * {@inheritdoc} Prevents handle collection items as DataObject class instances. + * @deprecated + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @since 2.2.0 + */ + protected function _toOptionArray($valueField = 'id', $labelField = 'name', $additional = []) + { + return []; + } + + /** + * {@inheritdoc} Prevents handle collection items as DataObject class instances. + * @deprecated + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @since 2.2.0 + */ + protected function _toOptionHash($valueField = 'id', $labelField = 'name') + { + return []; + } } diff --git a/app/code/Magento/Indexer/Model/Indexer/DependencyDecorator.php b/app/code/Magento/Indexer/Model/Indexer/DependencyDecorator.php new file mode 100644 index 0000000000000..201956dd39434 --- /dev/null +++ b/app/code/Magento/Indexer/Model/Indexer/DependencyDecorator.php @@ -0,0 +1,271 @@ +indexer = $indexer; + $this->dependencyInfoProvider = $dependencyInfoProvider; + $this->indexerRegistry = $indexerRegistry; + } + + /** + * @inheritdoc + */ + public function __call($method, $args) + { + return $this->indexer->__call($method, $args); + } + + /** + * @return array + */ + public function __sleep() + { + return ['indexer', 'dependencyProvider', 'indexerRegistry', 'mapperHandler']; + } + + /** + * @inheritdoc + */ + public function __clone() + { + $this->indexer = clone $this->indexer; + } + + /** + * @inheritdoc + */ + public function getId() + { + return $this->indexer->getId(); + } + + /** + * @inheritdoc + */ + public function getViewId() + { + return $this->indexer->getViewId(); + } + + /** + * @inheritdoc + */ + public function getActionClass() + { + return $this->indexer->getActionClass(); + } + + /** + * @inheritdoc + */ + public function getTitle() + { + return $this->indexer->getTitle(); + } + + /** + * @inheritdoc + */ + public function getDescription() + { + return $this->indexer->getDescription(); + } + + /** + * @inheritdoc + */ + public function getFields() + { + return $this->indexer->getFields(); + } + + /** + * @inheritdoc + */ + public function getSources() + { + return $this->indexer->getSources(); + } + + /** + * @inheritdoc + */ + public function getHandlers() + { + return $this->indexer->getHandlers(); + } + + /** + * @inheritdoc + */ + public function load($indexerId) + { + $this->indexer->load($indexerId); + return $this; + } + + /** + * @inheritdoc + */ + public function getView() + { + return $this->indexer->getView(); + } + + /** + * @inheritdoc + */ + public function getState() + { + return $this->indexer->getState(); + } + + /** + * @inheritdoc + */ + public function setState(StateInterface $state) + { + return $this->indexer->setState($state); + } + + /** + * @inheritdoc + */ + public function isScheduled() + { + return $this->indexer->isScheduled(); + } + + /** + * @inheritdoc + */ + public function setScheduled($scheduled) + { + $this->indexer->setScheduled($scheduled); + } + + /** + * @inheritdoc + */ + public function isValid() + { + return $this->indexer->isValid(); + } + + /** + * @inheritdoc + */ + public function isInvalid() + { + return $this->indexer->isInvalid(); + } + + /** + * @inheritdoc + */ + public function isWorking() + { + return $this->indexer->isWorking(); + } + + /** + * {@inheritdoc} + */ + public function invalidate() + { + $this->indexer->invalidate(); + $dependentIndexerIds = $this->dependencyInfoProvider->getIndexerIdsToRunAfter($this->indexer->getId()); + foreach ($dependentIndexerIds as $indexerId) { + $this->indexerRegistry->get($indexerId)->invalidate(); + } + } + + /** + * @inheritdoc + */ + public function getStatus() + { + return $this->indexer->getStatus(); + } + + /** + * @inheritdoc + */ + public function getLatestUpdated() + { + return $this->indexer->getLatestUpdated(); + } + + /** + * @inheritdoc + */ + public function reindexAll() + { + $this->indexer->reindexAll(); + } + + /** + * {@inheritdoc} + */ + public function reindexRow($id) + { + $this->indexer->reindexRow($id); + $dependentIndexerIds = $this->dependencyInfoProvider->getIndexerIdsToRunAfter($this->indexer->getId()); + foreach ($dependentIndexerIds as $indexerId) { + $this->indexerRegistry->get($indexerId)->reindexRow($id); + } + } + + /** + * {@inheritdoc} + */ + public function reindexList($ids) + { + $this->indexer->reindexList($ids); + $dependentIndexerIds = $this->dependencyInfoProvider->getIndexerIdsToRunAfter($this->indexer->getId()); + foreach ($dependentIndexerIds as $indexerId) { + $this->indexerRegistry->get($indexerId)->reindexList($ids); + } + } +} diff --git a/app/code/Magento/Indexer/Model/Processor.php b/app/code/Magento/Indexer/Model/Processor.php index 94893cc0115b1..34cdc42e02729 100644 --- a/app/code/Magento/Indexer/Model/Processor.php +++ b/app/code/Magento/Indexer/Model/Processor.php @@ -7,6 +7,7 @@ use Magento\Framework\Indexer\ConfigInterface; use Magento\Framework\Indexer\IndexerInterface; +use Magento\Framework\Indexer\IndexerInterfaceFactory; use Magento\Framework\Indexer\StateInterface; /** @@ -23,7 +24,7 @@ class Processor protected $config; /** - * @var IndexerFactory + * @var IndexerInterfaceFactory * @since 2.0.0 */ protected $indexerFactory; @@ -42,14 +43,14 @@ class Processor /** * @param ConfigInterface $config - * @param IndexerFactory $indexerFactory + * @param IndexerInterfaceFactory $indexerFactory * @param Indexer\CollectionFactory $indexersFactory * @param \Magento\Framework\Mview\ProcessorInterface $mviewProcessor * @since 2.0.0 */ public function __construct( ConfigInterface $config, - IndexerFactory $indexerFactory, + IndexerInterfaceFactory $indexerFactory, Indexer\CollectionFactory $indexersFactory, \Magento\Framework\Mview\ProcessorInterface $mviewProcessor ) { diff --git a/app/code/Magento/Indexer/Setup/RecurringData.php b/app/code/Magento/Indexer/Setup/RecurringData.php index 308bae1953c14..1476e00d06f33 100644 --- a/app/code/Magento/Indexer/Setup/RecurringData.php +++ b/app/code/Magento/Indexer/Setup/RecurringData.php @@ -6,10 +6,10 @@ namespace Magento\Indexer\Setup; +use Magento\Framework\Indexer\IndexerInterfaceFactory; use Magento\Framework\Setup\InstallDataInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\ModuleDataSetupInterface; -use Magento\Indexer\Model\IndexerFactory; use Magento\Framework\Indexer\ConfigInterface; /** @@ -19,7 +19,7 @@ class RecurringData implements InstallDataInterface { /** - * @var IndexerFactory + * @var IndexerInterfaceFactory * @since 2.2.0 */ private $indexerFactory; @@ -33,12 +33,12 @@ class RecurringData implements InstallDataInterface /** * RecurringData constructor. * - * @param IndexerFactory $indexerFactory + * @param IndexerInterfaceFactory $indexerFactory * @param ConfigInterface $configInterface * @since 2.2.0 */ public function __construct( - IndexerFactory $indexerFactory, + IndexerInterfaceFactory $indexerFactory, ConfigInterface $configInterface ) { $this->indexerFactory = $indexerFactory; diff --git a/app/code/Magento/Indexer/Test/Unit/Console/Command/AbstractIndexerCommandCommonSetup.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/AbstractIndexerCommandCommonSetup.php index f405099d7a34c..d5b1bc4ced69c 100644 --- a/app/code/Magento/Indexer/Test/Unit/Console/Command/AbstractIndexerCommandCommonSetup.php +++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/AbstractIndexerCommandCommonSetup.php @@ -6,8 +6,12 @@ namespace Magento\Indexer\Test\Unit\Console\Command; use Magento\Backend\App\Area\FrontNameResolver; -use Magento\Framework\App\ObjectManagerFactory; +use Magento\Framework\Indexer\IndexerInterface; +use Magento\Indexer\Model\Indexer\Collection; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class AbstractIndexerCommandCommonSetup extends \PHPUnit_Framework_TestCase { /** @@ -16,7 +20,7 @@ class AbstractIndexerCommandCommonSetup extends \PHPUnit_Framework_TestCase protected $configLoaderMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Indexer\Model\IndexerFactory + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Indexer\IndexerInterfaceFactory */ protected $indexerFactory; @@ -40,6 +44,11 @@ class AbstractIndexerCommandCommonSetup extends \PHPUnit_Framework_TestCase */ protected $objectManager; + /** + * @var Collection|\PHPUnit_Framework_MockObject_MockObject + */ + protected $indexerCollectionMock; + protected function setUp() { $this->objectManagerFactory = $this->getMock( @@ -65,7 +74,16 @@ protected function setUp() ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->indexerFactory = $this->getMockBuilder(\Magento\Indexer\Model\IndexerFactory::class) + + $this->indexerCollectionMock = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->collectionFactory + ->method('create') + ->willReturn($this->indexerCollectionMock); + + $this->indexerFactory = $this->getMockBuilder(\Magento\Framework\Indexer\IndexerInterfaceFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); @@ -78,7 +96,7 @@ protected function setUp() $this->getObjectManagerReturnValueMap(), [ [\Magento\Indexer\Model\Indexer\CollectionFactory::class, $this->collectionFactory], - [\Magento\Indexer\Model\IndexerFactory::class, $this->indexerFactory], + [\Magento\Framework\Indexer\IndexerInterfaceFactory::class, $this->indexerFactory], ] ) ) @@ -112,4 +130,36 @@ protected function configureAdminArea() ->method('setAreaCode') ->with(FrontNameResolver::AREA_CODE); } + + /** + * @param array $methods + * @param array $data + * @return \PHPUnit_Framework_MockObject_MockObject|IndexerInterface + */ + protected function getIndexerMock(array $methods = [], array $data = []) + { + /** @var \PHPUnit_Framework_MockObject_MockObject|IndexerInterface $indexer */ + $indexer = $this->getMockBuilder(IndexerInterface::class) + ->setMethods(array_merge($methods, ['getId', 'getTitle'])) + ->getMockForAbstractClass(); + $indexer->method('getId') + ->willReturn($data['indexer_id'] ?? ''); + $indexer->method('getTitle') + ->willReturn($data['title'] ?? ''); + return $indexer; + } + + /** + * Init Indexer Collection Mock by items. + * + * @param IndexerInterface[] $items + * @throws \Exception + */ + protected function initIndexerCollectionByItems(array $items) + { + $this->indexerCollectionMock + ->method('getItems') + ->with() + ->willReturn($items); + } } diff --git a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerInfoCommandTest.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerInfoCommandTest.php index ab4e72475d04b..6a34f759e96ca 100644 --- a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerInfoCommandTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerInfoCommandTest.php @@ -27,13 +27,11 @@ protected function setUp() public function testExecute() { $this->configureAdminArea(); - $collection = $this->getMock(\Magento\Indexer\Model\Indexer\Collection::class, [], [], '', false); - $indexerOne = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); - $indexerOne->expects($this->once())->method('getId')->willReturn('id_indexerOne'); - $indexerOne->expects($this->once())->method('getTitle')->willReturn('Title_indexerOne'); - $collection->expects($this->once())->method('getItems')->willReturn([$indexerOne]); - - $this->collectionFactory->expects($this->once())->method('create')->will($this->returnValue($collection)); + $indexerOne = $this->getIndexerMock( + [], + ['indexer_id' => 'id_indexerOne', 'title' => 'Title_indexerOne'] + ); + $this->initIndexerCollectionByItems([$indexerOne]); $this->command = new IndexerInfoCommand($this->objectManagerFactory); $commandTester = new CommandTester($this->command); $commandTester->execute([]); diff --git a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerReindexCommandTest.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerReindexCommandTest.php index 0165b28dfa268..f216b270472ff 100644 --- a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerReindexCommandTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerReindexCommandTest.php @@ -5,11 +5,20 @@ */ namespace Magento\Indexer\Test\Unit\Console\Command; +use Magento\Framework\Console\Cli; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Indexer\Config\DependencyInfoProvider; +use Magento\Framework\Indexer\IndexerInterface; +use Magento\Framework\Indexer\IndexerRegistry; +use Magento\Framework\Indexer\StateInterface; use Magento\Framework\Phrase; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Indexer\Console\Command\IndexerReindexCommand; use Symfony\Component\Console\Tester\CommandTester; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class IndexerReindexCommandTest extends AbstractIndexerCommandCommonSetup { /** @@ -24,12 +33,37 @@ class IndexerReindexCommandTest extends AbstractIndexerCommandCommonSetup */ protected $configMock; + /** + * @var IndexerRegistry|\PHPUnit_Framework_MockObject_MockObject + */ + private $indexerRegistryMock; + + /** + * @var DependencyInfoProvider|\PHPUnit_Framework_MockObject_MockObject + */ + private $dependencyInfoProviderMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + /** * Set up */ public function setUp() { + $this->objectManagerHelper = new ObjectManagerHelper($this); $this->configMock = $this->getMock(\Magento\Indexer\Model\Config::class, [], [], '', false); + $this->indexerRegistryMock = $this->getMockBuilder(IndexerRegistry::class) + ->disableOriginalConstructor() + ->getMock(); + $this->dependencyInfoProviderMock = $this->objectManagerHelper->getObject( + DependencyInfoProvider::class, + [ + 'config' => $this->configMock, + ] + ); parent::setUp(); } @@ -42,6 +76,7 @@ protected function getObjectManagerReturnValueMap() { $result = parent::getObjectManagerReturnValueMap(); $result[] = [\Magento\Framework\Indexer\ConfigInterface::class, $this->configMock]; + $result[] = [DependencyInfoProvider::class, $this->dependencyInfoProviderMock]; return $result; } @@ -61,143 +96,366 @@ public function testExecuteAll() 'shared_index' => null ])); $this->configureAdminArea(); - $collection = $this->getMock(\Magento\Indexer\Model\Indexer\Collection::class, [], [], '', false); - $indexerOne = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); - $indexerOne->expects($this->once())->method('getTitle')->willReturn('Title_indexerOne'); - $collection->expects($this->once())->method('getItems')->willReturn([$indexerOne]); - - $this->collectionFactory->expects($this->once())->method('create')->will($this->returnValue($collection)); + $this->initIndexerCollectionByItems([ + $this->getIndexerMock( + ['reindexAll', 'getStatus'], + ['indexer_id' => 'id_indexerOne', 'title' => 'Title_indexerOne'] + ) + ]); $this->indexerFactory->expects($this->never())->method('create'); $this->command = new IndexerReindexCommand($this->objectManagerFactory); $commandTester = new CommandTester($this->command); $commandTester->execute([]); $actualValue = $commandTester->getDisplay(); + $this->assertSame(Cli::RETURN_SUCCESS, $commandTester->getStatusCode()); $this->assertStringStartsWith('Title_indexerOne index has been rebuilt successfully in', $actualValue); } - public function testExecuteWithIndex() + /** + * @param array $inputIndexers + * @param array $indexers + * @param array $states + * @param array $reindexAllCallMatchers + * @param array $executedIndexers + * @param array $executedSharedIndexers + * @dataProvider executeWithIndexDataProvider + */ + public function testExecuteWithIndex( + array $inputIndexers, + array $indexers, + array $states, + array $reindexAllCallMatchers, + array $executedIndexers, + array $executedSharedIndexers + ) { + $this->addSeparateIndexersToConfigMock($indexers); + $this->addAllIndexersToConfigMock($indexers); + + $indexerMocks = []; + foreach ($indexers as $indexerData) { + $indexer = $this->getIndexerMock(['getState', 'reindexAll', 'isInvalid'], $indexerData); + $indexer->method('getState') + ->willReturn( + $this->getStateMock( + ['loadByIndexer', 'setStatus', 'save'], + $states[$indexer->getId()] ?? [] + ) + ); + $indexer->method('isInvalid') + ->willReturn(StateInterface::STATUS_INVALID === ($states[$indexer->getId()]['status'] ?? '')); + $indexer->expects($reindexAllCallMatchers[$indexer->getId()]) + ->method('reindexAll'); + $indexerMocks[] = $indexer; + } + $this->initIndexerCollectionByItems($indexerMocks); + + $emptyIndexer = $this->getIndexerMock(['load', 'getState']); + $this->indexerRegistryMock + ->expects($this->exactly(count($executedSharedIndexers))) + ->method('get') + ->withConsecutive(...$executedSharedIndexers) + ->willReturn($emptyIndexer); + $emptyIndexer->method('getState') + ->willReturn($this->getStateMock(['setStatus', 'save'])); + + $this->configureAdminArea(); + + $this->command = new IndexerReindexCommand( + $this->objectManagerFactory, + $this->indexerRegistryMock + ); + $commandTester = new CommandTester($this->command); + $commandTester->execute(['index' => $inputIndexers]); + $this->assertSame(Cli::RETURN_SUCCESS, $commandTester->getStatusCode()); + $pattern = '#^'; + $template = '{Title} index has been rebuilt successfully in \d{2}:\d{2}:\d{2}\W*'; + foreach ($executedIndexers as $indexerId) { + $pattern .= str_replace( + '{Title}', + $indexers[$indexerId]['title'], + $template + ); + } + $pattern .= '$#'; + $this->assertRegExp($pattern, $commandTester->getDisplay()); + } + + /** + * @param array $indexers + */ + private function addSeparateIndexersToConfigMock(array $indexers) { - $this->configMock->expects($this->any()) + $this->configMock ->method('getIndexer') - ->will($this->returnValueMap( - [ - ['id_indexerOne', ['title' => 'Title_indexerOne', 'shared_index' => null]], - ['id_indexerTwo', ['title' => 'Title_indexerTwo', 'shared_index' => 'with_indexer_3']], - ['id_indexer3', ['title' => 'Title_indexer3', 'shared_index' => 'with_indexer_3']] - ] - )); - $this->configMock->expects($this->any()) + ->willReturnMap( + array_map( + function ($elem) { + return [$elem['indexer_id'], $elem]; + }, + $indexers + ) + ); + } + + /** + * @param array $indexers + */ + private function addAllIndexersToConfigMock(array $indexers) + { + $this->configMock ->method('getIndexers') - ->will($this->returnValue( - [ - 'id_indexerOne' => [ - 'indexer_id' => 'id_indexerOne', - 'title' => 'Title_indexerOne', - 'shared_index' => null - ], - 'id_indexerTwo' => [ - 'indexer_id' => 'id_indexerTwo', - 'title' => 'Title_indexerTwo', - 'shared_index' => 'with_indexer_3' - ], - 'id_indexer3' => [ - 'indexer_id' => 'id_indexer3', - 'title' => 'Title_indexer3', - 'shared_index' => 'with_indexer_3' - ] - ] - )); + ->willReturn($indexers); + } - $this->configureAdminArea(); - $indexerOne = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); - $indexerOne->expects($this->once())->method('reindexAll'); - $indexerOne->expects($this->once())->method('getTitle')->willReturn('Title_indexerOne'); - $indexerOne->expects($this->any())->method('getId')->willReturn('id_indexerOne'); - $indexerOne->expects($this->any())->method('load')->with('id_indexerOne')->willReturn($indexerOne); - - $indexerTwo = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); - $indexerTwo->expects($this->once())->method('reindexAll'); - $indexerTwo->expects($this->once())->method('getTitle')->willReturn('Title_indexerTwo'); - $indexerTwo->expects($this->any())->method('getId')->willReturn('id_indexerTwo'); - $indexerTwo->expects($this->any())->method('load')->with('id_indexerTwo')->willReturn($indexerTwo); - - $indexer3 = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); - $indexer3->expects($this->never())->method('reindexAll'); - $indexer3->expects($this->once())->method('getTitle')->willReturn('Title_indexer3'); - $indexer3->expects($this->any())->method('getId')->willReturn('id_indexer3'); - $indexer3->expects($this->any())->method('load')->with('id_indexer3')->willReturn($indexer3); - - $stateMock = $this->getMock(\Magento\Indexer\Model\Indexer\State::class, [], [], '', false); - $stateMock->expects($this->exactly(2))->method('setStatus')->will($this->returnSelf()); - $stateMock->expects($this->exactly(2))->method('save'); - - $indexer3->expects($this->once())->method('getState')->willReturn($stateMock); - $indexerTwo->expects($this->once())->method('getState')->willReturn($stateMock); - - $this->collectionFactory->expects($this->never())->method('create'); - $this->indexerFactory->expects($this->at(0))->method('create')->willReturn($indexerOne); - $this->indexerFactory->expects($this->at(1))->method('create')->willReturn($indexerTwo); - $this->indexerFactory->expects($this->at(2))->method('create')->willReturn($indexer3); - $this->indexerFactory->expects($this->at(3))->method('create')->willReturn($indexerTwo); - $this->indexerFactory->expects($this->at(4))->method('create')->willReturn($indexer3); + /** + * @param array|null $methods + * @param array $data + * @return \PHPUnit_Framework_MockObject_MockObject|StateInterface + */ + private function getStateMock(array $methods = null, array $data = []) + { + /** @var \PHPUnit_Framework_MockObject_MockObject|StateInterface $state */ + $state = $this->getMockBuilder(StateInterface::class) + ->setMethods($methods) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $state->method('getStatus') + ->willReturn($data['status'] ?? StateInterface::STATUS_INVALID); + return $state; + } - $this->command = new IndexerReindexCommand($this->objectManagerFactory); - $commandTester = new CommandTester($this->command); - $commandTester->execute(['index' => ['id_indexerOne', 'id_indexerTwo', 'id_indexer3']]); - $actualValue = $commandTester->getDisplay(); - $this->assertStringStartsWith('Title_indexerOne index has been rebuilt successfully in', $actualValue); + /** + * @return array + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function executeWithIndexDataProvider() + { + return [ + 'Without dependencies' => [ + 'inputIndexers' => [ + 'indexer_1' + ], + 'indexers' => [ + 'indexer_1' => [ + 'indexer_id' => 'indexer_1', + 'title' => 'Title_indexer_1', + 'shared_index' => null, + 'dependencies' => [], + ], + 'indexer_2' => [ + 'indexer_id' => 'indexer_2', + 'title' => 'Title_indexer_2', + 'shared_index' => 'with_indexer_3', + 'dependencies' => [], + ], + 'indexer_3' => [ + 'indexer_id' => 'indexer_3', + 'title' => 'Title_indexer_3', + 'shared_index' => 'with_indexer_3', + 'dependencies' => [], + ], + ], + 'indexer_states' => [ + 'indexer_2' => [ + 'status' => StateInterface::STATUS_VALID, + ], + 'indexer_3' => [ + 'status' => StateInterface::STATUS_VALID, + ], + ], + 'expected_reindex_all_calls' => [ + 'indexer_1' => $this->once(), + 'indexer_2' => $this->never(), + 'indexer_3' => $this->never(), + ], + 'executed_indexers' => ['indexer_1'], + 'executed_shared_indexers' => [], + ], + 'With dependencies and some indexers is invalid' => [ + 'inputIndexers' => [ + 'indexer_1' + ], + 'indexers' => [ + 'indexer_2' => [ + 'indexer_id' => 'indexer_2', + 'title' => 'Title_indexer_2', + 'shared_index' => 'with_indexer_3', + 'dependencies' => [], + ], + 'indexer_3' => [ + 'indexer_id' => 'indexer_3', + 'title' => 'Title_indexer_3', + 'shared_index' => 'with_indexer_3', + 'dependencies' => [], + ], + 'indexer_1' => [ + 'indexer_id' => 'indexer_1', + 'title' => 'Title_indexer_1', + 'shared_index' => null, + 'dependencies' => ['indexer_2', 'indexer_3'], + ], + 'indexer_4' => [ + 'indexer_id' => 'indexer_4', + 'title' => 'Title_indexer_4', + 'shared_index' => null, + 'dependencies' => [], + ], + 'indexer_5' => [ + 'indexer_id' => 'indexer_5', + 'title' => 'Title_indexer_5', + 'shared_index' => null, + 'dependencies' => ['indexer_1'], + ], + ], + 'indexer_states' => [ + 'indexer_2' => [ + 'status' => StateInterface::STATUS_VALID, + ], + 'indexer_3' => [ + 'status' => StateInterface::STATUS_INVALID, + ], + 'indexer_4' => [ + 'status' => StateInterface::STATUS_INVALID, + ], + 'indexer_5' => [ + 'status' => StateInterface::STATUS_VALID, + ], + ], + 'expected_reindex_all_calls' => [ + 'indexer_1' => $this->once(), + 'indexer_2' => $this->never(), + 'indexer_3' => $this->once(), + 'indexer_4' => $this->never(), + 'indexer_5' => $this->once(), + ], + 'executed_indexers' => ['indexer_3', 'indexer_1', 'indexer_5'], + 'executed_shared_indexers' => [['indexer_2'],['indexer_3']], + ], + 'With dependencies and multiple indexers in request' => [ + 'inputIndexers' => [ + 'indexer_1', 'indexer_3' + ], + 'indexers' => [ + 'indexer_2' => [ + 'indexer_id' => 'indexer_2', + 'title' => 'Title_indexer_2', + 'shared_index' => null, + 'dependencies' => [], + ], + 'indexer_1' => [ + 'indexer_id' => 'indexer_1', + 'title' => 'Title_indexer_1', + 'shared_index' => null, + 'dependencies' => ['indexer_2'], + ], + 'indexer_4' => [ + 'indexer_id' => 'indexer_4', + 'title' => 'Title_indexer_4', + 'shared_index' => null, + 'dependencies' => [], + ], + 'indexer_3' => [ + 'indexer_id' => 'indexer_3', + 'title' => 'Title_indexer_3', + 'shared_index' => null, + 'dependencies' => ['indexer_4'], + ], + 'indexer_5' => [ + 'indexer_id' => 'indexer_5', + 'title' => 'Title_indexer_5', + 'shared_index' => null, + 'dependencies' => ['indexer_1'], + ], + ], + 'indexer_states' => [ + 'indexer_2' => [ + 'status' => StateInterface::STATUS_VALID, + ], + 'indexer_4' => [ + 'status' => StateInterface::STATUS_INVALID, + ], + 'indexer_5' => [ + 'status' => StateInterface::STATUS_VALID, + ], + ], + 'expected_reindex_all_calls' => [ + 'indexer_1' => $this->once(), + 'indexer_2' => $this->never(), + 'indexer_3' => $this->once(), + 'indexer_4' => $this->once(), + 'indexer_5' => $this->once(), + ], + 'executed_indexers' => ['indexer_1', 'indexer_4', 'indexer_3', 'indexer_5'], + 'executed_shared_indexers' => [], + ], + ]; } public function testExecuteWithLocalizedException() { $this->configureAdminArea(); - $indexerOne = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); + $indexerOne = $this->getIndexerMock(['reindexAll', 'getStatus'], ['indexer_id' => 'indexer_1']); $localizedException = new LocalizedException(new Phrase('Some Exception Message')); $indexerOne->expects($this->once())->method('reindexAll')->will($this->throwException($localizedException)); - $this->collectionFactory->expects($this->never())->method('create'); - $this->indexerFactory->expects($this->once())->method('create')->willReturn($indexerOne); + $this->initIndexerCollectionByItems([$indexerOne]); $this->command = new IndexerReindexCommand($this->objectManagerFactory); $commandTester = new CommandTester($this->command); - $commandTester->execute(['index' => ['id_indexerOne']]); + $commandTester->execute(['index' => ['indexer_1']]); $actualValue = $commandTester->getDisplay(); + $this->assertSame(Cli::RETURN_FAILURE, $commandTester->getStatusCode()); $this->assertStringStartsWith('Some Exception Message', $actualValue); } public function testExecuteWithException() { $this->configureAdminArea(); - $indexerOne = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); - $exception = new \Exception(); - $indexerOne->expects($this->once())->method('reindexAll')->will($this->throwException($exception)); - $indexerOne->expects($this->once())->method('getTitle')->willReturn('Title_indexerOne'); - $this->collectionFactory->expects($this->never())->method('create'); - $this->indexerFactory->expects($this->once())->method('create')->willReturn($indexerOne); + $indexerOne = $this->getIndexerMock( + ['reindexAll', 'getStatus'], + ['indexer_id' => 'indexer_1', 'title' => 'Title_indexer_1'] + ); + $indexerOne->expects($this->once()) + ->method('reindexAll') + ->willThrowException(new \Exception()); + $this->initIndexerCollectionByItems([$indexerOne]); $this->command = new IndexerReindexCommand($this->objectManagerFactory); $commandTester = new CommandTester($this->command); - $commandTester->execute(['index' => ['id_indexerOne']]); + $commandTester->execute(['index' => ['indexer_1']]); $actualValue = $commandTester->getDisplay(); - $this->assertStringStartsWith('Title_indexerOne indexer process unknown error:', $actualValue); + $this->assertSame(Cli::RETURN_FAILURE, $commandTester->getStatusCode()); + $this->assertStringStartsWith('Title_indexer_1' . ' indexer process unknown error:', $actualValue); } - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessageRegExp The following requested cache types are not supported:.* - */ - public function testExecuteWithExceptionInLoad() + public function testExecuteWithExceptionInGetIndexers() { $this->configureAdminArea(); - $collection = $this->getMock(\Magento\Indexer\Model\Indexer\Collection::class, [], [], '', false); - $indexerOne = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); - $indexerOne->expects($this->once())->method('getId')->willReturn('id_indexer1'); - $collection->expects($this->once())->method('getItems')->willReturn([$indexerOne]); + $inputIndexers = ['indexer_2']; + $indexerData = [ + 'indexer_id' => 'indexer_1', + 'shared_index' => 'new', + ]; + $indexerOne = $this->getIndexerMock( + ['reindexAll', 'getStatus', 'load'], + $indexerData + ); + $this->initIndexerCollectionByItems([$indexerOne]); - $exception = new \Exception(); - $indexerOne->expects($this->once())->method('load')->will($this->throwException($exception)); $indexerOne->expects($this->never())->method('getTitle'); - $this->collectionFactory->expects($this->once())->method('create')->will($this->returnValue($collection)); - $this->indexerFactory->expects($this->once())->method('create')->willReturn($indexerOne); + $this->setExpectedException( + \InvalidArgumentException::class, + "The following requested index types are not supported: '" + . join("', '", $inputIndexers) + . "'." . PHP_EOL . 'Supported types: ' + . join(", ", array_map( + function ($item) { + /** @var IndexerInterface $item */ + $item->getId(); + }, + $this->indexerCollectionMock->getItems() + )) + ); $this->command = new IndexerReindexCommand($this->objectManagerFactory); $commandTester = new CommandTester($this->command); - $commandTester->execute(['index' => ['id_indexerOne']]); + $commandTester->execute(['index' => $inputIndexers]); + $this->assertSame(Cli::RETURN_FAILURE, $commandTester->getStatusCode()); } } diff --git a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerResetStateCommandTest.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerResetStateCommandTest.php index cf59a455311a2..b4fb67026a49e 100644 --- a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerResetStateCommandTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerResetStateCommandTest.php @@ -27,16 +27,11 @@ protected function setUp() public function testExecute() { $this->configureAdminArea(); - $collection = $this->getMock(\Magento\Indexer\Model\Indexer\Collection::class, [], [], '', false); - $indexerOne = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); - - $indexerOne->expects($this->once()) - ->method('getTitle') - ->willReturn('Title_indexerOne'); - - $collection->expects($this->once()) - ->method('getItems') - ->willReturn([$indexerOne]); + $indexerOne = $this->getIndexerMock( + ['getState'], + ['indexer_id' => 'indexer_1', 'title' => 'Title_indexerOne'] + ); + $this->initIndexerCollectionByItems([$indexerOne]); $stateMock = $this->getMock(\Magento\Indexer\Model\Indexer\State::class, [], [], '', false); $stateMock->expects($this->exactly(1)) @@ -51,10 +46,6 @@ public function testExecute() ->method('getState') ->willReturn($stateMock); - $this->collectionFactory->expects($this->once()) - ->method('create') - ->will($this->returnValue($collection)); - $this->command = new IndexerResetStateCommand($this->objectManagerFactory); $commandTester = new CommandTester($this->command); $commandTester->execute([]); diff --git a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerSetModeCommandTest.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerSetModeCommandTest.php index c7fdbb7aeec65..ad3ae88816db4 100644 --- a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerSetModeCommandTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerSetModeCommandTest.php @@ -58,17 +58,18 @@ public function testExecuteInvalidMode() public function testExecuteAll() { $this->configureAdminArea(); - $collection = $this->getMock(\Magento\Indexer\Model\Indexer\Collection::class, [], [], '', false); - $indexerOne = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); + $indexerOne = $this->getIndexerMock( + ['isScheduled', 'setScheduled'], + ['indexer_id' => 'indexer_1', 'title' => 'Title_indexerOne'] + ); - $indexerOne->expects($this->at(0))->method('isScheduled')->willReturn(true); - $indexerOne->expects($this->at(2))->method('isScheduled')->willReturn(false); + $indexerOne->expects($this->exactly(2)) + ->method('isScheduled') + ->willReturnOnConsecutiveCalls([true, false]); $indexerOne->expects($this->once())->method('setScheduled')->with(false); - $indexerOne->expects($this->once())->method('getTitle')->willReturn('Title_indexerOne'); - $collection->expects($this->once())->method('getItems')->willReturn([$indexerOne]); - $this->collectionFactory->expects($this->once())->method('create')->will($this->returnValue($collection)); + $this->initIndexerCollectionByItems([$indexerOne]); $this->indexerFactory->expects($this->never())->method('create'); $this->command = new IndexerSetModeCommand($this->objectManagerFactory); $commandTester = new CommandTester($this->command); @@ -92,15 +93,15 @@ public function testExecuteAll() public function testExecuteWithIndex($isScheduled, $previous, $current, $mode, $expectedValue) { $this->configureAdminArea(); - $indexerOne = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); - $indexerOne->expects($this->once())->method('getTitle')->willReturn('Title_indexerOne'); - $indexerOne->expects($this->once())->method('load')->with('id_indexerOne')->willReturn($indexerOne); + $indexerOne = $this->getIndexerMock( + ['isScheduled', 'setScheduled'], + ['indexer_id' => 'id_indexerOne', 'title' => 'Title_indexerOne'] + ); + $this->initIndexerCollectionByItems([$indexerOne]); $indexerOne->expects($this->once())->method('setScheduled')->with($isScheduled); - $indexerOne->expects($this->at(1))->method('isScheduled')->willReturn($previous); - $indexerOne->expects($this->at(3))->method('isScheduled')->willReturn($current); - - $this->collectionFactory->expects($this->never())->method('create'); - $this->indexerFactory->expects($this->once())->method('create')->willReturn($indexerOne); + $indexerOne->expects($this->exactly(2)) + ->method('isScheduled') + ->willReturnOnConsecutiveCalls($previous, $current); $this->command = new IndexerSetModeCommand($this->objectManagerFactory); $commandTester = new CommandTester($this->command); @@ -153,11 +154,13 @@ public function executeWithIndexDataProvider() public function testExecuteWithLocalizedException() { $this->configureAdminArea(); - $indexerOne = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); + $indexerOne = $this->getIndexerMock( + ['isScheduled', 'setScheduled'], + ['indexer_id' => 'id_indexerOne'] + ); $localizedException = new \Magento\Framework\Exception\LocalizedException(__('Some Exception Message')); $indexerOne->expects($this->once())->method('setScheduled')->will($this->throwException($localizedException)); - $this->collectionFactory->expects($this->never())->method('create'); - $this->indexerFactory->expects($this->once())->method('create')->willReturn($indexerOne); + $this->initIndexerCollectionByItems([$indexerOne]); $this->command = new IndexerSetModeCommand($this->objectManagerFactory); $commandTester = new CommandTester($this->command); $commandTester->execute(['mode' => 'schedule', 'index' => ['id_indexerOne']]); @@ -168,12 +171,13 @@ public function testExecuteWithLocalizedException() public function testExecuteWithException() { $this->configureAdminArea(); - $indexerOne = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); + $indexerOne = $this->getIndexerMock( + ['isScheduled', 'setScheduled'], + ['indexer_id' => 'id_indexerOne', 'title' => 'Title_indexerOne'] + ); $exception = new \Exception(); $indexerOne->expects($this->once())->method('setScheduled')->will($this->throwException($exception)); - $indexerOne->expects($this->once())->method('getTitle')->willReturn('Title_indexerOne'); - $this->collectionFactory->expects($this->never())->method('create'); - $this->indexerFactory->expects($this->once())->method('create')->willReturn($indexerOne); + $this->initIndexerCollectionByItems([$indexerOne]); $this->command = new IndexerSetModeCommand($this->objectManagerFactory); $commandTester = new CommandTester($this->command); $commandTester->execute(['mode' => 'schedule', 'index' => ['id_indexerOne']]); diff --git a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerShowModeCommandTest.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerShowModeCommandTest.php index 4d0d3b27c303f..fe6020cb07167 100644 --- a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerShowModeCommandTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerShowModeCommandTest.php @@ -30,17 +30,17 @@ public function testGetOptions() public function testExecuteAll() { $this->configureAdminArea(); - $collection = $this->getMock(\Magento\Indexer\Model\Indexer\Collection::class, [], [], '', false); - $indexerOne = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); - $indexerOne->expects($this->once())->method('getTitle')->willReturn('Title_indexerOne'); + $indexerOne = $this->getIndexerMock( + ['isScheduled', 'setScheduled'], + ['indexer_id' => 'indexer_1', 'title' => 'Title_indexerOne'] + ); $indexerOne->expects($this->once())->method('isScheduled')->willReturn(true); - $indexerTwo = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); - $indexerTwo->expects($this->once())->method('getTitle')->willReturn('Title_indexerTwo'); + $indexerTwo = $this->getIndexerMock( + ['isScheduled', 'setScheduled'], + ['indexer_id' => 'indexer_2', 'title' => 'Title_indexerTwo'] + ); $indexerTwo->expects($this->once())->method('isScheduled')->willReturn(false); - $collection->expects($this->once())->method('getItems')->willReturn([$indexerOne, $indexerTwo]); - - $this->collectionFactory->expects($this->once())->method('create')->will($this->returnValue($collection)); - $this->indexerFactory->expects($this->never())->method('create'); + $this->initIndexerCollectionByItems([$indexerOne, $indexerTwo]); $this->command = new IndexerShowModeCommand($this->objectManagerFactory); $commandTester = new CommandTester($this->command); @@ -51,29 +51,68 @@ public function testExecuteAll() $this->assertStringStartsWith($expectedValue, $actualValue); } - public function testExecuteWithIndex() + /** + * @param array $inputIndexers + * @param array $indexers + * @param array $isScheduled + * @dataProvider executeWithIndexDataProvider + */ + public function testExecuteWithIndex(array $inputIndexers, array $indexers, array $isScheduled) { $this->configureAdminArea(); - $indexerOne = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); - $indexerOne->expects($this->once())->method('getTitle')->willReturn('Title_indexerOne'); - $indexerOne->expects($this->once())->method('isScheduled')->willReturn(true); - $indexerTwo = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); - $indexerTwo->expects($this->once())->method('getTitle')->willReturn('Title_indexerTwo'); - $indexerTwo->expects($this->once())->method('isScheduled')->willReturn(false); - $indexerThree = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); - $indexerThree->expects($this->never())->method('getTitle')->willReturn('Title_indexer3'); - $indexerThree->expects($this->never())->method('isScheduled')->willReturn(false); + $indexerMocks = []; + foreach ($indexers as $indexerData) { + $indexerMock = $this->getIndexerMock( + ['isScheduled', 'setScheduled'], + $indexerData + ); + $indexerMock->method('isScheduled') + ->willReturn($isScheduled[$indexerData['indexer_id']]); + $indexerMocks[] = $indexerMock; + } - $this->collectionFactory->expects($this->never())->method('create'); - $this->indexerFactory->expects($this->at(0))->method('create')->willReturn($indexerOne); - $this->indexerFactory->expects($this->at(1))->method('create')->willReturn($indexerTwo); + $this->initIndexerCollectionByItems($indexerMocks); $this->command = new IndexerShowModeCommand($this->objectManagerFactory); $commandTester = new CommandTester($this->command); - $commandTester->execute(['index' => ['id_indexerOne', 'id_indexerTwo']]); + $commandTester->execute(['index' => $inputIndexers]); $actualValue = $commandTester->getDisplay(); $expectedValue = sprintf('%-50s ', 'Title_indexerOne' . ':') . 'Update by Schedule' . PHP_EOL . sprintf('%-50s ', 'Title_indexerTwo' . ':') . 'Update on Save'; $this->assertStringStartsWith($expectedValue, $actualValue); } + + /** + * @return array + */ + public function executeWithIndexDataProvider() + { + return [ + [ + 'inputIndexers' => [ + 'id_indexerOne', + 'id_indexerTwo' + ], + 'indexers' => [ + 'id_indexerOne' => [ + 'indexer_id' => 'id_indexerOne', + 'title' => 'Title_indexerOne' + ], + 'id_indexerTwo' => [ + 'indexer_id' => 'id_indexerTwo', + 'title' => 'Title_indexerTwo' + ], + 'id_indexerThree' => [ + 'indexer_id' => 'id_indexerThree', + 'title' => 'Title_indexerThree' + ], + ], + 'Is Scheduled' => [ + 'id_indexerOne' => true, + 'id_indexerTwo' => false, + 'id_indexerThree' => false, + ] + ], + ]; + } } diff --git a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerStatusCommandTest.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerStatusCommandTest.php index 261c8b2c431fa..6eb7f7562b9cc 100644 --- a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerStatusCommandTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerStatusCommandTest.php @@ -5,6 +5,7 @@ */ namespace Magento\Indexer\Test\Unit\Console\Command; +use Magento\Framework\Indexer\StateInterface; use Magento\Indexer\Console\Command\IndexerStatusCommand; use Symfony\Component\Console\Tester\CommandTester; @@ -17,37 +18,25 @@ class IndexerStatusCommandTest extends AbstractIndexerCommandCommonSetup */ private $command; - public function testExecuteAll() + /** + * @param array $indexers + * @param array $statuses + * @dataProvider executeAllDataProvider + */ + public function testExecuteAll(array $indexers, array $statuses) { $this->configureAdminArea(); - $collection = $this->getMock(\Magento\Indexer\Model\Indexer\Collection::class, [], [], '', false); - $indexerOne = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); - $indexerOne->expects($this->once())->method('getTitle')->willReturn('Title_indexerOne'); - $indexerOne - ->expects($this->once()) - ->method('getStatus') - ->willReturn(\Magento\Framework\Indexer\StateInterface::STATUS_VALID); - $indexerTwo = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); - $indexerTwo->expects($this->once())->method('getTitle')->willReturn('Title_indexerTwo'); - $indexerTwo - ->expects($this->once()) - ->method('getStatus') - ->willReturn(\Magento\Framework\Indexer\StateInterface::STATUS_INVALID); - $indexerThree = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); - $indexerThree->expects($this->once())->method('getTitle')->willReturn('Title_indexerThree'); - $indexerThree - ->expects($this->once()) - ->method('getStatus') - ->willReturn(\Magento\Framework\Indexer\StateInterface::STATUS_WORKING); - $indexerFour = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); - $indexerFour->expects($this->once())->method('getTitle')->willReturn('Title_indexerFour'); - $collection - ->expects($this->once()) - ->method('getItems') - ->willReturn([$indexerOne, $indexerTwo, $indexerThree, $indexerFour]); - - $this->collectionFactory->expects($this->once())->method('create')->will($this->returnValue($collection)); - $this->indexerFactory->expects($this->never())->method('create'); + $indexerMocks = []; + foreach ($indexers as $indexerData) { + $indexerMock = $this->getIndexerMock( + ['getStatus'], + $indexerData + ); + $indexerMock->method('getStatus') + ->willReturn($statuses[$indexerData['indexer_id']]); + $indexerMocks[] = $indexerMock; + } + $this->initIndexerCollectionByItems($indexerMocks); $this->command = new IndexerStatusCommand($this->objectManagerFactory); $commandTester = new CommandTester($this->command); $commandTester->execute([]); @@ -59,4 +48,39 @@ public function testExecuteAll() $this->assertStringStartsWith($expectedValue, $actualValue); } + + /** + * @return array + */ + public function executeAllDataProvider() + { + return [ + [ + 'indexers' => [ + 'indexer_1' => [ + 'indexer_id' => 'indexer_1', + 'title' => 'Title_indexerOne' + ], + 'indexer_2' => [ + 'indexer_id' => 'indexer_2', + 'title' => 'Title_indexerTwo' + ], + 'indexer_3' => [ + 'indexer_id' => 'indexer_3', + 'title' => 'Title_indexerThree' + ], + 'indexer_4' => [ + 'indexer_id' => 'indexer_4', + 'title' => 'Title_indexerFour' + ], + ], + 'Statuses' => [ + 'indexer_1' => StateInterface::STATUS_VALID, + 'indexer_2' => StateInterface::STATUS_INVALID, + 'indexer_3' => StateInterface::STATUS_WORKING, + 'indexer_4' => null, + ] + ], + ]; + } } diff --git a/app/code/Magento/Indexer/Test/Unit/Model/Indexer/CollectionTest.php b/app/code/Magento/Indexer/Test/Unit/Model/Indexer/CollectionTest.php index f4bf22b945b0e..7e9b3d0d57892 100644 --- a/app/code/Magento/Indexer/Test/Unit/Model/Indexer/CollectionTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Model/Indexer/CollectionTest.php @@ -5,72 +5,324 @@ */ namespace Magento\Indexer\Test\Unit\Model\Indexer; +use Magento\Framework\Data\Collection\EntityFactoryInterface; +use Magento\Framework\Indexer\ConfigInterface; +use Magento\Framework\Indexer\IndexerInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Indexer\Model\Indexer\Collection; +use Magento\Indexer\Model\Indexer\State; +use Magento\Indexer\Model\ResourceModel\Indexer\State\Collection as StateCollection; +use Magento\Indexer\Model\ResourceModel\Indexer\State\CollectionFactory; + class CollectionTest extends \PHPUnit_Framework_TestCase { - public function testLoadData() + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var Collection + */ + private $collection; + + /** + * @var ConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * @var CollectionFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $statesFactoryMock; + + /** + * @var EntityFactoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $entityFactoryMock; + + /** + * @return void + */ + protected function setUp() + { + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->configMock = $this->getMockBuilder(ConfigInterface::class) + ->getMockForAbstractClass(); + + $this->statesFactoryMock = $this->getMockBuilder(CollectionFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + + $this->entityFactoryMock = $this->getMockBuilder(EntityFactoryInterface::class) + ->getMock(); + + $this->collection = $this->objectManagerHelper->getObject( + Collection::class, + [ + 'entityFactory' => $this->entityFactoryMock, + 'config' => $this->configMock, + 'statesFactory' => $this->statesFactoryMock, + ] + ); + } + + /** + * @param array $indexersData + * @param array $states + * @dataProvider loadDataDataProvider + */ + public function testLoadData(array $indexersData, array $states) + { + $statesCollection = $this->getMockBuilder(StateCollection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->statesFactoryMock + ->expects($this->once()) + ->method('create') + ->willReturn($statesCollection); + $statesCollection->method('getItems') + ->willReturn($states); + + $calls = []; + foreach ($indexersData as $indexerId => $indexerData) { + $indexer = $this->getIndexerMock($indexerData); + $state = $states[$indexerId] ?? ''; + $indexer + ->expects($this->once()) + ->method('load') + ->with($indexerId); + $indexer + ->expects($this->exactly($state ? 1: 0)) + ->method('setState') + ->with($state); + $calls[] = $indexer; + } + $this->configMock + ->method('getIndexers') + ->willReturn($indexersData); + $this->entityFactoryMock + ->method('create') + ->willReturnOnConsecutiveCalls(...$calls); + + $this->assertFalse((bool)$this->collection->isLoaded()); + $this->assertInstanceOf(Collection::class, $this->collection->loadData()); + $itemIds = []; + foreach ($this->collection->getItems() as $item) { + $itemIds[] = $item->getId(); + } + $this->assertEmpty(array_diff($itemIds, array_keys($indexersData))); + $this->assertTrue($this->collection->isLoaded()); + } + + /** + * @return array + */ + public function loadDataDataProvider() + { + return [ + [ + 'indexers' => [ + 'indexer_2' => [ + 'indexer_id' => 'indexer_2', + ], + 'indexer_3' => [ + 'indexer_id' => 'indexer_3', + ], + 'indexer_1' => [ + 'indexer_id' => 'indexer_1', + ], + ], + 'states' => [ + 'indexer_2' => $this->getStateMock(['indexer_id' => 'indexer_2']), + 'indexer_3' => $this->getStateMock(['indexer_id' => 'indexer_3']), + ], + ] + ]; + } + + /** + * @param array $indexersData + * @dataProvider getAllIdsDataProvider + */ + public function testGetAllIds(array $indexersData) + { + $statesCollection = $this->getMockBuilder(StateCollection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->statesFactoryMock + ->expects($this->once()) + ->method('create') + ->willReturn($statesCollection); + $statesCollection->method('getItems') + ->willReturn([]); + + $calls = []; + foreach ($indexersData as $indexerData) { + $calls[] = $this->getIndexerMock($indexerData); + } + $this->configMock + ->method('getIndexers') + ->willReturn($indexersData); + $this->entityFactoryMock + ->method('create') + ->willReturnOnConsecutiveCalls(...$calls); + + $this->assertEmpty(array_diff($this->collection->getAllIds(), array_keys($indexersData))); + } + + /** + * @return array + */ + public function getAllIdsDataProvider() { - $indexerIdOne = 'first_indexer_id'; - $indexerIdSecond = 'second_indexer_id'; - - $entityFactory = $this->getMockBuilder( - \Magento\Framework\Data\Collection\EntityFactoryInterface::class - )->disableOriginalConstructor()->setMethods( - ['create'] - )->getMock(); - - $config = $this->getMockBuilder(\Magento\Framework\Indexer\ConfigInterface::class)->getMock(); - - $statesFactory = $this->getMockBuilder( - \Magento\Indexer\Model\ResourceModel\Indexer\State\CollectionFactory::class - )->disableOriginalConstructor()->setMethods( - ['create'] - )->getMock(); - - $states = $this->getMockBuilder( - \Magento\Indexer\Model\ResourceModel\Indexer\State\Collection::class - )->disableOriginalConstructor()->getMock(); - - $state = $this->getMockBuilder( - \Magento\Indexer\Model\Indexer\State::class - )->setMethods( - ['getIndexerId', '__wakeup'] - )->disableOriginalConstructor()->getMock(); - - $state->expects($this->any())->method('getIndexerId')->will($this->returnValue('second_indexer_id')); - - $indexer = $this->getMockBuilder( - \Magento\Indexer\Model\Indexer\Collection::class - )->setMethods( - ['load', 'setState'] - )->disableOriginalConstructor()->getMock(); - - $indexer->expects($this->once())->method('setState')->with($state); - - $indexer->expects($this->any())->method('load')->with($this->logicalOr($indexerIdOne, $indexerIdSecond)); - - $entityFactory->expects( - $this->any() - )->method( - 'create' - )->with( - \Magento\Framework\Indexer\IndexerInterface::class - )->will( - $this->returnValue($indexer) + return [ + [ + 'indexers' => [ + 'indexer_2' => [ + 'indexer_id' => 'indexer_2', + ], + 'indexer_3' => [ + 'indexer_id' => 'indexer_3', + ], + 'indexer_1' => [ + 'indexer_id' => 'indexer_1', + ], + ], + ] + ]; + } + + /** + * @param string $methodName + * @param array $arguments + * @dataProvider stubMethodsDataProvider + */ + public function testStubMethods(string $methodName, array $arguments) + { + $this->statesFactoryMock + ->expects($this->never()) + ->method('create'); + $collection = $this->objectManagerHelper->getObject( + Collection::class, + [ + 'entityFactory' => $this->entityFactoryMock, + 'config' => $this->configMock, + 'statesFactory' => $this->statesFactoryMock, + '_items' => [$this->getIndexerMock()], + ] ); + $this->assertEmpty($collection->{$methodName}(...$arguments)); + } - $statesFactory->expects($this->once())->method('create')->will($this->returnValue($states)); + /** + * @return array + */ + public function stubMethodsDataProvider() + { + return [ + [ + 'getColumnValues', + ['colName'], + ], + [ + 'getItemsByColumnValue', + ['colName', 'value'] + ], + [ + 'getItemByColumnValue', + ['colName', 'value'] + ], + [ + 'toXml', + [] + ], + [ + 'toArray', + [] + ], + [ + 'toOptionArray', + [] + ], + [ + 'toOptionHash', + [] + ], + ]; + } - $config->expects( - $this->once() - )->method( - 'getIndexers' - )->will( - $this->returnValue([$indexerIdOne => 1, $indexerIdSecond => 2]) + /** + * @param string $methodName + * @param array $arguments + * @dataProvider stubMethodsWithReturnSelfDataProvider + */ + public function testStubMethodsWithReturnSelf(string $methodName, array $arguments) + { + $this->statesFactoryMock + ->expects($this->never()) + ->method('create'); + $collection = $this->objectManagerHelper->getObject( + Collection::class, + [ + 'entityFactory' => $this->entityFactoryMock, + 'config' => $this->configMock, + 'statesFactory' => $this->statesFactoryMock, + '_items' => [$this->getIndexerMock()], + ] ); + $this->assertInstanceOf(Collection::class, $collection->{$methodName}(...$arguments)); + } + + /** + * @return array + */ + public function stubMethodsWithReturnSelfDataProvider() + { + return [ + [ + 'setDataToAll', + ['colName', 'value'] + ], + [ + 'setItemObjectClass', + ['notValidClassName'] + ], + ]; + } - $states->expects($this->any())->method('getItems')->will($this->returnValue([$state])); + /** + * @return \PHPUnit_Framework_MockObject_MockObject|IndexerInterface + */ + private function getIndexerMock(array $data = []) + { + /** @var \PHPUnit_Framework_MockObject_MockObject|IndexerInterface $indexer */ + $indexer = $this->getMockBuilder(IndexerInterface::class) + ->getMockForAbstractClass(); + if (isset($data['indexer_id'])) { + $indexer->method('getId') + ->willReturn($data['indexer_id']); + } + return $indexer; + } + + /** + * @param array $data + * @return \PHPUnit_Framework_MockObject_MockObject|State + */ + private function getStateMock(array $data = []) + { + /** @var \PHPUnit_Framework_MockObject_MockObject|State $state */ + $state = $this->getMockBuilder(State::class) + ->disableOriginalConstructor() + ->getMock(); + if (isset($data['indexer_id'])) { + $state->method('getIndexerId') + ->willReturn($data['indexer_id']); + } - $collection = new \Magento\Indexer\Model\Indexer\Collection($entityFactory, $config, $statesFactory); - $this->assertInstanceOf(\Magento\Indexer\Model\Indexer\Collection::class, $collection->loadData()); + return $state; } } diff --git a/app/code/Magento/Indexer/Test/Unit/Model/Indexer/DependencyDecoratorTest.php b/app/code/Magento/Indexer/Test/Unit/Model/Indexer/DependencyDecoratorTest.php new file mode 100644 index 0000000000000..487910cacec1b --- /dev/null +++ b/app/code/Magento/Indexer/Test/Unit/Model/Indexer/DependencyDecoratorTest.php @@ -0,0 +1,263 @@ +objectManagerHelper = new ObjectManagerHelper($this); + + $this->indexerMock = $this->getMockBuilder(IndexerInterface::class) + ->getMockForAbstractClass(); + + $this->dependencyInfoProviderMock = $this->getMockBuilder(DependencyInfoProviderInterface::class) + ->getMockForAbstractClass(); + + $this->indexerRegistryMock = $this->getMockBuilder(IndexerRegistry::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->dependencyDecorator = $this->objectManagerHelper->getObject( + DependencyDecorator::class, + [ + 'indexer' => $this->indexerMock, + 'dependencyInfoProvider' => $this->dependencyInfoProviderMock, + 'indexerRegistry' => $this->indexerRegistryMock, + ] + ); + } + + /** + * @param string $methodName + * @dataProvider transitMethodsDataProvider + */ + public function testTransitMethods(string $methodName) + { + $value = 42; + $this->indexerMock + ->expects($this->once()) + ->method($methodName) + ->with() + ->willReturn($value); + $this->assertSame($value, $this->dependencyDecorator->{$methodName}()); + } + + /** + * @return array + */ + public function transitMethodsDataProvider() + { + return [ + ['getId'], + ['getViewId'], + ['getActionClass'], + ['getDescription'], + ['getFields'], + ['getSources'], + ['getHandlers'], + ['getView'], + ['getState'], + ['isScheduled'], + ['isValid'], + ['isInvalid'], + ['isWorking'], + ['getStatus'], + ['getLatestUpdated'], + ]; + } + + /** + * @param string $methodName + * @param array $params + * @dataProvider transitMethodsWithParamsDataProvider + */ + public function testTransitMethodsWithParams(string $methodName, array $params) + { + $this->indexerMock + ->expects($this->once()) + ->method($methodName) + ->with(...$params); + $this->dependencyDecorator->{$methodName}(...$params); + } + + /** + * @return array + */ + public function transitMethodsWithParamsDataProvider() + { + return [ + [ + 'setState', + [ + $this->getMockBuilder(StateInterface::class) + ->getMockForAbstractClass() + ] + ], + ['setScheduled', [true]], + ]; + } + + public function testLoad() + { + $inputValue = 42; + $this->indexerMock + ->expects($this->once()) + ->method('load') + ->with($inputValue) + ->willReturn($inputValue); + $this->assertInstanceOf(DependencyDecorator::class, $this->dependencyDecorator->load($inputValue)); + } + + public function testReindexAll() + { + $this->indexerMock + ->expects($this->once()) + ->method('reindexAll') + ->with(); + $this->dependencyDecorator->reindexAll(); + } + + public function testInvalidate() + { + $indexerId = 'indexer_1'; + $dependentIds = ['indexer_2', 'indexer_3']; + $calls = []; + foreach ($dependentIds as $dependentId) { + $indexer = $this->getIndexerMock(); + $indexer->expects($this->once()) + ->method('invalidate'); + $calls[] = [$dependentId, $indexer]; + } + $this->indexerMock + ->expects($this->once()) + ->method('invalidate') + ->with(); + $this->indexerMock + ->method('getId') + ->willReturn($indexerId); + $this->dependencyInfoProviderMock + ->expects($this->once()) + ->method('getIndexerIdsToRunAfter') + ->with($indexerId) + ->willReturn($dependentIds); + $this->indexerRegistryMock + ->expects($this->exactly(count($dependentIds))) + ->method('get') + ->willReturnMap($calls); + $this->dependencyDecorator->invalidate(); + } + + public function testReindexRow() + { + $inputId = 100200; + $indexerId = 'indexer_1'; + $dependentIds = ['indexer_2', 'indexer_3']; + $calls = []; + foreach ($dependentIds as $dependentId) { + $indexer = $this->getIndexerMock(); + $indexer->expects($this->once()) + ->method('reindexRow') + ->with($inputId); + $calls[] = [$dependentId, $indexer]; + } + $this->indexerMock + ->expects($this->once()) + ->method('reindexRow') + ->with($inputId); + $this->indexerMock + ->method('getId') + ->willReturn($indexerId); + $this->dependencyInfoProviderMock + ->expects($this->once()) + ->method('getIndexerIdsToRunAfter') + ->with($indexerId) + ->willReturn($dependentIds); + $this->indexerRegistryMock + ->expects($this->exactly(count($dependentIds))) + ->method('get') + ->willReturnMap($calls); + $this->dependencyDecorator->reindexRow($inputId); + } + + public function testReindexList() + { + $inputIds = [100200, 100300]; + $indexerId = 'indexer_1'; + $dependentIds = ['indexer_2', 'indexer_3']; + $calls = []; + foreach ($dependentIds as $dependentId) { + $indexer = $this->getIndexerMock(); + $indexer->expects($this->once()) + ->method('reindexList') + ->with($inputIds); + $calls[] = [$dependentId, $indexer]; + } + $this->indexerMock + ->expects($this->once()) + ->method('reindexList') + ->with($inputIds); + $this->indexerMock + ->method('getId') + ->willReturn($indexerId); + $this->dependencyInfoProviderMock + ->expects($this->once()) + ->method('getIndexerIdsToRunAfter') + ->with($indexerId) + ->willReturn($dependentIds); + $this->indexerRegistryMock + ->expects($this->exactly(count($dependentIds))) + ->method('get') + ->willReturnMap($calls); + $this->dependencyDecorator->reindexList($inputIds); + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject|IndexerInterface + */ + private function getIndexerMock() + { + $indexer = $this->getMockBuilder(IndexerInterface::class) + ->getMockForAbstractClass(); + return $indexer; + } +} diff --git a/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php b/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php index 3df32bbd4b9d9..089eeaa6ef651 100644 --- a/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php @@ -5,6 +5,7 @@ */ namespace Magento\Indexer\Test\Unit\Model; +use Magento\Framework\Indexer\IndexerInterfaceFactory; use Magento\Framework\Indexer\StateInterface; class ProcessorTest extends \PHPUnit_Framework_TestCase @@ -20,7 +21,7 @@ class ProcessorTest extends \PHPUnit_Framework_TestCase protected $configMock; /** - * @var \Magento\Indexer\Model\IndexerFactory|\PHPUnit_Framework_MockObject_MockObject + * @var IndexerInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject */ protected $indexerFactoryMock; @@ -45,13 +46,10 @@ protected function setUp() true, ['getIndexers'] ); - $this->indexerFactoryMock = $this->getMock( - \Magento\Indexer\Model\IndexerFactory::class, - ['create'], - [], - '', - false - ); + $this->indexerFactoryMock = $this->getMockBuilder(IndexerInterfaceFactory::class) + ->setMethods(['create']) + ->getMockForAbstractClass(); + $this->indexersFactoryMock = $this->getMock( \Magento\Indexer\Model\Indexer\CollectionFactory::class, ['create'], diff --git a/app/code/Magento/Indexer/Test/Unit/Ui/DataProvider/Indexer/DataCollectionTest.php b/app/code/Magento/Indexer/Test/Unit/Ui/DataProvider/Indexer/DataCollectionTest.php new file mode 100644 index 0000000000000..d192695369815 --- /dev/null +++ b/app/code/Magento/Indexer/Test/Unit/Ui/DataProvider/Indexer/DataCollectionTest.php @@ -0,0 +1,171 @@ +objectManagerHelper = new ObjectManagerHelper($this); + + $this->configMock = $this->getMockBuilder(ConfigInterface::class) + ->getMockForAbstractClass(); + + $this->indexerRegistryMock = $this->getMockBuilder(IndexerRegistry::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->entityFactoryMock = $this->getMockBuilder(EntityFactoryInterface::class) + ->getMock(); + + $this->dataCollection = $this->objectManagerHelper->getObject( + DataCollection::class, + [ + 'entityFactory' => $this->entityFactoryMock, + 'config' => $this->configMock, + 'indexerRegistry' => $this->indexerRegistryMock, + ] + ); + } + + /** + * @param array $indexersData + * @dataProvider loadDataDataProvider + */ + public function testLoadData(array $indexersData) + { + $calls = []; + foreach ($indexersData as $indexerId => $data) { + $indexer = $this->getIndexerMock($data); + $calls[] = [$indexerId, $indexer]; + } + $this->configMock + ->method('getIndexers') + ->willReturn($indexersData); + $this->entityFactoryMock + ->method('create') + ->willReturnMap([[DataObject::class, [], new DataObject()]]); + $this->indexerRegistryMock + ->expects($this->exactly(count($indexersData))) + ->method('get') + ->willReturnMap($calls); + $this->assertFalse((bool)$this->dataCollection->isLoaded()); + foreach ($this->dataCollection->getItems() as $item) { + $this->assertEmpty( + array_diff( + [ + 'indexer_id', + 'title', + 'description', + 'is_scheduled', + 'status', + 'updated', + ], + array_keys($item->getData()) + ) + ); + $this->assertEmpty( + array_diff( + $indexersData[$item->getData('indexer_id')], + $item->getData() + ) + ); + } + $this->assertTrue($this->dataCollection->isLoaded()); + } + + /** + * @return array + */ + public function loadDataDataProvider() + { + return [ + [ + 'indexers' => [ + 'indexer_2' => [ + 'getId' => 'indexer_2', + 'getTitle' => 'Title_2', + 'getDescription' => 'Description_2', + 'isScheduled' => true, + 'getStatus' => StateInterface::STATUS_INVALID, + 'getLatestUpdated' => '2017/07/01' + ], + 'indexer_3' => [ + 'getId' => 'indexer_3', + 'getTitle' => 'Title_3', + 'getDescription' => 'Description_3', + 'isScheduled' => true, + 'getStatus' => StateInterface::STATUS_VALID, + 'getLatestUpdated' => '2017/07/02' + ], + 'indexer_1' => [ + 'getId' => 'indexer_1', + 'getTitle' => 'Title_1', + 'getDescription' => 'Description_1', + 'isScheduled' => false, + 'getStatus' => StateInterface::STATUS_INVALID, + 'getLatestUpdated' => '2017/07/03' + ], + ], + ] + ]; + } + + /** + * @param array $data + * @return \PHPUnit_Framework_MockObject_MockObject|IndexerInterface + */ + private function getIndexerMock(array $data = []) + { + /** @var \PHPUnit_Framework_MockObject_MockObject|IndexerInterface $indexer */ + $indexer = $this->getMockBuilder(IndexerInterface::class) + ->getMockForAbstractClass(); + foreach ($data as $methodName => $result) { + $indexer + ->method($methodName) + ->willReturn($result); + } + return $indexer; + } +} diff --git a/app/code/Magento/Indexer/Ui/DataProvider/Indexer/DataCollection.php b/app/code/Magento/Indexer/Ui/DataProvider/Indexer/DataCollection.php new file mode 100644 index 0000000000000..5ba4521791c50 --- /dev/null +++ b/app/code/Magento/Indexer/Ui/DataProvider/Indexer/DataCollection.php @@ -0,0 +1,61 @@ +config = $config; + $this->indexerRegistry = $indexerRegistry; + parent::__construct($entityFactory); + } + + /** + * @inheritdoc + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function loadData($printQuery = false, $logQuery = false) + { + if (!$this->isLoaded()) { + foreach (array_keys($this->config->getIndexers()) as $indexerId) { + $indexer = $this->indexerRegistry->get($indexerId); + $item = $this->getNewEmptyItem(); + $data = [ + 'indexer_id' => $indexer->getId(), + 'title' => $indexer->getTitle(), + 'description' => $indexer->getDescription(), + 'is_scheduled' => $indexer->isScheduled(), + 'status' => $indexer->getStatus(), + 'updated' => $indexer->getLatestUpdated(), + ]; + $this->addItem($item->setData($data)); + } + $this->_setIsLoaded(true); + } + return $this; + } +} diff --git a/app/code/Magento/Indexer/etc/di.xml b/app/code/Magento/Indexer/etc/di.xml index 3eea4a295cecf..df0633e5aac6b 100644 --- a/app/code/Magento/Indexer/etc/di.xml +++ b/app/code/Magento/Indexer/etc/di.xml @@ -9,9 +9,10 @@ - + + @@ -56,4 +57,9 @@ + + + Magento\Indexer\Model\Indexer + + diff --git a/app/code/Magento/Indexer/view/adminhtml/layout/indexer_indexer_list_grid.xml b/app/code/Magento/Indexer/view/adminhtml/layout/indexer_indexer_list_grid.xml index c1ae1c68315a9..54888f94d9ae9 100644 --- a/app/code/Magento/Indexer/view/adminhtml/layout/indexer_indexer_list_grid.xml +++ b/app/code/Magento/Indexer/view/adminhtml/layout/indexer_indexer_list_grid.xml @@ -13,7 +13,7 @@ 0 0 gridIndexer - Magento\Indexer\Model\Indexer\Collection + Magento\Indexer\Ui\DataProvider\Indexer\DataCollection @@ -61,7 +61,7 @@ Mode - isScheduled + is_scheduled Magento\Indexer\Block\Backend\Grid\Column\Renderer\Scheduled 0 indexer-mode @@ -70,7 +70,7 @@ Status - getStatus + status Magento\Indexer\Block\Backend\Grid\Column\Renderer\Status 0 indexer-status @@ -81,7 +81,6 @@ Updated updated datetime - getLatestUpdated Magento\Indexer\Block\Backend\Grid\Column\Renderer\Updated 0 col-date diff --git a/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/ConverterTest.php b/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/ConverterTest.php index ccfa9dc43c9f3..554ba0712b69d 100644 --- a/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/ConverterTest.php +++ b/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/ConverterTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Indexer\Model\Config; +use Magento\Framework\Exception\ConfigurationMismatchException; + class ConverterTest extends \PHPUnit_Framework_TestCase { /** @@ -28,4 +30,33 @@ public function testConverter() $result = $this->model->convert($domDocument); $this->assertEquals($expectedResult, $result); } + + /** + * @return void + */ + public function testConverterWithCircularDependency() + { + $pathFiles = __DIR__ . '/_files'; + $path = $pathFiles . '/indexer_with_circular_dependency.xml'; + $domDocument = new \DOMDocument(); + $domDocument->load($path); + $this->setExpectedException(ConfigurationMismatchException::class, 'Circular dependency references from'); + $this->model->convert($domDocument); + } + + /** + * @return void + */ + public function testConverterWithDependencyOnNotExistingIndexer() + { + $pathFiles = __DIR__ . '/_files'; + $path = $pathFiles . '/dependency_on_not_existing_indexer.xml'; + $domDocument = new \DOMDocument(); + $domDocument->load($path); + $this->setExpectedException( + ConfigurationMismatchException::class, + "Dependency declaration 'indexer_4' in 'indexer_2' to the non-existing indexer." + ); + $this->model->convert($domDocument); + } } diff --git a/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/_files/dependency_on_not_existing_indexer.xml b/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/_files/dependency_on_not_existing_indexer.xml new file mode 100644 index 0000000000000..d9f1209516e80 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/_files/dependency_on_not_existing_indexer.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/_files/indexer.xml b/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/_files/indexer.xml index e485e9a0a308f..260d3bc779f61 100644 --- a/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/_files/indexer.xml +++ b/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/_files/indexer.xml @@ -29,6 +29,20 @@ - + + + + + + + + + + + + diff --git a/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/_files/indexer_with_circular_dependency.xml b/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/_files/indexer_with_circular_dependency.xml new file mode 100644 index 0000000000000..d4908c715440d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/_files/indexer_with_circular_dependency.xml @@ -0,0 +1,51 @@ + + + + + + Catalog Search + Rebuild Catalog product fulltext search index + +
+ + + + +
+ +
+ + +
+ + + + + + +
+ + + + + + + + + + + + +
diff --git a/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/_files/result.php b/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/_files/result.php index bb4869cf2bd5c..2382315fbb0c5 100644 --- a/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/_files/result.php +++ b/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/_files/result.php @@ -5,6 +5,29 @@ */ return [ + 'indexer_4' => [ + 'indexer_id' => 'indexer_4', + 'view_id' => 'indexer_4', + 'primary' => null, + 'action_class' => 'Magento\Module\IndexerFourth', + 'shared_index' => null, + 'title' => '', + 'description' => '', + 'dependencies' => [], + ], + 'indexer_2' => [ + 'indexer_id' => 'indexer_2', + 'view_id' => 'indexer_2', + 'primary' => null, + 'action_class' => 'Magento\Module\IndexerSecond', + 'shared_index' => null, + 'title' => '', + 'description' => '', + 'fieldsets' => [], + 'dependencies' => [ + 'indexer_4' + ], + ], 'catalogsearch_fulltext' => [ 'indexer_id' => 'catalogsearch_fulltext', 'shared_index' => null, @@ -69,5 +92,16 @@ ], 'saveHandler' => \Magento\Cms\Model\Indexer\StoreResource::class, 'structure' => \Magento\Cms\Model\Indexer\IndexStructure::class, + 'dependencies' => ['indexer_2'], + ], + 'indexer_3' => [ + 'indexer_id' => 'indexer_3', + 'view_id' => 'indexer_3', + 'primary' => null, + 'action_class' => 'Magento\Module\IndexerThird', + 'shared_index' => null, + 'title' => '', + 'description' => '', + 'dependencies' => [], ], ]; diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/blacklist/obsolete_mage.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/blacklist/obsolete_mage.php index 839a4c9cc5c64..24cc65eb8bf86 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/blacklist/obsolete_mage.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/blacklist/obsolete_mage.php @@ -8,4 +8,5 @@ 'dev/tests/static/testsuite/Magento/Test/Integrity/ClassesTest.php', 'dev/tests/static/testsuite/Magento/Test/Legacy/_files/*obsolete*.php', 'lib/internal/Magento/Framework/ObjectManager/Test/Unit/Factory/CompiledTest.php', + 'dev/tests/integration/testsuite/Magento/Indexer/Model/Config/_files/result.php', ]; diff --git a/lib/internal/Magento/Framework/Indexer/Config/Converter.php b/lib/internal/Magento/Framework/Indexer/Config/Converter.php index ff247d78a8500..bc6cab238271f 100644 --- a/lib/internal/Magento/Framework/Indexer/Config/Converter.php +++ b/lib/internal/Magento/Framework/Indexer/Config/Converter.php @@ -6,6 +6,8 @@ namespace Magento\Framework\Indexer\Config; use Magento\Framework\Config\ConverterInterface; +use Magento\Framework\Exception\ConfigurationMismatchException; +use Magento\Framework\Phrase; /** * Class \Magento\Framework\Indexer\Config\Converter @@ -47,8 +49,13 @@ public function convert($source) /** @var $childNode \DOMElement */ $data = $this->convertChild($childNode, $data); } + if (!isset($data['dependencies'])) { + $data['dependencies'] = []; + } $output[$indexerId] = $data; } + $output = $this->sortByDependencies($output); + return $output; } @@ -94,6 +101,9 @@ protected function convertChild(\DOMElement $childNode, $data) case 'fieldset': $data = $this->convertFieldset($childNode, $data); break; + case 'dependencies': + $data = $this->convertDependencies($childNode, $data); + break; } return $data; } @@ -164,6 +174,29 @@ protected function convertFieldset(\DOMElement $node, $data) return $this->sorting($data); } + /** + * Convert dependencies node + * + * @param \DOMElement $node + * @param array $data + * @return array + */ + private function convertDependencies(\DOMElement $node, array $data) + { + $data['dependencies'] = $data['dependencies'] ?? []; + + /** @var $childNode \DOMNode */ + foreach ($node->childNodes as $childNode) { + switch ($childNode->nodeName) { + case 'indexer': + $indexerId = $this->getAttributeValue($childNode, 'id'); + $data['dependencies'][] = $indexerId; + break; + } + } + return $data; + } + /** * Add virtual field * @@ -252,4 +285,84 @@ protected function sorting($data) }); return $data; } + + /** + * Sort the list of indexers using "dependencies" node data. + * + * This method also sort data in the "dependencies" node of indexers. + * + * @param array $indexers + * @return array + */ + private function sortByDependencies($indexers) + { + $expanded = []; + foreach (array_keys($indexers) as $indexerId) { + $expanded[] = [ + 'indexerId' => $indexerId, + 'dependencies' => $this->expandDependencies($indexers, $indexerId), + ]; + } + // Use "bubble sorting" because usort (which is using quicksort) is not a stable sort + $total = count($expanded); + for ($i = 0; $i < $total - 1; $i++) { + for ($j = $i; $j < $total; $j++) { + if (in_array($expanded[$j]['indexerId'], $expanded[$i]['dependencies'])) { + $temp = $expanded[$i]; + $expanded[$i] = $expanded[$j]; + $expanded[$j] = $temp; + } + } + } + + $orderedIndexerIds = array_map( + function ($item) { + return $item['indexerId']; + }, + $expanded + ); + + $result = []; + foreach ($orderedIndexerIds as $indexerId) { + $result[$indexerId] = $indexers[$indexerId]; + $result[$indexerId]['dependencies'] = array_values( + array_intersect($orderedIndexerIds, $result[$indexerId]['dependencies']) + ); + } + + return $result; + } + + /** + * Accumulate information about all transitive "dependencies" references. + * + * @param array $list + * @param string $indexerId + * @param array $accumulated + * @return array + * @throws ConfigurationMismatchException + */ + private function expandDependencies($list, $indexerId, $accumulated = []) + { + $accumulated[] = $indexerId; + $result = $list[$indexerId]['dependencies']; + foreach ($result as $relatedIndexerId) { + if (in_array($relatedIndexerId, $accumulated)) { + throw new ConfigurationMismatchException( + new Phrase("Circular dependency references from '{$indexerId}' to '{$relatedIndexerId}'.") + ); + } + if (!isset($list[$relatedIndexerId])) { + throw new ConfigurationMismatchException( + new Phrase( + "Dependency declaration '{$relatedIndexerId}' in " + . "'{$indexerId}' to the non-existing indexer." + ) + ); + } + $relatedResult = $this->expandDependencies($list, $relatedIndexerId, $accumulated); + $result = array_unique(array_merge($result, $relatedResult)); + } + return $result; + } } diff --git a/lib/internal/Magento/Framework/Indexer/Config/DependencyInfoProvider.php b/lib/internal/Magento/Framework/Indexer/Config/DependencyInfoProvider.php new file mode 100644 index 0000000000000..aceb1a2141830 --- /dev/null +++ b/lib/internal/Magento/Framework/Indexer/Config/DependencyInfoProvider.php @@ -0,0 +1,85 @@ +config = $config; + } + + /** + * @inheritdoc + */ + public function getIndexerIdsToRunBefore(string $indexerId) + { + return $this->getIndexerDataWithValidation($indexerId)['dependencies']; + } + + /** + * @inheritdoc + */ + public function getIndexerIdsToRunAfter(string $indexerId) + { + /** check indexer existence */ + $this->getIndexerDataWithValidation($indexerId); + $result = []; + foreach ($this->config->getIndexers() as $id => $indexerData) { + if (array_search($indexerId, $indexerData['dependencies']) !== false) { + $result[] = $id; + } + }; + + return $result; + } + + /** + * Return the indexer data from the configuration. + * + * @param string $indexerId + * @return array + */ + private function getIndexerData(string $indexerId) + { + return $this->config->getIndexer($indexerId); + } + + /** + * Return the indexer data from the configuration and validate this data. + * + * @param string $indexerId + * @return array + * @throws NoSuchEntityException In case when the indexer with the specified Id does not exist. + */ + private function getIndexerDataWithValidation(string $indexerId) + { + $indexerData = $this->getIndexerData($indexerId); + if (empty($indexerData) || empty($indexerData['indexer_id']) || $indexerData['indexer_id'] != $indexerId) { + throw new NoSuchEntityException( + new Phrase("{$indexerId} indexer does not exist.") + ); + } + + return $indexerData; + } +} diff --git a/lib/internal/Magento/Framework/Indexer/Config/DependencyInfoProviderInterface.php b/lib/internal/Magento/Framework/Indexer/Config/DependencyInfoProviderInterface.php new file mode 100644 index 0000000000000..52f357c2f9729 --- /dev/null +++ b/lib/internal/Magento/Framework/Indexer/Config/DependencyInfoProviderInterface.php @@ -0,0 +1,33 @@ +assertEquals($data['expected'], $this->_model->convert($dom)); } + + /** + * @param string $inputXml + * @param string $exceptionMessage + * @dataProvider convertWithCircularDependenciesDataProvider + */ + public function testConvertWithCircularDependencies($inputXml, $exceptionMessage) + { + $dom = new \DOMDocument(); + $dom->loadXML($inputXml); + $this->setExpectedException(ConfigurationMismatchException::class, $exceptionMessage); + $this->_model->convert($dom); + } + + /** + * @return array + */ + public function convertWithCircularDependenciesDataProvider() + { + return [ + 'Circular dependency on the first level' => [ + 'inputXML' => '' + . '' + . '' + . '', + 'exceptionMessage' => "Circular dependency references from 'indexer_2' to 'indexer_1'.", + ], + 'Circular dependency a deeper than the first level' => [ + 'inputXML' => '' + . '' + . '' + . '' + . '' + . '', + 'exceptionMessage' => "Circular dependency references from 'indexer_4' to 'indexer_1'.", + ], + ]; + } + + /** + * @param string $inputXml + * @param string $exceptionMessage + * @dataProvider convertWithDependencyOnNotExistingIndexerDataProvider + */ + public function testConvertWithDependencyOnNotExistingIndexer($inputXml, $exceptionMessage) + { + $dom = new \DOMDocument(); + $dom->loadXML($inputXml); + $this->setExpectedException(ConfigurationMismatchException::class, $exceptionMessage); + $this->_model->convert($dom); + } + + /** + * @return array + */ + public function convertWithDependencyOnNotExistingIndexerDataProvider() + { + return [ + [ + 'inputXML' => '' + . '' + . '' + . '', + 'exceptionMessage' => "Dependency declaration 'indexer_3' in 'indexer_1' to the non-existing indexer.", + ], + ]; + } } diff --git a/lib/internal/Magento/Framework/Indexer/Test/Unit/Config/DependencyInfoProviderTest.php b/lib/internal/Magento/Framework/Indexer/Test/Unit/Config/DependencyInfoProviderTest.php new file mode 100644 index 0000000000000..79e8d2554d572 --- /dev/null +++ b/lib/internal/Magento/Framework/Indexer/Test/Unit/Config/DependencyInfoProviderTest.php @@ -0,0 +1,292 @@ +objectManagerHelper = new ObjectManagerHelper($this); + + $this->configMock = $this->getMockBuilder(ConfigInterface::class) + ->getMockForAbstractClass(); + + $this->dependencyInfoProvider = $this->objectManagerHelper->getObject( + DependencyInfoProvider::class, + [ + 'config' => $this->configMock, + ] + ); + } + + public function testGetDependencies() + { + $indexerId = 'indexer_1'; + $dependencies = [ + 'indexer_2', + 'indexer_3', + ]; + $this->addSeparateIndexersToConfigMock([ + [ + 'indexer_id' => $indexerId, + 'dependencies' => $dependencies, + ] + ]); + $this->assertSame($dependencies, $this->dependencyInfoProvider->getIndexerIdsToRunBefore($indexerId)); + } + + public function testGetDependenciesNonExistentIndexer() + { + $indexerId = 'indexer_1'; + $this->setExpectedException( + NoSuchEntityException::class, + "{$indexerId} indexer does not exist." + ); + $this->dependencyInfoProvider->getIndexerIdsToRunBefore($indexerId); + } + + /** + * @param string $indexerId + * @param array $indexersData + * @param array $dependencySequence + * @dataProvider getDependencySequenceDataProvider + */ + /*public function testGetDependencySequence(string $indexerId, array $indexersData, array $dependencySequence) + { + $this->addSeparateIndexersToConfigMock($indexersData); + $this->addAllIndexersToConfigMock($indexersData); + $this->assertSame($dependencySequence, $this->dependencyProvider->getDependencySequence($indexerId)); + }*/ + + /** + * @return array + */ + /*public function getDependencySequenceDataProvider() + { + return [ + [ + 'indexer' => 'indexer_1', + 'indexers' => [ + 'indexer_2' => [ + 'indexer_id' => 'indexer_2', + 'dependencies' => [], + ], + 'indexer_4' => [ + 'indexer_id' => 'indexer_4', + 'dependencies' => [], + ], + 'indexer_3' => [ + 'indexer_id' => 'indexer_3', + 'dependencies' => [ + 'indexer_4', + ], + ], + 'indexer_1' => [ + 'indexer_id' => 'indexer_1', + 'dependencies' => [ + 'indexer_2', + 'indexer_3', + ], + ], + 'indexer_5' => [ + 'indexer_id' => 'indexer_5', + 'dependencies' => [], + ], + ], + 'dependency_sequence' => ['indexer_2', 'indexer_4', 'indexer_3'], + ] + ]; + }*/ + + /*public function testGetDependencySequenceNonExistentIndexer() + { + $indexerId = 'indexer_1'; + $this->setExpectedException( + NoSuchEntityException::class, + "{$indexerId} indexer does not exist." + ); + $this->dependencyProvider->getDependentSequence($indexerId); + }*/ + + /** + * @param string $indexerId + * @param array $indexersData + * @param array $dependentIndexerIds + * @dataProvider getDependentIndexerIdsDataProvider + */ + public function testGetDependentIndexerIds(string $indexerId, array $indexersData, array $dependentIndexerIds) + { + $this->addSeparateIndexersToConfigMock($indexersData); + $this->addAllIndexersToConfigMock($indexersData); + $this->assertSame( + $dependentIndexerIds, + array_values($this->dependencyInfoProvider->getIndexerIdsToRunAfter($indexerId)) + ); + } + + /** + * @return array + */ + public function getDependentIndexerIdsDataProvider() + { + return [ + [ + 'indexer' => 'indexer_2', + 'indexers' => [ + 'indexer_2' => [ + 'indexer_id' => 'indexer_2', + 'dependencies' => [], + ], + 'indexer_4' => [ + 'indexer_id' => 'indexer_4', + 'dependencies' => [ + 'indexer_2', + ], + ], + 'indexer_3' => [ + 'indexer_id' => 'indexer_3', + 'dependencies' => [ + 'indexer_4', + ], + ], + 'indexer_1' => [ + 'indexer_id' => 'indexer_1', + 'dependencies' => [ + 'indexer_2', + 'indexer_3', + ], + ], + 'indexer_5' => [ + 'indexer_id' => 'indexer_5', + 'dependencies' => [], + ], + ], + 'dependent_indexers' => ['indexer_4', 'indexer_1'], + ] + ]; + } + + public function testGetDependentIndexerIdsNonExistentIndexer() + { + $indexerId = 'indexer_1'; + $this->setExpectedException( + NoSuchEntityException::class, + "{$indexerId} indexer does not exist." + ); + $this->dependencyInfoProvider->getIndexerIdsToRunAfter($indexerId); + } + + /** + * @param string $indexerId + * @param array $indexersData + * @param array $dependentSequence + * @dataProvider getDependentSequenceDataProvider + */ + /*public function testGetDependentSequence(string $indexerId, array $indexersData, array $dependentSequence) + { + $this->addSeparateIndexersToConfigMock($indexersData); + $this->addAllIndexersToConfigMock($indexersData); + $this->assertSame( + $dependentSequence, + array_values($this->dependencyProvider->getDependentSequence($indexerId)) + ); + }*/ + + /** + * @return array + */ + /*public function getDependentSequenceDataProvider() + { + return [ + [ + 'indexer' => 'indexer_4', + 'indexers' => [ + 'indexer_2' => [ + 'indexer_id' => 'indexer_2', + 'dependencies' => [], + ], + 'indexer_4' => [ + 'indexer_id' => 'indexer_4', + 'dependencies' => [ + 'indexer_2', + ], + ], + 'indexer_3' => [ + 'indexer_id' => 'indexer_3', + 'dependencies' => [ + 'indexer_4', + ], + ], + 'indexer_1' => [ + 'indexer_id' => 'indexer_1', + 'dependencies' => [ + 'indexer_2', + 'indexer_3', + ], + ], + 'indexer_5' => [ + 'indexer_id' => 'indexer_5', + 'dependencies' => [ + 'indexer_1', + ], + ], + ], + 'dependent_sequence' => ['indexer_3', 'indexer_1', 'indexer_5'], + ] + ]; + }*/ + + /** + * @param array $indexers + */ + private function addSeparateIndexersToConfigMock(array $indexers) + { + $this->configMock + ->method('getIndexer') + ->willReturnMap( + array_map( + function ($elem) { + return [$elem['indexer_id'], $elem]; + }, + $indexers + ) + ); + } + + /** + * @param array $indexers + */ + private function addAllIndexersToConfigMock(array $indexers) + { + $this->configMock + ->method('getIndexers') + ->willReturn($indexers); + } +} diff --git a/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/indexer_config.php b/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/indexer_config.php index 402a9f6be5439..98d58b6ad5e7f 100644 --- a/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/indexer_config.php +++ b/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/indexer_config.php @@ -7,21 +7,17 @@ 'inputXML' => '' . '' . '' . - 'Indexer public nameIndexer public description' . - '' . - 'Indexer public description' . - '', + 'Indexer public nameIndexer public description' + . '' + . '' + . '' + . '' + . '' + . '' + . 'Indexer public description' + . '' + . '', 'expected' => [ - 'indexer_internal_name' => [ - 'indexer_id' => 'indexer_internal_name', - 'view_id' => 'view01', - 'action_class' => 'Index\Class\Name', - 'title' => __('Indexer public name'), - 'description' => __('Indexer public description'), - 'primary' => null, - 'shared_index' => null, - 'fieldsets' => [] - ], 'test_indexer' => [ 'indexer_id' => 'test_indexer', 'view_id' => '', @@ -30,6 +26,31 @@ 'description' => '', 'primary' => null, 'shared_index' => null, + 'dependencies' => [], + ], + 'test_indexer_with_dependencies' => [ + 'indexer_id' => 'test_indexer_with_dependencies', + 'view_id' => '', + 'action_class' => 'Index\Temp', + 'title' => '', + 'description' => '', + 'primary' => null, + 'shared_index' => null, + 'fieldsets' => [], + 'dependencies' => ['test_indexer'], + ], + 'indexer_internal_name' => [ + 'indexer_id' => 'indexer_internal_name', + 'view_id' => 'view01', + 'action_class' => 'Index\Class\Name', + 'title' => __('Indexer public name'), + 'description' => __('Indexer public description'), + 'primary' => null, + 'shared_index' => null, + 'fieldsets' => [], + 'dependencies' => [ + 'test_indexer_with_dependencies' + ], ], ] ]; diff --git a/lib/internal/Magento/Framework/Indexer/etc/indexer.xsd b/lib/internal/Magento/Framework/Indexer/etc/indexer.xsd index 76fb0f4e421a5..6a06a4d55ee35 100644 --- a/lib/internal/Magento/Framework/Indexer/etc/indexer.xsd +++ b/lib/internal/Magento/Framework/Indexer/etc/indexer.xsd @@ -12,13 +12,16 @@ Indexer declaration. - - - - - - - + + + + + + + + + + @@ -217,7 +220,7 @@ - + Indexer Id must be unique. @@ -225,7 +228,6 @@ - @@ -254,4 +256,40 @@ + + + + + + + + Related indexer id must be unique. + + + + + + + + + + + + + Root element of the indexer dependencies. + + + + + + + + Indexer dependency that represents another indexer. + + + + + + + diff --git a/lib/internal/Magento/Framework/Indexer/etc/indexer_merged.xsd b/lib/internal/Magento/Framework/Indexer/etc/indexer_merged.xsd index 849f12a0a66bb..8949d3e6b8e61 100644 --- a/lib/internal/Magento/Framework/Indexer/etc/indexer_merged.xsd +++ b/lib/internal/Magento/Framework/Indexer/etc/indexer_merged.xsd @@ -12,13 +12,16 @@ Indexer declaration. - - - - - - - + + + + + + + + + + @@ -51,6 +54,17 @@ + + + + Indexer ID can contain only [a-zA-Z0-9_]. + + + + + + + @@ -207,7 +221,7 @@ - + Indexer Id must be unique. @@ -215,7 +229,6 @@ - @@ -253,4 +266,40 @@ + + + + + + + + Related indexer id must be unique. + + + + + + + + + + + + + Root element of the indexer dependencies. + + + + + + + + Indexer dependency that represents another indexer. + + + + + + + diff --git a/lib/internal/Magento/Framework/Mview/Test/Unit/View/CollectionTest.php b/lib/internal/Magento/Framework/Mview/Test/Unit/View/CollectionTest.php index 4f9b95e13e7e4..0c2898530e1ee 100644 --- a/lib/internal/Magento/Framework/Mview/Test/Unit/View/CollectionTest.php +++ b/lib/internal/Magento/Framework/Mview/Test/Unit/View/CollectionTest.php @@ -4,92 +4,264 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - namespace Magento\Framework\Mview\Test\Unit\View; +use Magento\Framework\Data\Collection\EntityFactoryInterface; +use Magento\Framework\Indexer\ConfigInterface as IndexerConfigInterface; +use Magento\Framework\Indexer\IndexerInterface; +use Magento\Framework\Mview\ConfigInterface as MviewConfigInterface; +use Magento\Framework\Mview\View\Collection; +use Magento\Framework\Mview\View\State\CollectionFactory; +use Magento\Framework\Mview\View\State\CollectionInterface as StateCollectionInterface; +use Magento\Framework\Mview\View\StateInterface; +use Magento\Framework\Mview\ViewInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + class CollectionTest extends \PHPUnit_Framework_TestCase { - public function testLoadDataAndGetViewsByStateMode() + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var IndexerConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $indexerConfigMock; + + /** + * @var EntityFactoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $entityFactoryMock; + + /** + * @var MviewConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $mviewConfigMock; + + /** + * @var CollectionFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $statesFactoryMock; + + /** + * @var Collection + */ + private $collection; + + public function setUp() { - $indexerIdOne = 'first_indexer_id'; - $indexerIdSecond = 'second_indexer_id'; - - $entityFactory = $this->getMockBuilder( - \Magento\Framework\Data\Collection\EntityFactoryInterface::class - )->disableOriginalConstructor()->setMethods( - ['create'] - )->getMock(); - - $config = $this->getMockBuilder(\Magento\Framework\Mview\ConfigInterface::class)->getMock(); - - $statesFactory = $this->getMockBuilder( - \Magento\Framework\Mview\View\State\CollectionFactory::class - )->disableOriginalConstructor()->setMethods( - ['create'] - )->getMock(); - - $states = $this->getMockBuilder( - \Magento\Framework\Mview\View\State\Collection::class - )->setMethods( - ['getItems'] - )->disableOriginalConstructor()->getMock(); - - $state = $this->getMockForAbstractClass( - \Magento\Framework\Mview\View\StateInterface::class, [], '', false, false, true, - ['getViewId', 'getMode', '__wakeup'] - ); + $this->objectManagerHelper = new ObjectManagerHelper($this); - $state->expects($this->any())->method('getViewId')->will($this->returnValue('second_indexer_id')); + $this->indexerConfigMock = $this->getMockBuilder(IndexerConfigInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); - $state->expects( - $this->any() - )->method( - 'getMode' - )->will( - $this->returnValue(\Magento\Framework\Mview\View\StateInterface::MODE_DISABLED) - ); + $this->entityFactoryMock = $this->getMockBuilder(EntityFactoryInterface::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); - $view = $this->getMockForAbstractClass( - \Magento\Framework\Mview\ViewInterface::class, [], '', false, false, true, - ['load', 'setState', 'getState', '__wakeup'] - ); + $this->mviewConfigMock = $this->getMockBuilder(MviewConfigInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->statesFactoryMock = $this->getMockBuilder(CollectionFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); - $view->expects($this->once())->method('setState')->with($state); - $view->expects($this->any())->method('getState')->will($this->returnValue($state)); - $view->expects($this->any())->method('load')->with($this->logicalOr($indexerIdOne, $indexerIdSecond)); - - $entityFactory->expects( - $this->any() - )->method( - 'create' - )->with( - \Magento\Framework\Mview\ViewInterface::class - )->will( - $this->returnValue($view) + $this->collection = $this->objectManagerHelper->getObject( + Collection::class, + [ + 'entityFactory' => $this->entityFactoryMock, + 'config' => $this->mviewConfigMock, + 'statesFactory' => $this->statesFactoryMock, + 'indexerConfig' => $this->indexerConfigMock, + ] ); + } - $statesFactory->expects($this->once())->method('create')->will($this->returnValue($states)); + /** + * @param array $indexers + * @param array $views + * @param array $stateMode + * @param int $numDisabledViews + * @param int $numEnabledViews + * @dataProvider loadDataAndGetViewsByStateModeDataProvider + */ + public function testLoadDataAndGetViewsByStateMode( + array $indexers, + array $views, + array $stateMode, + $numDisabledViews, + $numEnabledViews + ) { + $this->indexerConfigMock + ->method('getIndexers') + ->willReturn($indexers); - $config->expects( - $this->once() - )->method( - 'getViews' - )->will( - $this->returnValue([$indexerIdOne => 1, $indexerIdSecond => 2]) - ); + $this->mviewConfigMock + ->expects($this->once()) + ->method('getViews') + ->willReturn(array_flip($views)); + + $orderedViews = []; + foreach ($indexers as $indexerData) { + $state = $this->getStateMock(['getMode'], $indexerData); + $state->method('getMode') + ->willReturn($stateMode[$indexerData['indexer_id']]); + $view = $this->getViewMock(['setState', 'getState']); + $view->expects($this->once()) + ->method('setState'); + $view->method('getState') + ->willReturn($state); + $orderedViews[$indexerData['view_id']] = $view; + } + + $emptyView = $this->getViewMock(); + $emptyView->method('load') + ->withConsecutive( + ...array_map( + function ($elem) { + return [$elem]; + }, + array_keys($orderedViews) + ) + ) + ->willReturnOnConsecutiveCalls(...array_values($orderedViews)); - $states->expects($this->any())->method('getItems')->will($this->returnValue([$state])); + $indexer = $this->getIndexerMock(); + $indexer->method('load') + ->willReturnMap(array_map( + function ($elem) { + return [$elem['indexer_id'], $this->getIndexerMock([], $elem)]; + }, + $indexers + )); - $collection = new \Magento\Framework\Mview\View\Collection($entityFactory, $config, $statesFactory); - $this->assertInstanceOf(\Magento\Framework\Mview\View\Collection::class, $collection->loadData()); + $this->entityFactoryMock + ->method('create') + ->willReturnMap([ + [IndexerInterface::class, [], $indexer], + [ViewInterface::class, [], $emptyView] + ]); - $views = $collection->getViewsByStateMode(\Magento\Framework\Mview\View\StateInterface::MODE_DISABLED); - $this->assertCount(2, $views); - $this->assertInstanceOf(\Magento\Framework\Mview\ViewInterface::class, $views[0]); - $this->assertInstanceOf(\Magento\Framework\Mview\ViewInterface::class, $views[1]); + $states = $this->getMockBuilder(StateCollectionInterface::class) + ->getMockForAbstractClass(); + $states->method('getItems') + ->willReturn(array_map( + function ($elem) { + return $this->getStateMock([], ['view_id' => $elem]); + }, + $views + )); - $views = $collection->getViewsByStateMode(\Magento\Framework\Mview\View\StateInterface::MODE_ENABLED); - $this->assertCount(0, $views); + $this->statesFactoryMock + ->expects($this->once()) + ->method('create') + ->willReturn($states); + + $this->assertInstanceOf(\Magento\Framework\Mview\View\Collection::class, $this->collection->loadData()); + + $views = $this->collection->getViewsByStateMode(StateInterface::MODE_DISABLED); + $this->assertCount($numDisabledViews, $views); + foreach ($views as $view) { + $this->assertInstanceOf(ViewInterface::class, $view); + } + + $views = $this->collection->getViewsByStateMode(StateInterface::MODE_ENABLED); + $this->assertCount($numEnabledViews, $views); + } + + /** + * @param array $methods + * @param array $data + * @return StateInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private function getStateMock(array $methods = [], array $data = []) + { + $state = $this->getMockBuilder(StateInterface::class) + ->setMethods(array_merge($methods, ['getViewId'])) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $state->method('getViewId') + ->willReturn($data['view_id'] ?? ''); + return $state; + } + + /** + * @param array $methods + * @return ViewInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private function getViewMock(array $methods = []) + { + $view = $this->getMockBuilder(ViewInterface::class) + ->setMethods(array_merge($methods, ['load'])) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + return $view; + } + + /** + * @param array $methods + * @param array $data + * @return \PHPUnit_Framework_MockObject_MockObject|IndexerInterface + */ + private function getIndexerMock(array $methods = [], array $data = []) + { + /** @var \PHPUnit_Framework_MockObject_MockObject|IndexerInterface $indexer */ + $indexer = $this->getMockBuilder(IndexerInterface::class) + ->setMethods(array_merge($methods, ['getId', 'getViewId'])) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $indexer->method('getId') + ->willReturn($data['indexer_id'] ?? ''); + $indexer->method('getViewId') + ->willReturn($data['view_id'] ?? []); + return $indexer; + } + + /** + * @return array + */ + public function loadDataAndGetViewsByStateModeDataProvider() + { + return [ + 'Indexers with sequence' => [ + 'indexers' => [ + 'indexer_4' => [ + 'indexer_id' => 'indexer_4', + 'view_id' => 'view_4', + ], + 'indexer_2' => [ + 'indexer_id' => 'indexer_2', + 'view_id' => 'view_2', + ], + 'indexer_1' => [ + 'indexer_id' => 'indexer_1', + 'view_id' => 'view_1', + ], + 'indexer_3' => [ + 'indexer_id' => 'indexer_3', + 'view_id' => 'view_3', + ], + ], + 'views' => [ + 'view_1', + 'view_2', + 'view_3', + 'view_4', + ], + 'state_mode' => [ + 'indexer_1' => StateInterface::MODE_DISABLED, + 'indexer_2' => StateInterface::MODE_DISABLED, + 'indexer_3' => StateInterface::MODE_DISABLED, + 'indexer_4' => StateInterface::MODE_ENABLED, + ], + 'num_disabled_views' => 3, + 'num_enabled_views' => 1, + ], + ]; } } diff --git a/lib/internal/Magento/Framework/Mview/View/Collection.php b/lib/internal/Magento/Framework/Mview/View/Collection.php index 91ea4917e2902..118dfa9dc7431 100644 --- a/lib/internal/Magento/Framework/Mview/View/Collection.php +++ b/lib/internal/Magento/Framework/Mview/View/Collection.php @@ -5,6 +5,10 @@ */ namespace Magento\Framework\Mview\View; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Indexer\ConfigInterface; +use Magento\Framework\Indexer\IndexerInterface; + /** * Class \Magento\Framework\Mview\View\Collection * @@ -32,19 +36,28 @@ class Collection extends \Magento\Framework\Data\Collection implements Collectio */ protected $statesFactory; + /** + * @var ConfigInterface + * @since 2.2.0 + */ + private $indexerConfig; + /** * @param \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory * @param \Magento\Framework\Mview\ConfigInterface $config * @param State\CollectionFactory $statesFactory + * @param ConfigInterface $indexerConfig * @since 2.0.0 */ public function __construct( \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory, \Magento\Framework\Mview\ConfigInterface $config, - \Magento\Framework\Mview\View\State\CollectionFactory $statesFactory + \Magento\Framework\Mview\View\State\CollectionFactory $statesFactory, + ConfigInterface $indexerConfig = null ) { $this->config = $config; $this->statesFactory = $statesFactory; + $this->indexerConfig = $indexerConfig ?: ObjectManager::getInstance()->get(ConfigInterface::class); parent::__construct($entityFactory); } @@ -62,10 +75,10 @@ public function loadData($printQuery = false, $logQuery = false) { if (!$this->isLoaded()) { $states = $this->statesFactory->create(); - foreach (array_keys($this->config->getViews()) as $viewId) { + foreach ($this->getOrderedViewIds() as $viewId) { /** @var \Magento\Framework\Mview\ViewInterface $view */ $view = $this->getNewEmptyItem(); - $view->load($viewId); + $view = $view->load($viewId); foreach ($states->getItems() as $state) { /** @var \Magento\Framework\Mview\View\StateInterface $state */ if ($state->getViewId() == $viewId) { @@ -80,6 +93,24 @@ public function loadData($printQuery = false, $logQuery = false) return $this; } + /** + * @return array + * @since 2.2.0 + */ + private function getOrderedViewIds() + { + $orderedViewIds = []; + /** @var IndexerInterface $indexer */ + foreach (array_keys($this->indexerConfig->getIndexers()) as $indexerId) { + $indexer = $this->_entityFactory->create(IndexerInterface::class); + $orderedViewIds[] = $indexer->load($indexerId)->getViewId(); + } + $orderedViewIds = array_filter($orderedViewIds); + $orderedViewIds += array_diff(array_keys($this->config->getViews()), $orderedViewIds); + + return $orderedViewIds; + } + /** * Return views by given state mode * From 3a1d99f8684c996d9d07d5ca7039aebfda9d4afc Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky Date: Wed, 2 Aug 2017 15:56:59 +0300 Subject: [PATCH 10/82] MAGETWO-69967: Double re-indexation of configurable product --- .../Magento/CatalogInventory/Model/Stock/StockItemRepository.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php b/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php index a3903a45c06b6..2cdf12bef8b22 100644 --- a/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php +++ b/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php @@ -77,6 +77,7 @@ class StockItemRepository implements StockItemRepositoryInterface /** * @var Processor + * @deprecated */ protected $indexProcessor; From 45c3f81de941b732eb678140e8bf647f2db77298 Mon Sep 17 00:00:00 2001 From: nmalevanec Date: Wed, 2 Aug 2017 16:31:29 +0300 Subject: [PATCH 11/82] MAGETWO-65703: Fatal error on creation simple product with tier price in custom website via webapi handler --- .../Test/TestCase/ExportAdvancedPricingTest.xml | 1 - .../Catalog/Test/Handler/CatalogProductSimple/Webapi.php | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ExportAdvancedPricingTest.xml b/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ExportAdvancedPricingTest.xml index 06fb80a154a62..8ecaa477da9af 100644 --- a/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ExportAdvancedPricingTest.xml +++ b/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ExportAdvancedPricingTest.xml @@ -49,7 +49,6 @@ - MAGETWO-65703: Fatal error on creation simple product with tier price in custom website via webapi handler price_scope_website csv_with_advanced_pricing diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Webapi.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Webapi.php index 8ea5b7f838061..50dd376f648ce 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Webapi.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Webapi.php @@ -288,7 +288,11 @@ protected function prepareTierPrice() $priceInfo['qty'] = $priceInfo['price_qty']; unset($priceInfo['price_qty']); - unset($priceInfo['website_id']); + if (isset($priceInfo['website_id'])) { + $priceInfo['extension_attributes']['website_id'] = $priceInfo['website_id']; + unset($priceInfo['website_id']); + } + unset($priceInfo['delete']); $this->fields['product']['tier_prices'][$key] = $priceInfo; From 00d1d27f09e71183168142d3a8f57da30f88618e Mon Sep 17 00:00:00 2001 From: nmalevanec Date: Thu, 3 Aug 2017 15:46:09 +0300 Subject: [PATCH 12/82] MAGETWO-65703: Fatal error on creation simple product with tier price in custom website via webapi handler --- .../Test/TestCase/ExportAdvancedPricingTest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ExportAdvancedPricingTest.xml b/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ExportAdvancedPricingTest.xml index 8ecaa477da9af..9f19ff4cb00a8 100644 --- a/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ExportAdvancedPricingTest.xml +++ b/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ExportAdvancedPricingTest.xml @@ -48,7 +48,7 @@ - + price_scope_website csv_with_advanced_pricing From 731894179e8eaa0104336e3f8d9ef8805b23e737 Mon Sep 17 00:00:00 2001 From: RomanKis Date: Fri, 4 Aug 2017 11:00:26 +0300 Subject: [PATCH 13/82] MAGETWO-60470: [Import Custom options] An error occurs on attempt to save the product with imported options --- .../Magento/Catalog/Test/Repository/Product/CustomOptions.xml | 2 +- .../Test/TestCase/Product/CreateSimpleProductEntityTest.xml | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Product/CustomOptions.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Product/CustomOptions.xml index 158a9b395aeb7..c98a3dd46f7d1 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Product/CustomOptions.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Product/CustomOptions.xml @@ -55,7 +55,7 @@ 20 Percent sku_drop_down_row_2 - 0 + 1 diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.xml index 8f13a676c9e27..98e4b71e2d72e 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.xml @@ -328,7 +328,6 @@ - MAGETWO-60470: [Import Custom options] An error occurs on attempt to save the product with imported options Create product wit suite of custom options simple-product-%isolation% Simple Product %isolation% @@ -341,7 +340,6 @@ options_suite simple_options_suite catalogProductSimple::with_two_custom_option,catalogProductSimple::with_all_custom_option - MAGETWO-58181: All kind of Custom Options have dynamic view on Bamboo From 668b1797dac5fdf9f4a8115162cf0c927d0f3d7c Mon Sep 17 00:00:00 2001 From: RomanKis Date: Mon, 7 Aug 2017 16:35:32 +0300 Subject: [PATCH 14/82] MAGETWO-60470: [Import Custom options] An error occurs on attempt to save the product with imported options --- .../Test/TestCase/Product/CreateSimpleProductEntityTest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.xml index 98e4b71e2d72e..c5dc7f0ab7df5 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.xml @@ -327,7 +327,7 @@ - + Create product wit suite of custom options simple-product-%isolation% Simple Product %isolation% From edb4e3ab7d318fedcbd94312354b67a1ca2b2872 Mon Sep 17 00:00:00 2001 From: RomanKis Date: Mon, 7 Aug 2017 16:51:46 +0300 Subject: [PATCH 15/82] MAGETWO-60470: [Import Custom options] An error occurs on attempt to save the product with imported options --- .../Test/TestCase/Product/CreateSimpleProductEntityTest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.xml index c5dc7f0ab7df5..b307e28d42232 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.xml @@ -327,7 +327,7 @@ - + Create product wit suite of custom options simple-product-%isolation% Simple Product %isolation% From f4844e547d70665560f6c6fe5c627d94ac1e0b50 Mon Sep 17 00:00:00 2001 From: Nadiya Syvokonenko Date: Tue, 8 Aug 2017 10:01:22 +0300 Subject: [PATCH 16/82] MAGETWO-71349: Email Return-Path Setting is not used as Return-Path in Mail Transport --- .../Model/Mail/TransportInterfacePlugin.php | 55 ----- app/code/Magento/Email/Model/Template.php | 11 +- app/code/Magento/Email/Model/Transport.php | 122 ++++++++++ .../Test/Unit/Model/Mail/TransportTest.php | 212 ++++++++++++++++++ app/code/Magento/Email/etc/di.xml | 3 +- .../Magento/Framework/Mail/Transport.php | 4 +- .../Framework/Mail/TransportInterface.php | 2 +- 7 files changed, 346 insertions(+), 63 deletions(-) delete mode 100644 app/code/Magento/Email/Model/Mail/TransportInterfacePlugin.php create mode 100644 app/code/Magento/Email/Model/Transport.php create mode 100644 app/code/Magento/Email/Test/Unit/Model/Mail/TransportTest.php diff --git a/app/code/Magento/Email/Model/Mail/TransportInterfacePlugin.php b/app/code/Magento/Email/Model/Mail/TransportInterfacePlugin.php deleted file mode 100644 index cbfbf0649df8a..0000000000000 --- a/app/code/Magento/Email/Model/Mail/TransportInterfacePlugin.php +++ /dev/null @@ -1,55 +0,0 @@ -scopeConfig = $scopeConfig; - } - - /** - * Omit email sending if disabled - * - * @param TransportInterface $subject - * @param \Closure $proceed - * @return void - * @throws MailException - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function aroundSendMessage( - TransportInterface $subject, - \Closure $proceed - ) { - if (!$this->scopeConfig->isSetFlag(self::XML_PATH_SYSTEM_SMTP_DISABLE, ScopeInterface::SCOPE_STORE)) { - $proceed(); - } - } -} diff --git a/app/code/Magento/Email/Model/Template.php b/app/code/Magento/Email/Model/Template.php index 37beca03c9de9..6a7b03f3b784e 100644 --- a/app/code/Magento/Email/Model/Template.php +++ b/app/code/Magento/Email/Model/Template.php @@ -42,16 +42,23 @@ class Template extends AbstractTemplate implements \Magento\Framework\Mail\TemplateInterface { /** - * Configuration path for default email templates + * Configuration path to source of Return-Path and whether it should be set at all + * @deprecated + * @see \Magento\Email\Model\Transport::XML_PATH_SENDING_SET_RETURN_PATH */ const XML_PATH_SENDING_SET_RETURN_PATH = 'system/smtp/set_return_path'; + /** + * Configuration path for custom Return-Path email + * @deprecated + * @see \Magento\Email\Model\Transport::XML_PATH_SENDING_RETURN_PATH_EMAIL + */ const XML_PATH_SENDING_RETURN_PATH_EMAIL = 'system/smtp/return_path_email'; /** * Config path to mail sending setting that shows if email communications are disabled * @deprecated - * @see \Magento\Email\Model\Mail\TransportInterfacePlugin::XML_PATH_SYSTEM_SMTP_DISABLE + * @see \Magento\Email\Model\Transport::XML_PATH_SYSTEM_SMTP_DISABLE */ const XML_PATH_SYSTEM_SMTP_DISABLE = 'system/smtp/disable'; diff --git a/app/code/Magento/Email/Model/Transport.php b/app/code/Magento/Email/Model/Transport.php new file mode 100644 index 0000000000000..bab71f9d0f3dd --- /dev/null +++ b/app/code/Magento/Email/Model/Transport.php @@ -0,0 +1,122 @@ +transport = $transport; + $this->message = $message; + $this->scopeConfig = $scopeConfig; + } + + /** + * Sets Return-Path to email if necessary, and sends email if it is allowed by System Configurations + * + * @return void + * @throws \Magento\Framework\Exception\MailException + */ + public function sendMessage() + { + try { + if (!$this->scopeConfig->isSetFlag(self::XML_PATH_SYSTEM_SMTP_DISABLE, ScopeInterface::SCOPE_STORE)) { + /* configuration of whether return path should be set or no. Possible values are: + * 0 - no + * 1 - yes (set value as FROM address) + * 2 - use custom value + * @see Magento\Config\Model\Config\Source\Yesnocustom + */ + $isSetReturnPath = $this->scopeConfig->getValue( + self::XML_PATH_SENDING_SET_RETURN_PATH, + ScopeInterface::SCOPE_STORE + ); + $returnPathValue = $this->scopeConfig->getValue( + self::XML_PATH_SENDING_RETURN_PATH_EMAIL, + ScopeInterface::SCOPE_STORE + ); + + if ($isSetReturnPath == '1') { + $this->message->setReturnPath($this->message->getFrom()); + } elseif ($isSetReturnPath == '2' && $returnPathValue !== null) { + $this->message->setReturnPath($returnPathValue); + } + $this->transport->send($this->message); + } + } catch (\Exception $e) { + throw new MailException(__($e->getMessage()), $e); + } + } + + /** + * @inheritdoc + */ + public function getMessage() + { + return $this->message; + } +} diff --git a/app/code/Magento/Email/Test/Unit/Model/Mail/TransportTest.php b/app/code/Magento/Email/Test/Unit/Model/Mail/TransportTest.php new file mode 100644 index 0000000000000..88455fd2412ff --- /dev/null +++ b/app/code/Magento/Email/Test/Unit/Model/Mail/TransportTest.php @@ -0,0 +1,212 @@ +transportMock = $this->getMockBuilder(\Zend_Mail_Transport_Sendmail::class) + ->getMock(); + + $this->messageMock = $this->getMockBuilder(\Magento\Framework\Mail\Message::class) + ->getMock(); + + $this->scopeConfigMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) + ->getMockForAbstractClass(); + + $this->model = new Transport($this->transportMock, $this->messageMock, $this->scopeConfigMock); + } + + /** + * Tests that if any exception was caught, \Magento\Framework\Exception\MailException will thrown + * + * @expectedException \Magento\Framework\Exception\MailException + */ + public function testSendMessageException() + { + $this->scopeConfigMock->expects($this->once()) + ->method('isSetFlag') + ->willThrowException(new \Exception('some exception')); + $this->model->sendMessage(); + } + + /** + * Tests that if sending emails is disabled in System Configuration, send nothing + */ + public function testSendMessageSmtpDisabled() + { + $this->scopeConfigMock->expects($this->once()) + ->method('isSetFlag') + ->with(Transport::XML_PATH_SYSTEM_SMTP_DISABLE, ScopeInterface::SCOPE_STORE) + ->willReturn(true); + $this->transportMock->expects($this->never()) + ->method('send'); + $this->model->sendMessage(); + } + + /** + * Tests that if sending Return-Path was disabled or email was not provided, - this header won't be set + * + * @param string|int|null $returnPathSet + * @param string|null $returnPathEmail + * + * @dataProvider sendMessageWithoutReturnPathDataProvider + */ + public function testSendMessageWithoutReturnPath($returnPathSet, $returnPathEmail = null) + { + $this->prepareSendingMessage($returnPathSet, $returnPathEmail); + + $this->messageMock->expects($this->never()) + ->method('setReturnPath'); + $this->transportMock->expects($this->once()) + ->method('send'); + $this->model->sendMessage(); + } + + /** + * Tests that if sending Return-Path was disabled, this header won't be set + * + * @param string|int|null $returnPathSet + * @param string|null $emailFrom + * + * @dataProvider sendMessageWithDefaultReturnPathDataProvider + */ + public function testSendMessageWithDefaultReturnPath($returnPathSet, $emailFrom) + { + $this->prepareSendingMessage($returnPathSet, null); + + $this->messageMock->expects($this->once()) + ->method('setReturnPath') + ->with($emailFrom); + $this->messageMock->expects($this->once()) + ->method('getFrom') + ->willReturn($emailFrom); + $this->transportMock->expects($this->once()) + ->method('send'); + $this->model->sendMessage(); + } + + /** + * Tests that if sending Return-Path was disabled, this header won't be set + * + * @param string|int|null $returnPathSet + * @param string|null $emailFrom + * + * @dataProvider sendMessageWithCustomReturnPathDataProvider + */ + public function testSendMessageWithCustomReturnPath($returnPathSet, $emailFrom) + { + $this->prepareSendingMessage($returnPathSet, $emailFrom); + + $this->messageMock->expects($this->once()) + ->method('setReturnPath') + ->with($emailFrom); + $this->messageMock->expects($this->never()) + ->method('getFrom') + ->willReturn($emailFrom); + $this->transportMock->expects($this->once()) + ->method('send'); + $this->model->sendMessage(); + } + + /** + * Tests retrieving message object + */ + public function testGetMessage() + { + $this->assertEquals($this->messageMock, $this->model->getMessage()); + } + + /** + * Executes all main sets for sending message + * + * @param string|int|null $returnPathSet + * @param string|null $returnPathEmail + */ + private function prepareSendingMessage($returnPathSet, $returnPathEmail) + { + $this->scopeConfigMock->expects($this->once()) + ->method('isSetFlag') + ->with(Transport::XML_PATH_SYSTEM_SMTP_DISABLE, ScopeInterface::SCOPE_STORE) + ->willReturn(false); + + $map = [ + [Transport::XML_PATH_SENDING_SET_RETURN_PATH, ScopeInterface::SCOPE_STORE, null, $returnPathSet], + [Transport::XML_PATH_SENDING_RETURN_PATH_EMAIL, ScopeInterface::SCOPE_STORE, null, $returnPathEmail] + ]; + $this->scopeConfigMock->expects($this->exactly(2)) + ->method('getValue') + ->willReturnMap($map); + } + + /** + * Data provider for testSendMessageWithoutReturnPath + * @return array + */ + public function sendMessageWithoutReturnPathDataProvider() + { + return [ + [0], + ['0'], + [3], + ['2', null], + [2, null], + ]; + } + + /** + * Data provider for testSendMessageWithDefaultReturnPath + * @return array + */ + public function sendMessageWithDefaultReturnPathDataProvider() + { + return [ + [1, 'test@exemple.com'], + ['1', 'test@exemple.com'], + ['1', ''] + ]; + } + + /** + * Data provider for testSendMessageWithCustomReturnPath + * @return array + */ + public function sendMessageWithCustomReturnPathDataProvider() + { + return [ + [2, 'test@exemple.com'], + ['2', 'test@exemple.com'], + ['2', ''] + ]; + } +} diff --git a/app/code/Magento/Email/etc/di.xml b/app/code/Magento/Email/etc/di.xml index 5bcefd105767b..b9f67c57df683 100644 --- a/app/code/Magento/Email/etc/di.xml +++ b/app/code/Magento/Email/etc/di.xml @@ -8,7 +8,7 @@ - + @@ -59,7 +59,6 @@ - diff --git a/lib/internal/Magento/Framework/Mail/Transport.php b/lib/internal/Magento/Framework/Mail/Transport.php index 4bfba5746b94a..b9ee7b1a6b271 100644 --- a/lib/internal/Magento/Framework/Mail/Transport.php +++ b/lib/internal/Magento/Framework/Mail/Transport.php @@ -47,9 +47,7 @@ public function sendMessage() } /** - * Get message - * - * @return string + * @inheritdoc */ public function getMessage() { diff --git a/lib/internal/Magento/Framework/Mail/TransportInterface.php b/lib/internal/Magento/Framework/Mail/TransportInterface.php index 31c0072f31095..cccfad885838e 100644 --- a/lib/internal/Magento/Framework/Mail/TransportInterface.php +++ b/lib/internal/Magento/Framework/Mail/TransportInterface.php @@ -23,7 +23,7 @@ public function sendMessage(); /** * Get message * - * @return string + * @return \Magento\Framework\Mail\MessageInterface * @since 100.2.0 */ public function getMessage(); From 695dcfa2551e69e5c8881a3438e9518d36fafc51 Mon Sep 17 00:00:00 2001 From: Nadiya Syvokonenko Date: Tue, 8 Aug 2017 10:41:37 +0300 Subject: [PATCH 17/82] MAGETWO-71349: Email Return-Path Setting is not used as Return-Path in Mail Transport - fix Unit test for new version --- .../Unit/Model/{Mail => }/TransportTest.php | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) rename app/code/Magento/Email/Test/Unit/Model/{Mail => }/TransportTest.php (90%) diff --git a/app/code/Magento/Email/Test/Unit/Model/Mail/TransportTest.php b/app/code/Magento/Email/Test/Unit/Model/TransportTest.php similarity index 90% rename from app/code/Magento/Email/Test/Unit/Model/Mail/TransportTest.php rename to app/code/Magento/Email/Test/Unit/Model/TransportTest.php index 88455fd2412ff..05d1df0cf4a3d 100644 --- a/app/code/Magento/Email/Test/Unit/Model/Mail/TransportTest.php +++ b/app/code/Magento/Email/Test/Unit/Model/TransportTest.php @@ -6,12 +6,14 @@ namespace Magento\Email\Test\Unit\Model; use Magento\Email\Model\Transport; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Mail\Message; use Magento\Store\Model\ScopeInterface; /** * Covers \Magento\Email\Model\Transport */ -class TransportTest extends \PHPUnit_Framework_TestCase +class TransportTest extends \PHPUnit\Framework\TestCase { /** * @var \Zend_Mail_Transport_Sendmail|\PHPUnit_Framework_MockObject_MockObject @@ -19,12 +21,12 @@ class TransportTest extends \PHPUnit_Framework_TestCase private $transportMock; /** - * @var \Magento\Framework\Mail\Message|\PHPUnit_Framework_MockObject_MockObject + * @var Message|\PHPUnit_Framework_MockObject_MockObject */ private $messageMock; /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ private $scopeConfigMock; @@ -35,14 +37,11 @@ class TransportTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $this->transportMock = $this->getMockBuilder(\Zend_Mail_Transport_Sendmail::class) - ->getMock(); + $this->transportMock = $this->createMock(\Zend_Mail_Transport_Sendmail::class); - $this->messageMock = $this->getMockBuilder(\Magento\Framework\Mail\Message::class) - ->getMock(); + $this->messageMock = $this->createMock(Message::class); - $this->scopeConfigMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) - ->getMockForAbstractClass(); + $this->scopeConfigMock = $this->getMockForAbstractClass(ScopeConfigInterface::class); $this->model = new Transport($this->transportMock, $this->messageMock, $this->scopeConfigMock); } From abc94270a67e6eb5043f277acb2cd95114bdb464 Mon Sep 17 00:00:00 2001 From: Nadiya Syvokonenko Date: Wed, 9 Aug 2017 09:32:49 +0300 Subject: [PATCH 18/82] MAGETWO-71349: Email Return-Path Setting is not used as Return-Path in Mail Transport --- app/code/Magento/Email/Model/Transport.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Email/Model/Transport.php b/app/code/Magento/Email/Model/Transport.php index bab71f9d0f3dd..165f5f9ede108 100644 --- a/app/code/Magento/Email/Model/Transport.php +++ b/app/code/Magento/Email/Model/Transport.php @@ -60,7 +60,8 @@ class Transport implements TransportInterface * @param MessageInterface $message Email message object * @param ScopeConfigInterface $scopeConfig Core store config * @param string|array|\Zend_Config|null $parameters Config options for sendmail parameters - * @throws \InvalidArgumentException when $message is not instance of \Zend_Mail + * + * @throws \InvalidArgumentException when $message is not an instance of \Zend_Mail */ public function __construct( \Zend_Mail_Transport_Sendmail $transport, @@ -79,7 +80,7 @@ public function __construct( * Sets Return-Path to email if necessary, and sends email if it is allowed by System Configurations * * @return void - * @throws \Magento\Framework\Exception\MailException + * @throws MailException */ public function sendMessage() { From 0a4275a3646f12ef4e3ba456d3e3c3f50492b051 Mon Sep 17 00:00:00 2001 From: Iryna Lagno Date: Wed, 9 Aug 2017 12:48:30 +0300 Subject: [PATCH 19/82] MAGETWO-71179: Customers are unsubscribed from the newsletter after confirming their account --- .../Magento/Newsletter/Model/Subscriber.php | 3 ++ .../Test/Unit/Model/SubscriberTest.php | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php index f69f26a8d64ed..0e0c0dd1b6ac4 100644 --- a/app/code/Magento/Newsletter/Model/Subscriber.php +++ b/app/code/Magento/Newsletter/Model/Subscriber.php @@ -555,6 +555,9 @@ protected function _updateCustomerSubscription($customerId, $subscribe) } elseif ($isConfirmNeed) { $status = self::STATUS_NOT_ACTIVE; } + } elseif (($this->getStatus() == self::STATUS_UNCONFIRMED) && ($customerData->getConfirmation() === null)) { + $status = self::STATUS_SUBSCRIBED; + $sendInformationEmail = true; } else { $status = self::STATUS_UNSUBSCRIBED; } diff --git a/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php b/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php index 52b087cf0a0e5..7716f4744a922 100644 --- a/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php +++ b/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php @@ -273,6 +273,35 @@ public function testSubscribeCustomerById1() $this->assertEquals(\Magento\Newsletter\Model\Subscriber::STATUS_NOT_ACTIVE, $this->subscriber->getStatus()); } + public function testSubscribeCustomerByIdAfterConfirmation() + { + $customerId = 1; + $customerDataMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) + ->getMock(); + $this->customerRepository->expects($this->atLeastOnce()) + ->method('getById') + ->with($customerId)->willReturn($customerDataMock); + $this->resource->expects($this->atLeastOnce()) + ->method('loadByCustomerData') + ->with($customerDataMock) + ->willReturn( + [ + 'subscriber_id' => 1, + 'subscriber_status' => 4 + ] + ); + $customerDataMock->expects($this->atLeastOnce())->method('getId')->willReturn('id'); + $this->resource->expects($this->atLeastOnce())->method('save')->willReturnSelf(); + $customerDataMock->expects($this->once())->method('getStoreId')->willReturn('store_id'); + $customerDataMock->expects($this->once())->method('getEmail')->willReturn('email'); + $this->sendEmailCheck(); + $this->customerAccountManagement->expects($this->never())->method('getConfirmationStatus'); + $this->scopeConfig->expects($this->atLeastOnce())->method('getValue')->with()->willReturn(true); + + $this->subscriber->updateSubscription($customerId); + $this->assertEquals(\Magento\Newsletter\Model\Subscriber::STATUS_SUBSCRIBED, $this->subscriber->getStatus()); + } + public function testUnsubscribe() { $this->resource->expects($this->once())->method('save')->willReturnSelf(); From 6cdfd6825389f6bdc55b5486ddf2d464f3ceece4 Mon Sep 17 00:00:00 2001 From: Max Lesechko Date: Wed, 9 Aug 2017 13:08:22 +0300 Subject: [PATCH 20/82] MAGETWO-69559: Declaration of dependencies for indexers - fix merge conflicts --- .../Command/AbstractIndexerManageCommand.php | 3 -- .../Console/Command/IndexerReindexCommand.php | 25 ++------- app/code/Magento/Indexer/Model/Processor.php | 10 ---- .../Magento/Indexer/Setup/RecurringData.php | 5 -- .../AbstractIndexerCommandCommonSetup.php | 23 ++------- .../Command/IndexerReindexCommandTest.php | 6 +-- .../Command/IndexerResetStateCommandTest.php | 2 +- .../Unit/Model/Indexer/CollectionTest.php | 2 +- .../Indexer/Test/Unit/Model/ProcessorTest.php | 51 ++++++------------- .../Indexer/Model/Config/ConverterTest.php | 11 ++-- .../Test/Unit/Config/ConverterTest.php | 8 +-- .../Mview/Test/Unit/View/CollectionTest.php | 2 +- .../Framework/Mview/View/Collection.php | 9 ---- 13 files changed, 38 insertions(+), 119 deletions(-) diff --git a/app/code/Magento/Indexer/Console/Command/AbstractIndexerManageCommand.php b/app/code/Magento/Indexer/Console/Command/AbstractIndexerManageCommand.php index 0530bf54ff1cf..f1cc7d8a72972 100644 --- a/app/code/Magento/Indexer/Console/Command/AbstractIndexerManageCommand.php +++ b/app/code/Magento/Indexer/Console/Command/AbstractIndexerManageCommand.php @@ -11,7 +11,6 @@ /** * An Abstract class for all Indexer related commands. - * @since 2.0.0 */ abstract class AbstractIndexerManageCommand extends AbstractIndexerCommand { @@ -26,7 +25,6 @@ abstract class AbstractIndexerManageCommand extends AbstractIndexerCommand * @param InputInterface $input * @return IndexerInterface[] * @throws \InvalidArgumentException - * @since 2.0.0 */ protected function getIndexers(InputInterface $input) { @@ -57,7 +55,6 @@ protected function getIndexers(InputInterface $input) * Get list of options and arguments for the command * * @return mixed - * @since 2.0.0 */ public function getInputList() { diff --git a/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php index 9213add4cfb8c..43065ada77271 100644 --- a/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php +++ b/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php @@ -18,31 +18,26 @@ /** * Command to run indexers - * @since 2.0.0 */ class IndexerReindexCommand extends AbstractIndexerManageCommand { /** * @var array - * @since 2.1.0 */ private $sharedIndexesComplete = []; /** * @var ConfigInterface - * @since 2.1.0 */ private $config; /** * @var IndexerRegistry - * @since 2.2.0 */ private $indexerRegistry; /** * @var DependencyInfoProvider|null - * @since 2.2.0 */ private $dependencyInfoProvider; @@ -50,7 +45,6 @@ class IndexerReindexCommand extends AbstractIndexerManageCommand * @param ObjectManagerFactory $objectManagerFactory * @param IndexerRegistry|null $indexerRegistry * @param DependencyInfoProvider|null $dependencyInfoProvider - * @since 2.2.0 */ public function __construct( ObjectManagerFactory $objectManagerFactory, @@ -64,7 +58,6 @@ public function __construct( /** * {@inheritdoc} - * @since 2.0.0 */ protected function configure() { @@ -77,7 +70,6 @@ protected function configure() /** * {@inheritdoc} - * @since 2.0.0 */ protected function execute(InputInterface $input, OutputInterface $output) { @@ -113,7 +105,6 @@ protected function execute(InputInterface $input, OutputInterface $output) /** * {@inheritdoc} Returns the ordered list of specified indexers and related indexers. - * @since 2.2.0 */ protected function getIndexers(InputInterface $input) { @@ -162,7 +153,6 @@ protected function getIndexers(InputInterface $input) * * @param string $indexerId * @return array - * @since 2.2.0 */ private function getRelatedIndexerIds(string $indexerId) { @@ -183,7 +173,6 @@ private function getRelatedIndexerIds(string $indexerId) * * @param string $indexerId * @return array - * @since 2.2.0 */ private function getDependentIndexerIds(string $indexerId) { @@ -208,7 +197,6 @@ private function getDependentIndexerIds(string $indexerId) * @param IndexerInterface $indexer * @return void * @throws LocalizedException - * @since 2.1.0 */ private function validateIndexerStatus(IndexerInterface $indexer) { @@ -227,7 +215,6 @@ private function validateIndexerStatus(IndexerInterface $indexer) * * @param string $sharedIndex * @return array - * @since 2.1.0 */ private function getIndexerIdsBySharedIndex($sharedIndex) { @@ -246,7 +233,6 @@ private function getIndexerIdsBySharedIndex($sharedIndex) * * @param string $sharedIndex * @return $this - * @since 2.1.0 */ private function validateSharedIndex($sharedIndex) { @@ -272,8 +258,7 @@ private function validateSharedIndex($sharedIndex) * Get config * * @return ConfigInterface - * @deprecated 2.1.0 - * @since 2.1.0 + * @deprecated 100.1.0 */ private function getConfig() { @@ -284,11 +269,8 @@ private function getConfig() } /** - * Get indexer registry. - * * @return IndexerRegistry - * @deprecated 2.2.0 - * @since 2.2.0 + * @deprecated 100.2.0 */ private function getIndexerRegistry() { @@ -300,8 +282,7 @@ private function getIndexerRegistry() /** * @return DependencyInfoProvider - * @deprecated 2.2.0 - * @since 2.2.0 + * @deprecated 100.2.0 */ private function getDependencyInfoProvider() { diff --git a/app/code/Magento/Indexer/Model/Processor.php b/app/code/Magento/Indexer/Model/Processor.php index 34cdc42e02729..292424a830728 100644 --- a/app/code/Magento/Indexer/Model/Processor.php +++ b/app/code/Magento/Indexer/Model/Processor.php @@ -13,31 +13,26 @@ /** * Class \Magento\Indexer\Model\Processor * - * @since 2.0.0 */ class Processor { /** * @var ConfigInterface - * @since 2.0.0 */ protected $config; /** * @var IndexerInterfaceFactory - * @since 2.0.0 */ protected $indexerFactory; /** * @var Indexer\CollectionFactory - * @since 2.0.0 */ protected $indexersFactory; /** * @var \Magento\Framework\Mview\ProcessorInterface - * @since 2.0.0 */ protected $mviewProcessor; @@ -46,7 +41,6 @@ class Processor * @param IndexerInterfaceFactory $indexerFactory * @param Indexer\CollectionFactory $indexersFactory * @param \Magento\Framework\Mview\ProcessorInterface $mviewProcessor - * @since 2.0.0 */ public function __construct( ConfigInterface $config, @@ -64,7 +58,6 @@ public function __construct( * Regenerate indexes for all invalid indexers * * @return void - * @since 2.0.0 */ public function reindexAllInvalid() { @@ -95,7 +88,6 @@ public function reindexAllInvalid() * Regenerate indexes for all indexers * * @return void - * @since 2.0.0 */ public function reindexAll() { @@ -110,7 +102,6 @@ public function reindexAll() * Update indexer views * * @return void - * @since 2.0.0 */ public function updateMview() { @@ -121,7 +112,6 @@ public function updateMview() * Clean indexer view changelogs * * @return void - * @since 2.0.0 */ public function clearChangelog() { diff --git a/app/code/Magento/Indexer/Setup/RecurringData.php b/app/code/Magento/Indexer/Setup/RecurringData.php index 1476e00d06f33..1f6ea09ba853f 100644 --- a/app/code/Magento/Indexer/Setup/RecurringData.php +++ b/app/code/Magento/Indexer/Setup/RecurringData.php @@ -14,19 +14,16 @@ /** * Recurring data upgrade for indexer module - * @since 2.2.0 */ class RecurringData implements InstallDataInterface { /** * @var IndexerInterfaceFactory - * @since 2.2.0 */ private $indexerFactory; /** * @var ConfigInterface - * @since 2.2.0 */ private $configInterface; @@ -35,7 +32,6 @@ class RecurringData implements InstallDataInterface * * @param IndexerInterfaceFactory $indexerFactory * @param ConfigInterface $configInterface - * @since 2.2.0 */ public function __construct( IndexerInterfaceFactory $indexerFactory, @@ -47,7 +43,6 @@ public function __construct( /** * {@inheritdoc} - * @since 2.2.0 */ public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) { diff --git a/app/code/Magento/Indexer/Test/Unit/Console/Command/AbstractIndexerCommandCommonSetup.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/AbstractIndexerCommandCommonSetup.php index d5b1bc4ced69c..829e5bd5100de 100644 --- a/app/code/Magento/Indexer/Test/Unit/Console/Command/AbstractIndexerCommandCommonSetup.php +++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/AbstractIndexerCommandCommonSetup.php @@ -9,10 +9,7 @@ use Magento\Framework\Indexer\IndexerInterface; use Magento\Indexer\Model\Indexer\Collection; -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class AbstractIndexerCommandCommonSetup extends \PHPUnit_Framework_TestCase +class AbstractIndexerCommandCommonSetup extends \PHPUnit\Framework\TestCase { /** * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\ObjectManager\ConfigLoader @@ -51,24 +48,12 @@ class AbstractIndexerCommandCommonSetup extends \PHPUnit_Framework_TestCase protected function setUp() { - $this->objectManagerFactory = $this->getMock( - \Magento\Framework\App\ObjectManagerFactory::class, - [], - [], - '', - false - ); + $this->objectManagerFactory = $this->createMock(\Magento\Framework\App\ObjectManagerFactory::class); $this->objectManager = $this->getMockForAbstractClass(\Magento\Framework\ObjectManagerInterface::class); $this->objectManagerFactory->expects($this->any())->method('create')->willReturn($this->objectManager); - $this->stateMock = $this->getMock(\Magento\Framework\App\State::class, [], [], '', false); - $this->configLoaderMock = $this->getMock( - \Magento\Framework\App\ObjectManager\ConfigLoader::class, - [], - [], - '', - false - ); + $this->stateMock = $this->createMock(\Magento\Framework\App\State::class); + $this->configLoaderMock = $this->createMock(\Magento\Framework\App\ObjectManager\ConfigLoader::class); $this->collectionFactory = $this->getMockBuilder(\Magento\Indexer\Model\Indexer\CollectionFactory::class) ->disableOriginalConstructor() diff --git a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerReindexCommandTest.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerReindexCommandTest.php index f216b270472ff..4877ceaaec85b 100644 --- a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerReindexCommandTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerReindexCommandTest.php @@ -54,7 +54,7 @@ class IndexerReindexCommandTest extends AbstractIndexerCommandCommonSetup public function setUp() { $this->objectManagerHelper = new ObjectManagerHelper($this); - $this->configMock = $this->getMock(\Magento\Indexer\Model\Config::class, [], [], '', false); + $this->configMock = $this->createMock(\Magento\Indexer\Model\Config::class); $this->indexerRegistryMock = $this->getMockBuilder(IndexerRegistry::class) ->disableOriginalConstructor() ->getMock(); @@ -440,8 +440,8 @@ public function testExecuteWithExceptionInGetIndexers() $this->initIndexerCollectionByItems([$indexerOne]); $indexerOne->expects($this->never())->method('getTitle'); - $this->setExpectedException( - \InvalidArgumentException::class, + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage( "The following requested index types are not supported: '" . join("', '", $inputIndexers) . "'." . PHP_EOL . 'Supported types: ' diff --git a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerResetStateCommandTest.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerResetStateCommandTest.php index b4fb67026a49e..76969b275697e 100644 --- a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerResetStateCommandTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerResetStateCommandTest.php @@ -33,7 +33,7 @@ public function testExecute() ); $this->initIndexerCollectionByItems([$indexerOne]); - $stateMock = $this->getMock(\Magento\Indexer\Model\Indexer\State::class, [], [], '', false); + $stateMock = $this->createMock(\Magento\Indexer\Model\Indexer\State::class); $stateMock->expects($this->exactly(1)) ->method('setStatus') ->with(\Magento\Framework\Indexer\StateInterface::STATUS_INVALID) diff --git a/app/code/Magento/Indexer/Test/Unit/Model/Indexer/CollectionTest.php b/app/code/Magento/Indexer/Test/Unit/Model/Indexer/CollectionTest.php index 7e9b3d0d57892..2489111fa6753 100644 --- a/app/code/Magento/Indexer/Test/Unit/Model/Indexer/CollectionTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Model/Indexer/CollectionTest.php @@ -14,7 +14,7 @@ use Magento\Indexer\Model\ResourceModel\Indexer\State\Collection as StateCollection; use Magento\Indexer\Model\ResourceModel\Indexer\State\CollectionFactory; -class CollectionTest extends \PHPUnit_Framework_TestCase +class CollectionTest extends \PHPUnit\Framework\TestCase { /** * @var ObjectManagerHelper diff --git a/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php b/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php index 089eeaa6ef651..4b3b8eeead04d 100644 --- a/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php @@ -8,7 +8,7 @@ use Magento\Framework\Indexer\IndexerInterfaceFactory; use Magento\Framework\Indexer\StateInterface; -class ProcessorTest extends \PHPUnit_Framework_TestCase +class ProcessorTest extends \PHPUnit\Framework\TestCase { /** * @var \Magento\Indexer\Model\Processor|\PHPUnit_Framework_MockObject_MockObject @@ -46,16 +46,13 @@ protected function setUp() true, ['getIndexers'] ); - $this->indexerFactoryMock = $this->getMockBuilder(IndexerInterfaceFactory::class) - ->setMethods(['create']) - ->getMockForAbstractClass(); - - $this->indexersFactoryMock = $this->getMock( + $this->indexerFactoryMock = $this->createPartialMock( + IndexerInterfaceFactory::class, + ['create'] + ); + $this->indexersFactoryMock = $this->createPartialMock( \Magento\Indexer\Model\Indexer\CollectionFactory::class, - ['create'], - [], - '', - false + ['create'] ); $this->viewProcessorMock = $this->getMockForAbstractClass( \Magento\Framework\Mview\ProcessorInterface::class, @@ -77,13 +74,7 @@ public function testReindexAllInvalid() $this->configMock->expects($this->once())->method('getIndexers')->will($this->returnValue($indexers)); - $state1Mock = $this->getMock( - \Magento\Indexer\Model\Indexer\State::class, - ['getStatus', '__wakeup'], - [], - '', - false - ); + $state1Mock = $this->createPartialMock(\Magento\Indexer\Model\Indexer\State::class, ['getStatus', '__wakeup']); $state1Mock->expects( $this->once() )->method( @@ -91,23 +82,14 @@ public function testReindexAllInvalid() )->will( $this->returnValue(StateInterface::STATUS_INVALID) ); - $indexer1Mock = $this->getMock( + $indexer1Mock = $this->createPartialMock( \Magento\Indexer\Model\Indexer::class, - ['load', 'getState', 'reindexAll'], - [], - '', - false + ['load', 'getState', 'reindexAll'] ); $indexer1Mock->expects($this->once())->method('getState')->will($this->returnValue($state1Mock)); $indexer1Mock->expects($this->once())->method('reindexAll'); - $state2Mock = $this->getMock( - \Magento\Indexer\Model\Indexer\State::class, - ['getStatus', '__wakeup'], - [], - '', - false - ); + $state2Mock = $this->createPartialMock(\Magento\Indexer\Model\Indexer\State::class, ['getStatus', '__wakeup']); $state2Mock->expects( $this->once() )->method( @@ -115,12 +97,9 @@ public function testReindexAllInvalid() )->will( $this->returnValue(StateInterface::STATUS_VALID) ); - $indexer2Mock = $this->getMock( + $indexer2Mock = $this->createPartialMock( \Magento\Indexer\Model\Indexer::class, - ['load', 'getState', 'reindexAll'], - [], - '', - false + ['load', 'getState', 'reindexAll'] ); $indexer2Mock->expects($this->never())->method('reindexAll'); $indexer2Mock->expects($this->once())->method('getState')->will($this->returnValue($state2Mock)); @@ -133,11 +112,11 @@ public function testReindexAllInvalid() public function testReindexAll() { - $indexerMock = $this->getMock(\Magento\Indexer\Model\Indexer::class, [], [], '', false); + $indexerMock = $this->createMock(\Magento\Indexer\Model\Indexer::class); $indexerMock->expects($this->exactly(2))->method('reindexAll'); $indexers = [$indexerMock, $indexerMock]; - $indexersMock = $this->getMock(\Magento\Indexer\Model\Indexer\Collection::class, [], [], '', false); + $indexersMock = $this->createMock(\Magento\Indexer\Model\Indexer\Collection::class); $this->indexersFactoryMock->expects($this->once())->method('create')->will($this->returnValue($indexersMock)); $indexersMock->expects($this->once())->method('getItems')->will($this->returnValue($indexers)); diff --git a/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/ConverterTest.php b/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/ConverterTest.php index 554ba0712b69d..7786d274abb53 100644 --- a/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/ConverterTest.php +++ b/dev/tests/integration/testsuite/Magento/Indexer/Model/Config/ConverterTest.php @@ -7,7 +7,7 @@ use Magento\Framework\Exception\ConfigurationMismatchException; -class ConverterTest extends \PHPUnit_Framework_TestCase +class ConverterTest extends \PHPUnit\Framework\TestCase { /** * @var \Magento\Framework\Indexer\Config\Converter @@ -40,7 +40,8 @@ public function testConverterWithCircularDependency() $path = $pathFiles . '/indexer_with_circular_dependency.xml'; $domDocument = new \DOMDocument(); $domDocument->load($path); - $this->setExpectedException(ConfigurationMismatchException::class, 'Circular dependency references from'); + $this->expectException(ConfigurationMismatchException::class); + $this->expectExceptionMessage('Circular dependency references from'); $this->model->convert($domDocument); } @@ -53,10 +54,8 @@ public function testConverterWithDependencyOnNotExistingIndexer() $path = $pathFiles . '/dependency_on_not_existing_indexer.xml'; $domDocument = new \DOMDocument(); $domDocument->load($path); - $this->setExpectedException( - ConfigurationMismatchException::class, - "Dependency declaration 'indexer_4' in 'indexer_2' to the non-existing indexer." - ); + $this->expectException(ConfigurationMismatchException::class); + $this->expectExceptionMessage("Dependency declaration 'indexer_4' in 'indexer_2' to the non-existing indexer."); $this->model->convert($domDocument); } } diff --git a/lib/internal/Magento/Framework/Indexer/Test/Unit/Config/ConverterTest.php b/lib/internal/Magento/Framework/Indexer/Test/Unit/Config/ConverterTest.php index 3c9161651f238..ffbe8bd85d5f6 100644 --- a/lib/internal/Magento/Framework/Indexer/Test/Unit/Config/ConverterTest.php +++ b/lib/internal/Magento/Framework/Indexer/Test/Unit/Config/ConverterTest.php @@ -7,7 +7,7 @@ use Magento\Framework\Exception\ConfigurationMismatchException; -class ConverterTest extends \PHPUnit_Framework_TestCase +class ConverterTest extends \PHPUnit\Framework\TestCase { /** * @var \Magento\Framework\Indexer\Config\Converter|\PHPUnit_Framework_MockObject_MockObject @@ -37,7 +37,8 @@ public function testConvertWithCircularDependencies($inputXml, $exceptionMessage { $dom = new \DOMDocument(); $dom->loadXML($inputXml); - $this->setExpectedException(ConfigurationMismatchException::class, $exceptionMessage); + $this->expectException(ConfigurationMismatchException::class); + $this->expectExceptionMessage($exceptionMessage); $this->_model->convert($dom); } @@ -75,7 +76,8 @@ public function testConvertWithDependencyOnNotExistingIndexer($inputXml, $except { $dom = new \DOMDocument(); $dom->loadXML($inputXml); - $this->setExpectedException(ConfigurationMismatchException::class, $exceptionMessage); + $this->expectException(ConfigurationMismatchException::class); + $this->expectExceptionMessage($exceptionMessage); $this->_model->convert($dom); } diff --git a/lib/internal/Magento/Framework/Mview/Test/Unit/View/CollectionTest.php b/lib/internal/Magento/Framework/Mview/Test/Unit/View/CollectionTest.php index 0c2898530e1ee..0c4dfdefbba0d 100644 --- a/lib/internal/Magento/Framework/Mview/Test/Unit/View/CollectionTest.php +++ b/lib/internal/Magento/Framework/Mview/Test/Unit/View/CollectionTest.php @@ -17,7 +17,7 @@ use Magento\Framework\Mview\ViewInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; -class CollectionTest extends \PHPUnit_Framework_TestCase +class CollectionTest extends \PHPUnit\Framework\TestCase { /** * @var ObjectManagerHelper diff --git a/lib/internal/Magento/Framework/Mview/View/Collection.php b/lib/internal/Magento/Framework/Mview/View/Collection.php index 118dfa9dc7431..11e5649b96a6d 100644 --- a/lib/internal/Magento/Framework/Mview/View/Collection.php +++ b/lib/internal/Magento/Framework/Mview/View/Collection.php @@ -12,7 +12,6 @@ /** * Class \Magento\Framework\Mview\View\Collection * - * @since 2.0.0 */ class Collection extends \Magento\Framework\Data\Collection implements CollectionInterface { @@ -20,25 +19,21 @@ class Collection extends \Magento\Framework\Data\Collection implements Collectio * Item object class name * * @var string - * @since 2.0.0 */ protected $_itemObjectClass = \Magento\Framework\Mview\ViewInterface::class; /** * @var \Magento\Framework\Mview\ConfigInterface - * @since 2.0.0 */ protected $config; /** * @var \Magento\Framework\Mview\View\State\CollectionFactory - * @since 2.0.0 */ protected $statesFactory; /** * @var ConfigInterface - * @since 2.2.0 */ private $indexerConfig; @@ -47,7 +42,6 @@ class Collection extends \Magento\Framework\Data\Collection implements Collectio * @param \Magento\Framework\Mview\ConfigInterface $config * @param State\CollectionFactory $statesFactory * @param ConfigInterface $indexerConfig - * @since 2.0.0 */ public function __construct( \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory, @@ -69,7 +63,6 @@ public function __construct( * @return \Magento\Framework\Mview\View\CollectionInterface * * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * @since 2.0.0 */ public function loadData($printQuery = false, $logQuery = false) { @@ -95,7 +88,6 @@ public function loadData($printQuery = false, $logQuery = false) /** * @return array - * @since 2.2.0 */ private function getOrderedViewIds() { @@ -116,7 +108,6 @@ private function getOrderedViewIds() * * @param string $mode * @return \Magento\Framework\Mview\ViewInterface[] - * @since 2.0.0 */ public function getViewsByStateMode($mode) { From 0e60eac1103a4551379fc3548c09a6461834bb7e Mon Sep 17 00:00:00 2001 From: Leonid Poluyanov Date: Wed, 9 Aug 2017 15:57:29 +0300 Subject: [PATCH 21/82] MAGETWO-71431: ImportProductsTest random failures --- .../Test/TestCase/ImportProductsTest.xml | 12 +++++ .../CatalogImportExport/Test/etc/testcase.xml | 3 +- .../Indexer/Test/TestStep/ReindexStep.php | 50 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 dev/tests/functional/tests/app/Magento/Indexer/Test/TestStep/ReindexStep.php diff --git a/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ImportProductsTest.xml b/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ImportProductsTest.xml index cb29537c8dea0..2c7f6665df0aa 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ImportProductsTest.xml +++ b/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ImportProductsTest.xml @@ -29,6 +29,10 @@ + + catalog_category_product + catalog_product_category + @@ -45,6 +49,10 @@ 4 catalogProductSimple::default_in_custom_website catalogProductSimple::default + + catalog_category_product + catalog_product_category + @@ -60,6 +68,10 @@ 7 7 catalogProductSimple::default_in_custom_website + + catalog_category_product + catalog_product_category + diff --git a/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/etc/testcase.xml b/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/etc/testcase.xml index 3438787d668f7..be8bf8b89dd16 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/etc/testcase.xml +++ b/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/etc/testcase.xml @@ -11,6 +11,7 @@ - + + diff --git a/dev/tests/functional/tests/app/Magento/Indexer/Test/TestStep/ReindexStep.php b/dev/tests/functional/tests/app/Magento/Indexer/Test/TestStep/ReindexStep.php new file mode 100644 index 0000000000000..1a3cb4a034765 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Indexer/Test/TestStep/ReindexStep.php @@ -0,0 +1,50 @@ +indexer = $indexer; + $this->indexerType = $indexerType; + } + + /** + * Run reindex process. + * All indexers will be refreshed in a case of empty $indexerType array. + * + * @return void + */ + public function run() + { + $this->indexer->reindex($this->indexerType); + } +} From 62976552433f522c71e76d1d12f0db47bb6bf340 Mon Sep 17 00:00:00 2001 From: Dmytro Vilchynskyi Date: Wed, 9 Aug 2017 17:09:49 +0300 Subject: [PATCH 22/82] MAGETWO-70571: There is no possibility to activate DEBUG logging in production mode - update after merging --- app/code/Magento/Deploy/Test/Unit/Model/ModeTest.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/code/Magento/Deploy/Test/Unit/Model/ModeTest.php b/app/code/Magento/Deploy/Test/Unit/Model/ModeTest.php index 7d943e17eb9ce..f80c6cb69f1a9 100644 --- a/app/code/Magento/Deploy/Test/Unit/Model/ModeTest.php +++ b/app/code/Magento/Deploy/Test/Unit/Model/ModeTest.php @@ -154,6 +154,9 @@ public function testEnableProductionMode() State::PARAM_MODE => State::MODE_DEVELOPER, ], ]; + $this->configProvider->expects($this->any()) + ->method('getConfigs') + ->willReturn([]); $this->writerMock->expects($this->once()) ->method("saveConfig") ->willReturnCallback(function ($data) use (&$dataStorage) { @@ -187,6 +190,12 @@ public function testEnableDeveloperModeOnFail() State::PARAM_MODE => State::MODE_DEVELOPER, ], ]; + $this->readerMock->expects($this->any()) + ->method('load') + ->willReturn([State::PARAM_MODE => State::MODE_DEVELOPER]); + $this->configProvider->expects($this->any()) + ->method('getConfigs') + ->willReturn([]); $this->writerMock->expects($this->exactly(2)) ->method("saveConfig") ->withConsecutive( From e54a4ad0e373b5725646147370980e2d7340f0e4 Mon Sep 17 00:00:00 2001 From: Alex Paliarush Date: Wed, 9 Aug 2017 17:18:28 -0500 Subject: [PATCH 23/82] MAGETWO-63175: Layered navigation price step range displays in the base currency --- .../Model/Layer/Filter/Price.php | 8 +-- .../Unit/Model/Layer/Filter/PriceTest.php | 1 + .../Model/Layer/Filter/PriceTest.php | 52 ++++++++++++++----- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Price.php b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Price.php index 9f54bd279d906..108f1b9f4fd8d 100644 --- a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Price.php +++ b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Price.php @@ -175,6 +175,9 @@ public function getCurrencyRate() */ protected function _renderRangeLabel($fromPrice, $toPrice) { + $fromPrice = empty($fromPrice) ? 0 : $fromPrice * $this->getCurrencyRate(); + $toPrice = empty($toPrice) ? $toPrice : $toPrice * $this->getCurrencyRate(); + $formattedFromPrice = $this->priceCurrency->format($fromPrice); if ($toPrice === '') { return __('%1 and above', $formattedFromPrice); @@ -261,10 +264,7 @@ private function prepareData($key, $count) if ($to == '*') { $to = $this->getTo($to); } - $label = $this->_renderRangeLabel( - empty($from) ? 0 : $from * $this->getCurrencyRate(), - empty($to) ? $to : $to * $this->getCurrencyRate() - ); + $label = $this->_renderRangeLabel($from, $to); $value = $from . '-' . $to . $this->dataProvider->getAdditionalRequestData(); $data = [ diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/PriceTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/PriceTest.php index 9631773cad2c9..abad58a6876d3 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/PriceTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/PriceTest.php @@ -225,6 +225,7 @@ function ($field) use ($requestVar, $priceId) { ->with('price') ->will($this->returnSelf()); + $this->target->setCurrencyRate(1); $this->target->apply($this->request); } diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/PriceTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/PriceTest.php index cd4ee02616b3a..451553113af2c 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/PriceTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/PriceTest.php @@ -5,6 +5,8 @@ */ namespace Magento\CatalogSearch\Model\Layer\Filter; +use Magento\TestFramework\Helper\Bootstrap; + /** * Test class for \Magento\CatalogSearch\Model\Layer\Filter\Price. * @@ -19,26 +21,31 @@ class PriceTest extends \PHPUnit\Framework\TestCase */ protected $_model; + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + protected function setUp() { - $category = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + $this->objectManager = Bootstrap::getObjectManager(); + $category = $this->objectManager->create( \Magento\Catalog\Model\Category::class ); $category->load(4); - $layer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->get(\Magento\Catalog\Model\Layer\Category::class); + $layer = $this->objectManager->get(\Magento\Catalog\Model\Layer\Category::class); $layer->setCurrentCategory($category); - $this->_model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(\Magento\CatalogSearch\Model\Layer\Filter\Price::class, ['layer' => $layer]); + $this->_model = $this->objectManager->create( + \Magento\CatalogSearch\Model\Layer\Filter\Price::class, + ['layer' => $layer] + ); } public function testApplyNothing() { $this->assertEmpty($this->_model->getData('price_range')); - /** @var $objectManager \Magento\TestFramework\ObjectManager */ - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); /** @var $request \Magento\TestFramework\Request */ - $request = $objectManager->get(\Magento\TestFramework\Request::class); + $request = $this->objectManager->get(\Magento\TestFramework\Request::class); $this->_model->apply($request); $this->assertEmpty($this->_model->getData('price_range')); @@ -47,10 +54,8 @@ public function testApplyNothing() public function testApplyInvalid() { $this->assertEmpty($this->_model->getData('price_range')); - /** @var $objectManager \Magento\TestFramework\ObjectManager */ - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); /** @var $request \Magento\TestFramework\Request */ - $request = $objectManager->get(\Magento\TestFramework\Request::class); + $request = $this->objectManager->get(\Magento\TestFramework\Request::class); $request->setParam('price', 'non-numeric'); $this->_model->apply($request); @@ -62,12 +67,31 @@ public function testApplyInvalid() */ public function testApplyManual() { - /** @var $objectManager \Magento\TestFramework\ObjectManager */ - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); /** @var $request \Magento\TestFramework\Request */ - $request = $objectManager->get(\Magento\TestFramework\Request::class); + $request = $this->objectManager->get(\Magento\TestFramework\Request::class); + $request->setParam('price', '10-20'); + $this->_model->apply($request); + } + + /** + * Make sure that currency rate is used to calculate label for applied price filter + */ + public function testApplyWithCustomCurrencyRate() + { + /** @var $request \Magento\TestFramework\Request */ + $request = $this->objectManager->get(\Magento\TestFramework\Request::class); + $request->setParam('price', '10-20'); + $this->_model->setCurrencyRate(10); + $this->_model->apply($request); + + $filters = $this->_model->getLayer()->getState()->getFilters(); + $this->assertArrayHasKey(0, $filters); + $this->assertEquals( + '$100.00 - $199.99', + (string)$filters[0]->getLabel() + ); } public function testGetSetCustomerGroupId() From 30081f9e7f410c956b3016df25258e4872a9ec97 Mon Sep 17 00:00:00 2001 From: Oleksandr Miroshnichenko Date: Tue, 1 Aug 2017 11:49:55 +0300 Subject: [PATCH 24/82] MAGETWO-71030: Sound from video from parent configurable product plays when start play video of child --- .../view/frontend/web/js/fotorama-add-video-events.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js b/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js index f78e424d741eb..03ce42bf25c4a 100644 --- a/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js +++ b/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js @@ -617,6 +617,7 @@ define([ var videoSettings; videoSettings = this.options.videoSettings[0]; + $image.find('.' + this.PV).remove(); $image.append( '