diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ImportWithNotExistImagesTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ImportWithNotExistImagesTest.php new file mode 100644 index 0000000000000..e317ee119b180 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ImportWithNotExistImagesTest.php @@ -0,0 +1,225 @@ +get(DeleteTopicRelatedMessages::class); + $deleteMessages->execute(self::TOPIC); + } + + /** + * @inheritdoc + */ + protected function setUp(): void + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->queue = $this->objectManager->create(Queue::class, ['queueName' => 'export']); + $this->messageEncoder = $this->objectManager->get(MessageEncoder::class); + $this->consumer = $this->objectManager->get(Consumer::class); + $this->directory = $this->objectManager->get(Filesystem::class)->getDirectoryWrite(DirectoryList::VAR_DIR); + $this->csvReader = $this->objectManager->get(Csv::class); + $this->import = $this->objectManager->get(ProductFactory::class)->create(); + $this->csvFactory = $this->objectManager->get(CsvFactory::class); + $this->fileSystem = $this->objectManager->get(Filesystem::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + } + + /** + * @inheritdoc + */ + protected function tearDown(): void + { + if ($this->filePath && $this->directory->isExist($this->filePath)) { + $this->directory->delete($this->filePath); + } + + parent::tearDown(); + } + + /** + * @magentoDataFixture Magento/CatalogImportExport/_files/export_queue_product_with_images.php + * + * @return void + */ + public function testImportWithUnexistingImages(): void + { + $this->exportProducts(); + $this->assertTrue($this->directory->isExist($this->filePath), 'Products were not imported to file'); + $fileContent = $this->csvReader->getData($this->directory->getAbsolutePath($this->filePath)); + $this->assertCount(2, $fileContent); + $this->updateFileImagesToInvalidValues(); + $this->import->setParameters([ + 'entity' => Product::ENTITY, + 'behavior' => ImportModel::BEHAVIOR_ADD_UPDATE, + ]); + $this->assertImportErrors(); + $this->assertProductImages('/m/a/magento_image.jpg', 'simple'); + } + + /** + * Export products from queue to csv file + * + * @return void + */ + private function exportProducts(): void + { + $envelope = $this->queue->dequeue(); + $decodedMessage = $this->messageEncoder->decode(self::TOPIC, $envelope->getBody()); + $this->consumer->process($decodedMessage); + $this->filePath = 'export/' . $decodedMessage->getFileName(); + } + + /** + * Change image names in an export file + * + * @return void + */ + private function updateFileImagesToInvalidValues(): void + { + $absolutePath = $this->directory->getAbsolutePath($this->filePath); + $csv = $this->csvReader->getData($absolutePath); + $imagesKeys = ['base_image', 'small_image', 'thumbnail_image']; + $imagesPositions = []; + foreach ($imagesKeys as $key) { + $imagesPositions[] = array_search($key, $csv[0]); + } + + foreach ($imagesPositions as $imagesPosition) { + $csv[1][$imagesPosition] = '/m/a/invalid_image.jpg'; + } + + $this->csvReader->appendData($absolutePath, $csv); + } + + /** + * Get export csv file + * + * @param string $file + * @return CsvSource + */ + private function prepareFile(string $file): CsvSource + { + return $this->csvFactory->create([ + 'file' => $file, + 'directory' => $this->fileSystem->getDirectoryWrite(DirectoryList::VAR_DIR), + ]); + } + + /** + * Assert import errors + * + * @return void + */ + private function assertImportErrors(): void + { + $validationErrors = $this->import->setSource($this->prepareFile($this->filePath))->validateData(); + $this->assertEmpty($validationErrors->getAllErrors()); + $this->import->getErrorAggregator()->clear(); + $this->import->importData(); + $importErrors = $this->import->getErrorAggregator()->getAllErrors(); + $this->assertCount(1, $importErrors); + $importError = reset($importErrors); + $this->assertEquals( + RowValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE, + $importError->getErrorCode() + ); + $errorMsg = (string)__('Imported resource (image) could not be downloaded ' . + 'from external resource due to timeout or access permissions'); + $this->assertEquals($errorMsg, $importError->getErrorMessage()); + } + + /** + * Assert product images were not changed after import + * + * @param string $imageName + * @param string $productSku + * @return void + */ + private function assertProductImages(string $imageName, string $productSku): void + { + $product = $this->productRepository->get($productSku); + $this->assertEquals($imageName, $product->getImage()); + $this->assertEquals($imageName, $product->getSmallImage()); + $this->assertEquals($imageName, $product->getThumbnail()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/export_queue_product_with_images.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/export_queue_product_with_images.php new file mode 100644 index 0000000000000..24e24b6aa4542 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/export_queue_product_with_images.php @@ -0,0 +1,28 @@ +requireDataFixture('Magento/Catalog/_files/product_with_image.php'); + +$objectManager = Bootstrap::getObjectManager(); +/** @var ExportInfoFactory $exportInfoFactory */ +$exportInfoFactory = $objectManager->get(ExportInfoFactory::class); +/** @var PublisherInterface $messagePublisher */ +$messagePublisher = $objectManager->get(PublisherInterface::class); +$dataObject = $exportInfoFactory->create( + 'csv', + ProductAttributeInterface::ENTITY_TYPE_CODE, + [ProductInterface::SKU => 'simple'], + [] +); +$messagePublisher->publish('import_export.export', $dataObject); diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/export_queue_product_with_images_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/export_queue_product_with_images_rollback.php new file mode 100644 index 0000000000000..07bab53b7015b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/export_queue_product_with_images_rollback.php @@ -0,0 +1,17 @@ +get(DeleteTopicRelatedMessages::class); +$deleteTopicRelatedMessages->execute('import_export.export'); + +Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/product_with_image_rollback.php'); diff --git a/dev/tests/integration/testsuite/Magento/Config/Controller/Adminhtml/System/Config/SaveTest.php b/dev/tests/integration/testsuite/Magento/Config/Controller/Adminhtml/System/Config/SaveTest.php new file mode 100644 index 0000000000000..adf91b0b6b051 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Config/Controller/Adminhtml/System/Config/SaveTest.php @@ -0,0 +1,179 @@ +configLoader = $this->_objectManager->get(Loader::class); + $this->scopeResolverPool = $this->_objectManager->get(ScopeResolverPool::class); + } + + /** + * @dataProvider saveConfigDataProvider + * @magentoDbIsolation enabled + * @param array $params + * @param array $post + * @return void + */ + public function testSaveConfig(array $params, array $post): void + { + $expectedPathValue = $this->prepareExpectedPathValue($params['section'], $post['groups']); + $this->dispatchWithParams($params, $post); + $this->assertSessionMessages( + $this->containsEqual((string)__('You saved the configuration.')), + MessageInterface::TYPE_SUCCESS + ); + $this->assertPathValue($expectedPathValue); + } + + /** + * @return array + */ + public function saveConfigDataProvider(): array + { + return [ + 'configure_shipping_origin' => [ + 'params' => ['section' => 'shipping'], + 'post' => [ + 'groups' => [ + 'origin' => [ + 'fields' => [ + 'country_id' => ['value' => 'CH'], + 'region_id' => ['value' => '107'], + 'postcode' => ['value' => '3005'], + 'city' => ['value' => 'Bern'], + 'street_line1' => ['value' => 'Weinbergstrasse 4'], + 'street_line2' => ['value' => 'Suite 1'], + ], + ], + ], + ], + ], + 'configure_multi_shipping_options' => [ + 'params' => ['section' => 'multishipping'], + 'post' => [ + 'groups' => [ + 'options' => [ + 'fields' => [ + 'checkout_multiple' => ['value' => '1'], + 'checkout_multiple_maximum_qty' => ['value' => '99'], + ], + ], + ], + ], + ], + 'configure_flat_rate_shipping_method' => [ + 'params' => ['section' => 'carriers'], + 'post' => [ + 'groups' => [ + 'flatrate' => [ + 'fields' => [ + 'active' => ['value' => '1'], + 'type' => ['value' => 'I'], + 'price' => ['value' => '5.00'], + 'sallowspecific' => ['value' => '0'], + ], + ], + ], + ], + ], + ]; + } + + /** + * Prepare expected path value array. + * + * @param string $section + * @param array $groups + * @return array + */ + private function prepareExpectedPathValue(string $section, array $groups): array + { + foreach ($groups as $groupId => $groupData) { + $groupPath = $section . '/' . $groupId; + foreach ($groupData['fields'] as $fieldId => $fieldData) { + $path = $groupPath . '/' . $fieldId; + $expectedData[$groupPath][$path] = $fieldData['value']; + } + } + + return $expectedData ?? []; + } + + /** + * Check that the values for the paths in the config data were saved successfully. + * + * @param array $expectedPathValue + * @return void + */ + private function assertPathValue(array $expectedPathValue): void + { + $scope = $this->scopeResolverPool->get(ScopeInterface::SCOPE_DEFAULT)->getScope(); + foreach ($expectedPathValue as $groupPath => $groupData) { + $actualPathValue = $this->configLoader->getConfigByPath( + $groupPath, + $scope->getScopeType(), + $scope->getId(), + false + ); + foreach ($groupData as $fieldPath => $fieldValue) { + $this->assertArrayHasKey( + $fieldPath, + $actualPathValue, + sprintf('The expected config setting was not saved in the database. Path: %s', $fieldPath) + ); + $this->assertEquals( + $fieldValue, + $actualPathValue[$fieldPath], + sprintf('The expected value of the config setting is not correct. Path: %s', $fieldPath) + ); + } + } + } + + /** + * Dispatch request with params + * + * @param array $params + * @param array $postParams + * @return void + */ + private function dispatchWithParams(array $params = [], array $postParams = []): void + { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST) + ->setParams($params) + ->setPostValue($postParams); + $this->dispatch('backend/admin/system_config/save'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Model/Plugin/EavAttributeTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Model/Plugin/EavAttributeTest.php new file mode 100644 index 0000000000000..57c44ba2e48fc --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/Model/Plugin/EavAttributeTest.php @@ -0,0 +1,122 @@ +objectManager = Bootstrap::getObjectManager(); + $this->productAttributeRepository = $this->objectManager->get(ProductAttributeRepositoryInterface::class); + $this->swatchResource = $this->objectManager->get(SwatchResource::class); + } + + /** + * @return void + */ + public function testEavAttributePluginIsRegistered(): void + { + $pluginInfo = $this->objectManager->get(PluginList::class)->get(Attribute::class); + $this->assertSame(EavAttribute::class, $pluginInfo['save_swatches_option_params']['instance']); + } + + /** + * @magentoDataFixture Magento/Swatches/_files/text_swatch_attribute.php + * + * @return void + */ + public function testChangeAttributeToDropdown(): void + { + $attribute = $this->productAttributeRepository->get('test_configurable'); + $options = $attribute->getOptions(); + unset($options[0]); + $optionsIds = $this->collectOptionsIds($options); + $attribute->addData($this->prepareOptions($options)); + $attribute->setData(Swatch::SWATCH_INPUT_TYPE_KEY, Swatch::SWATCH_INPUT_TYPE_DROPDOWN); + $attribute->beforeSave(); + $this->assertEmpty($this->fetchSwatchOptions($optionsIds), 'Swatch options were not deleted'); + } + + /** + * Prepare options + * + * @param array $options + * @return array + */ + private function prepareOptions(array $options): array + { + foreach ($options as $key => $option) { + $preparedOptions['option']['order'][$option->getValue()] = $key; + $preparedOptions['option']['value'][$option->getValue()] = [$option->getLabel()]; + $preparedOptions['option']['delete'][$option->getValue()] = ''; + } + + return $preparedOptions ?? []; + } + + /** + * Collect options ids + * + * @param array $options + * @return array + */ + private function collectOptionsIds(array $options): array + { + foreach ($options as $option) { + $optionsIds[] = $option->getValue(); + } + + return $optionsIds ?? []; + } + + /** + * Fetch related to provided ids records from eav_attribute_option_swatch table + * + * @param $optionsIds + * @return array + */ + private function fetchSwatchOptions(array $optionsIds): array + { + $connection = $this->swatchResource->getConnection(); + $select = $connection->select()->from(['main_table' => $this->swatchResource->getMainTable()]) + ->where('main_table.option_id IN (?)', $optionsIds); + + return $connection->fetchAll($select); + } +}