Skip to content

Commit a8743bb

Browse files
committed
MC-37070: Create automated test for "Import products with shared images"
1 parent 3c3c8ed commit a8743bb

File tree

4 files changed

+339
-2
lines changed

4 files changed

+339
-2
lines changed

dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/UpdateHandlerTest.php

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace Magento\Catalog\Model\Product\Gallery;
1010

11+
use Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface;
1112
use Magento\Catalog\Api\Data\ProductInterface;
1213
use Magento\Catalog\Api\ProductRepositoryInterface;
1314
use Magento\Catalog\Model\Product;
@@ -351,6 +352,23 @@ public function testExecuteWithTwoImagesOnStoreView(): void
351352
}
352353
}
353354

355+
/**
356+
* @magentoDataFixture Magento/Catalog/_files/product_with_image.php
357+
* @magentoDataFixture Magento/Catalog/_files/second_product_simple.php
358+
*
359+
* @return void
360+
*/
361+
public function testDeleteSharedImage(): void
362+
{
363+
$product = $this->getProduct(null, 'simple');
364+
$this->duplicateMediaGalleryForProduct('/m/a/magento_image.jpg', 'simple2');
365+
$secondProduct = $this->getProduct(null, 'simple2');
366+
$this->updateHandler->execute($this->prepareRemoveImage($product), []);
367+
$product = $this->getProduct(null, 'simple');
368+
$this->assertEmpty($product->getMediaGalleryImages()->getItems());
369+
$this->checkProductImageExist($secondProduct, '/m/a/magento_image.jpg');
370+
}
371+
354372
/**
355373
* @inheritdoc
356374
*/
@@ -371,11 +389,13 @@ protected function tearDown(): void
371389
* Returns current product.
372390
*
373391
* @param int|null $storeId
392+
* @param string|null $sku
374393
* @return ProductInterface|Product
375394
*/
376-
private function getProduct(?int $storeId = null): ProductInterface
395+
private function getProduct(?int $storeId = null, ?string $sku = null): ProductInterface
377396
{
378-
return $this->productRepository->get('simple', false, $storeId, true);
397+
$sku = $sku ?: 'simple';
398+
return $this->productRepository->get($sku, false, $storeId, true);
379399
}
380400

381401
/**
@@ -464,6 +484,84 @@ public function testDeleteWithMultiWebsites(): void
464484
$this->assertArrayNotHasKey($secondStoreId, $imageRolesPerStore);
465485
}
466486

487+
/**
488+
* Check product image link and product image exist
489+
*
490+
* @param ProductInterface $product
491+
* @param string $imagePath
492+
* @return void
493+
*/
494+
private function checkProductImageExist(ProductInterface $product, string $imagePath): void
495+
{
496+
$productImageItem = $product->getMediaGalleryImages()->getFirstItem();
497+
$this->assertEquals($imagePath, $productImageItem->getFile());
498+
$productImageFile = $productImageItem->getPath();
499+
$this->assertNotEmpty($productImageFile);
500+
$this->assertTrue($this->mediaDirectory->getDriver()->isExists($productImageFile));
501+
$this->fileName = $productImageFile;
502+
}
503+
504+
/**
505+
* Prepare the product to remove image
506+
*
507+
* @param ProductInterface $product
508+
* @return ProductInterface
509+
*/
510+
private function prepareRemoveImage(ProductInterface $product): ProductInterface
511+
{
512+
$item = $product->getMediaGalleryImages()->getFirstItem();
513+
$item->setRemoved('1');
514+
$galleryData = [
515+
'images' => [
516+
(int)$item->getValueId() => $item->getData(),
517+
]
518+
];
519+
$product->setData(ProductInterface::MEDIA_GALLERY, $galleryData);
520+
$product->setStoreId(0);
521+
522+
return $product;
523+
}
524+
525+
/**
526+
* Duplicate media gallery entries for a product
527+
*
528+
* @param string $imagePath
529+
* @param string $productSku
530+
* @return void
531+
*/
532+
private function duplicateMediaGalleryForProduct(string $imagePath, string $productSku): void
533+
{
534+
$product = $this->getProduct(null, $productSku);
535+
$connect = $this->galleryResource->getConnection();
536+
$select = $connect->select()->from($this->galleryResource->getMainTable())->where('value = ?', $imagePath);
537+
$res = $connect->fetchRow($select);
538+
$value_id = $res['value_id'];
539+
unset($res['value_id']);
540+
$rows = [
541+
'attribute_id' => $res['attribute_id'],
542+
'value' => $res['value'],
543+
ProductAttributeMediaGalleryEntryInterface::MEDIA_TYPE => $res['media_type'],
544+
ProductAttributeMediaGalleryEntryInterface::DISABLED => $res['disabled'],
545+
];
546+
$connect->insert($this->galleryResource->getMainTable(), $rows);
547+
$select = $connect->select()
548+
->from($this->galleryResource->getTable(Gallery::GALLERY_VALUE_TABLE))
549+
->where('value_id = ?', $value_id);
550+
$res = $connect->fetchRow($select);
551+
$newValueId = (int)$value_id + 1;
552+
$rows = [
553+
'value_id' => $newValueId,
554+
'store_id' => $res['store_id'],
555+
ProductAttributeMediaGalleryEntryInterface::LABEL => $res['label'],
556+
ProductAttributeMediaGalleryEntryInterface::POSITION => $res['position'],
557+
ProductAttributeMediaGalleryEntryInterface::DISABLED => $res['disabled'],
558+
'row_id' => $product->getRowId(),
559+
];
560+
$connect->insert($this->galleryResource->getTable(Gallery::GALLERY_VALUE_TABLE), $rows);
561+
$rows = ['value_id' => $newValueId, 'row_id' => $product->getRowId()];
562+
$connect->insert($this->galleryResource->getTable(Gallery::GALLERY_VALUE_TO_ENTITY_TABLE), $rows);
563+
}
564+
467565
/**
468566
* @param Product $product
469567
* @param array $roles
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CatalogImportExport\Model\Import;
9+
10+
use Magento\Catalog\Api\ProductRepositoryInterface;
11+
use Magento\Catalog\Model\Product as ProductEntity;
12+
use Magento\Catalog\Model\Product\Media\ConfigInterface;
13+
use Magento\Framework\App\Filesystem\DirectoryList;
14+
use Magento\Framework\Exception\NoSuchEntityException;
15+
use Magento\Framework\Filesystem;
16+
use Magento\Framework\Filesystem\Driver\File;
17+
use Magento\Framework\ObjectManagerInterface;
18+
use Magento\ImportExport\Model\Import;
19+
use Magento\ImportExport\Model\Import\Source\Csv;
20+
use Magento\ImportExport\Model\Import\Source\CsvFactory;
21+
use Magento\ImportExport\Model\ResourceModel\Import\Data;
22+
use Magento\TestFramework\Helper\Bootstrap;
23+
use PHPUnit\Framework\TestCase;
24+
25+
/**
26+
* Checks that product import with same images can be successfully done
27+
*
28+
* @magentoAppArea adminhtml
29+
* @magentoDbIsolation enabled
30+
*
31+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
32+
*/
33+
class ImportWithSharedImagesTest extends TestCase
34+
{
35+
/** @var ObjectManagerInterface */
36+
private $objectManager;
37+
38+
/** @var Filesystem */
39+
private $fileSystem;
40+
41+
/** @var ProductRepositoryInterface */
42+
private $productRepository;
43+
44+
/** @var File */
45+
private $fileDriver;
46+
47+
/** @var Import */
48+
private $import;
49+
50+
/** @var ConfigInterface */
51+
private $mediaConfig;
52+
53+
/** @var array */
54+
private $appParams;
55+
56+
/** @var array */
57+
private $createdProductsSkus = [];
58+
59+
/** @var array */
60+
private $filesToRemove = [];
61+
62+
/** @var CsvFactory */
63+
private $csvFactory;
64+
65+
/** @var Data */
66+
private $importDataResource;
67+
68+
/**
69+
* @inheritdoc
70+
*/
71+
protected function setUp(): void
72+
{
73+
parent::setUp();
74+
75+
$this->objectManager = Bootstrap::getObjectManager();
76+
$this->fileSystem = $this->objectManager->get(Filesystem::class);
77+
$this->fileDriver = $this->objectManager->get(File::class);
78+
$this->mediaConfig = $this->objectManager->get(ConfigInterface::class);
79+
$this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
80+
$this->productRepository->cleanCache();
81+
$this->import = $this->objectManager->get(ProductFactory::class)->create();
82+
$this->csvFactory = $this->objectManager->get(CsvFactory::class);
83+
$this->importDataResource = $this->objectManager->get(Data::class);
84+
$this->appParams = Bootstrap::getInstance()->getBootstrap()->getApplication()
85+
->getInitParams()[\Magento\Framework\App\Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS];
86+
}
87+
88+
/**
89+
* @inheritdoc
90+
*/
91+
protected function tearDown(): void
92+
{
93+
$this->removeFiles();
94+
$this->removeProducts();
95+
$this->importDataResource->cleanBunches();
96+
97+
parent::tearDown();
98+
}
99+
100+
/**
101+
* @return void
102+
*/
103+
public function testImportProductsWithSameImages(): void
104+
{
105+
$this->moveImages('magento_image.jpg');
106+
$source = $this->prepareFile('catalog_import_products_with_same_images.csv');
107+
$this->updateUploader();
108+
$errors = $this->import->setParameters([
109+
'behavior' => Import::BEHAVIOR_ADD_UPDATE,
110+
'entity' => ProductEntity::ENTITY,
111+
])
112+
->setSource($source)->validateData();
113+
$this->assertEmpty($errors->getAllErrors());
114+
$this->import->importData();
115+
$this->createdProductsSkus = ['SimpleProductForTest1', 'SimpleProductForTest2'];
116+
$this->checkProductsImages('/m/a/magento_image.jpg', $this->createdProductsSkus);
117+
}
118+
119+
/**
120+
* Check product images
121+
*
122+
* @param string $expectedImagePath
123+
* @param array $productSkus
124+
* @return void
125+
*/
126+
private function checkProductsImages(string $expectedImagePath, array $productSkus): void
127+
{
128+
foreach ($productSkus as $productSku) {
129+
$product = $this->productRepository->get($productSku);
130+
$productImageItem = $product->getMediaGalleryImages()->getFirstItem();
131+
$productImageFile = $productImageItem->getFile();
132+
$productImagePath = $productImageItem->getPath();
133+
$this->filesToRemove[] = $productImagePath;
134+
$this->assertEquals($expectedImagePath, $productImageFile);
135+
$this->assertNotEmpty($productImagePath);
136+
$this->assertTrue($this->fileDriver->isExists($productImagePath));
137+
}
138+
}
139+
140+
/**
141+
* Remove created files
142+
*
143+
* @return void
144+
*/
145+
private function removeFiles(): void
146+
{
147+
foreach ($this->filesToRemove as $file) {
148+
if ($this->fileDriver->isExists($file)) {
149+
$this->fileDriver->deleteFile($file);
150+
}
151+
}
152+
}
153+
154+
/**
155+
* Remove created products
156+
*
157+
* @return void
158+
*/
159+
private function removeProducts(): void
160+
{
161+
foreach ($this->createdProductsSkus as $sku) {
162+
try {
163+
$this->productRepository->deleteById($sku);
164+
} catch (NoSuchEntityException $e) {
165+
//already removed
166+
}
167+
}
168+
}
169+
170+
/**
171+
* Prepare file
172+
*
173+
* @param string $fileName
174+
* @return Csv
175+
*/
176+
private function prepareFile(string $fileName): Csv
177+
{
178+
$tmpDirectory = $this->fileSystem->getDirectoryWrite(DirectoryList::VAR_DIR);
179+
$fixtureDir = realpath(__DIR__ . '/../../_files');
180+
$filePath = $tmpDirectory->getAbsolutePath($fileName);
181+
$this->filesToRemove[] = $filePath;
182+
$tmpDirectory->getDriver()->copy($fixtureDir . DIRECTORY_SEPARATOR . $fileName, $filePath);
183+
$source = $this->csvFactory->create(
184+
[
185+
'file' => $fileName,
186+
'directory' => $tmpDirectory
187+
]
188+
);
189+
190+
return $source;
191+
}
192+
193+
/**
194+
* Update upload to use sandbox folders
195+
*
196+
* @return void
197+
*/
198+
private function updateUploader(): void
199+
{
200+
$uploader = $this->import->getUploader();
201+
$rootDirectory = $this->fileSystem->getDirectoryWrite(DirectoryList::ROOT);
202+
$destDir = $rootDirectory->getRelativePath(
203+
$this->appParams[DirectoryList::MEDIA][DirectoryList::PATH]
204+
. DS . $this->mediaConfig->getBaseMediaPath()
205+
);
206+
$tmpDir = $rootDirectory->getRelativePath(
207+
$this->appParams[DirectoryList::MEDIA][DirectoryList::PATH]
208+
);
209+
$rootDirectory->create($destDir);
210+
$rootDirectory->create($tmpDir);
211+
$uploader->setDestDir($destDir);
212+
$uploader->setTmpDir($tmpDir);
213+
}
214+
215+
/**
216+
* Move images to appropriate folder
217+
*
218+
* @param string $fileName
219+
* @return void
220+
*/
221+
private function moveImages(string $fileName): void
222+
{
223+
$rootDirectory = $this->fileSystem->getDirectoryWrite(DirectoryList::ROOT);
224+
$tmpDir = $rootDirectory->getRelativePath(
225+
$this->appParams[DirectoryList::MEDIA][DirectoryList::PATH]
226+
);
227+
$fixtureDir = realpath(__DIR__ . '/../../_files');
228+
$tmpFilePath = $rootDirectory->getAbsolutePath($tmpDir . DS . $fileName);
229+
$this->fileDriver->createDirectory($tmpDir);
230+
$rootDirectory->getDriver()->copy(
231+
$fixtureDir . DIRECTORY_SEPARATOR . $fileName,
232+
$tmpFilePath
233+
);
234+
$this->filesToRemove[] = $tmpFilePath;
235+
}
236+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,swatch_image,swatch_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,related_skus,related_position,crosssell_skus,crosssell_position,upsell_skus,upsell_position,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,bundle_shipment_type,configurable_variations,configurable_variation_labels,associated_skus
2+
SimpleProductForTest1,,Default,simple,Default,base,SimpleProductAfterImport1,,,1,1,Taxable Goods,"Catalog, Search",250,,,,simple-product-for-test-1,,,,magento_image.jpg,BASE magento_image.jpg,magento_image.jpg,SMALL blueshirt,magento_image.jpg,Thumb Image,,,"3/4/19, 5:53 AM","3/4/19, 4:47 PM",,,Block after Info Column,,,,,,,,,,,Use config,,,100,0,1,0,0,1,1,1,0,1,1,,1,0,1,1,0,1,0,0,0,,,,,,,,,,,,,,,,,,,
3+
SimpleProductForTest2,,Default,simple,Default,base,SimpleProductAfterImport2,,,1,1,Taxable Goods,"Catalog, Search",300,,,,simple-product-for-test-2,,,,magento_image.jpg,BASE magento_image.jpg,magento_image.jpg,SMALL blueshirt,magento_image.jpg,Thumb Image,,,"3/4/19, 5:53 AM","3/4/19, 4:47 PM",,,Block after Info Column,,,,,,,,,,,Use config,,,100,0,1,0,0,1,1,1,0,1,1,,1,0,1,1,0,1,0,0,0,,,,,,,,,,,,,,,,,,,
Loading

0 commit comments

Comments
 (0)