Skip to content

Commit 9c6edc7

Browse files
committed
Merge remote-tracking branch 'origin/MC-39550' into 2.4-develop-sidecar-pr12
2 parents 0853a40 + 8912370 commit 9c6edc7

File tree

5 files changed

+422
-0
lines changed

5 files changed

+422
-0
lines changed
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
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\Catalog\Model\Attribute\Backend;
9+
10+
use Magento\AsynchronousOperations\Api\Data\OperationInterface as OperationDataInterface;
11+
use Magento\AsynchronousOperations\Model\ResourceModel\Operation\CollectionFactory;
12+
use Magento\Catalog\Api\ProductRepositoryInterface;
13+
use Magento\Catalog\Model\Product\Action;
14+
use Magento\Framework\Bulk\OperationInterface;
15+
use Magento\Framework\DB\Adapter\DeadlockException;
16+
use Magento\Framework\Exception\NoSuchEntityException;
17+
use Magento\Framework\MessageQueue\MessageEncoder;
18+
use Magento\Framework\ObjectManagerInterface;
19+
use Magento\MysqlMq\Model\Driver\Queue;
20+
use Magento\Store\Api\WebsiteRepositoryInterface;
21+
use Magento\TestFramework\Helper\Bootstrap;
22+
use Magento\TestFramework\MysqlMq\DeleteTopicRelatedMessages;
23+
use PHPUnit\Framework\TestCase;
24+
25+
/**
26+
* Tests for Mysql website assigning consumer
27+
*
28+
* @see \Magento\Catalog\Model\Attribute\Backend\ConsumerWebsiteAssign
29+
*
30+
* @magentoDbIsolation disabled
31+
* @magentoAppArea adminhtml
32+
*/
33+
class ConsumerWebsiteAssignTest extends TestCase
34+
{
35+
private const TOPIC_NAME = 'product_action_attribute.website.update';
36+
37+
/** @var DeleteTopicRelatedMessages */
38+
private static $deleteTopicRelatedMessages;
39+
40+
/** @var ObjectManagerInterface */
41+
private $objectManager;
42+
43+
/** @var ConsumerWebsiteAssign */
44+
private $consumer;
45+
46+
/** @var Queue */
47+
private $queue;
48+
49+
/** @var MessageEncoder */
50+
private $messageEncoder;
51+
52+
/** @var ProductRepositoryInterface */
53+
private $productRepository;
54+
55+
/** @var WebsiteRepositoryInterface */
56+
private $websiteRepository;
57+
58+
/** @var CollectionFactory */
59+
private $operationCollectionFactory;
60+
61+
/**
62+
* @inheritdoc
63+
*/
64+
public static function setUpBeforeClass(): void
65+
{
66+
parent::setUpBeforeClass();
67+
68+
$objectManager = Bootstrap::getObjectManager();
69+
self::$deleteTopicRelatedMessages = $objectManager->get(DeleteTopicRelatedMessages::class);
70+
self::$deleteTopicRelatedMessages->execute(self::TOPIC_NAME);
71+
}
72+
73+
/**
74+
* @inheritdoc
75+
*/
76+
protected function setUp(): void
77+
{
78+
parent::setUp();
79+
80+
$this->objectManager = Bootstrap::getObjectManager();
81+
$this->consumer = $this->objectManager->get(ConsumerWebsiteAssign::class);
82+
$this->queue = $this->objectManager->create(
83+
Queue::class,
84+
['queueName' => 'product_action_attribute.website.update']
85+
);
86+
$this->messageEncoder = $this->objectManager->get(MessageEncoder::class);
87+
$this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
88+
$this->websiteRepository = $this->objectManager->get(WebsiteRepositoryInterface::class);
89+
$this->operationCollectionFactory = $this->objectManager->get(CollectionFactory::class);
90+
}
91+
92+
/**
93+
* @inheritdoc
94+
*/
95+
protected function tearDown(): void
96+
{
97+
$this->objectManager->removeSharedInstance(Action::class);
98+
self::$deleteTopicRelatedMessages->execute(self::TOPIC_NAME);
99+
100+
parent::tearDown();
101+
}
102+
103+
/**
104+
* @magentoDataFixture Magento/Catalog/_files/update_product_website_quene_data.php
105+
*
106+
* @return void
107+
*/
108+
public function testAddWebsite(): void
109+
{
110+
$this->processMessages();
111+
$this->assertProductWebsites('simple2', ['base', 'test']);
112+
$this->assertOperation(OperationInterface::STATUS_TYPE_COMPLETE);
113+
}
114+
115+
/**
116+
* @magentoDataFixture Magento/Catalog/_files/detach_product_website_quene_data.php
117+
*
118+
* @return void
119+
*/
120+
public function testRemoveWebsite(): void
121+
{
122+
$this->processMessages();
123+
$this->assertProductWebsites('unique-simple-azaza', ['base']);
124+
$this->assertOperation(OperationInterface::STATUS_TYPE_COMPLETE);
125+
}
126+
127+
/**
128+
* @magentoDataFixture Magento/Catalog/_files/update_product_website_quene_data.php
129+
*
130+
* @return void
131+
*/
132+
public function testAddWebsiteToDeletedProduct(): void
133+
{
134+
$expectedMessage = __('Something went wrong while adding products to websites.');
135+
$this->productRepository->deleteById('simple2');
136+
$this->processMessages();
137+
$this->assertOperation(OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED, (string)$expectedMessage);
138+
}
139+
140+
/**
141+
* @dataProvider errorProvider
142+
*
143+
* @magentoDataFixture Magento/Catalog/_files/update_product_website_quene_data.php
144+
*
145+
* @param \Throwable $exception
146+
* @param int $code
147+
* @return void
148+
*/
149+
public function testWithException(\Throwable $exception, int $code): void
150+
{
151+
$this->prepareMock($exception);
152+
$this->processMessages();
153+
$this->assertOperation($code, $exception->getMessage());
154+
}
155+
156+
/**
157+
* @return array
158+
*/
159+
public function errorProvider(): array
160+
{
161+
return [
162+
'with_dead_lock_exception' => [
163+
'exception' => new DeadlockException('Test lock'),
164+
'code' => OperationDataInterface::STATUS_TYPE_RETRIABLY_FAILED,
165+
],
166+
'with_db_exception' => [
167+
'exception' => new \Zend_Db_Adapter_Exception(
168+
(string)__(
169+
'Sorry, something went wrong during product attributes update. Please see log for details.'
170+
)
171+
),
172+
'code' => OperationDataInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED,
173+
],
174+
'with_no_such_entity_exception' => [
175+
'exception' => new NoSuchEntityException(),
176+
'code' => OperationDataInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED,
177+
],
178+
'with_general_exception' => [
179+
'exception' => new \Exception(
180+
(string)__(
181+
'Sorry, something went wrong during product attributes update. Please see log for details.'
182+
)
183+
),
184+
'code' => OperationDataInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED,
185+
],
186+
];
187+
}
188+
189+
/**
190+
* Assert product website ids
191+
*
192+
* @param string $sku
193+
* @param array $expectedWebsites
194+
* @return void
195+
*/
196+
private function assertProductWebsites(string $sku, array $expectedWebsites): void
197+
{
198+
$product = $this->productRepository->get($sku, false, null, true);
199+
$websitesIds = $product->getWebsiteIds();
200+
$this->assertCount(count($expectedWebsites), $websitesIds);
201+
202+
foreach ($expectedWebsites as $expectedWebsite) {
203+
$expectedWebsiteId = $this->websiteRepository->get($expectedWebsite)->getId();
204+
$this->assertContains($expectedWebsiteId, $websitesIds);
205+
}
206+
}
207+
208+
/**
209+
* Process current consumer topic messages
210+
*
211+
* @return void
212+
*/
213+
private function processMessages(): void
214+
{
215+
$envelope = $this->queue->dequeue();
216+
$decodedMessage = $this->messageEncoder->decode(self::TOPIC_NAME, $envelope->getBody());
217+
$this->consumer->process($decodedMessage);
218+
}
219+
220+
/**
221+
* Get last current topic related operation
222+
*
223+
* @return OperationDataInterface
224+
*/
225+
private function getLastTopicOperation(): OperationDataInterface
226+
{
227+
$collection = $this->operationCollectionFactory->create();
228+
$collection->addFieldToFilter('topic_name', self::TOPIC_NAME);
229+
$collection->setPageSize(1)->setCurPage($collection->getLastPageNumber());
230+
231+
return $collection->getLastItem();
232+
}
233+
234+
/**
235+
* Assert performed operation
236+
*
237+
* @param int $status
238+
* @param string|null $resultMessage
239+
* @return void
240+
*/
241+
private function assertOperation(int $status, ?string $resultMessage = null): void
242+
{
243+
$operation = $this->getLastTopicOperation();
244+
$this->assertNotNull($operation->getData('id'));
245+
$this->assertEquals($status, $operation->getStatus());
246+
$this->assertEquals($resultMessage, $operation->getResultMessage());
247+
}
248+
249+
/**
250+
* Create mock with provided exception
251+
*
252+
* @param \Throwable $exception
253+
* @return void
254+
*/
255+
private function prepareMock(\Throwable $exception): void
256+
{
257+
$object = $this->createPartialMock(Action::class, ['updateWebsites']);
258+
$object->method('updateWebsites')->willThrowException($exception);
259+
$this->objectManager->addSharedInstance($object, Action::class);
260+
$this->consumer = $this->objectManager->create(ConsumerWebsiteAssign::class);
261+
}
262+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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+
use Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory;
9+
use Magento\Catalog\Api\ProductRepositoryInterface;
10+
use Magento\Framework\Bulk\BulkManagementInterface;
11+
use Magento\Framework\Bulk\OperationInterface;
12+
use Magento\Framework\DataObject\IdentityGeneratorInterface;
13+
use Magento\Framework\Serialize\SerializerInterface;
14+
use Magento\Store\Api\WebsiteRepositoryInterface;
15+
use Magento\TestFramework\Helper\Bootstrap;
16+
use Magento\TestFramework\Workaround\Override\Fixture\Resolver;
17+
18+
Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/product_with_two_websites.php');
19+
20+
$objectManager = Bootstrap::getObjectManager();
21+
/** @var ProductRepositoryInterface $productRepository */
22+
$productRepository = $objectManager->get(ProductRepositoryInterface::class);
23+
$productRepository->cleanCache();
24+
/** @var WebsiteRepositoryInterface $websiteRepository */
25+
$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class);
26+
/** @var IdentityGeneratorInterface $identityService */
27+
$identityService = $objectManager->get(IdentityGeneratorInterface::class);
28+
/** @var SerializerInterface $jsonEncoder */
29+
$jsonEncoder = $objectManager->get(SerializerInterface::class);
30+
/** @var OperationInterfaceFactory $optionFactory */
31+
$optionFactory = $objectManager->get(OperationInterfaceFactory::class);
32+
/** @var BulkManagementInterface $bulkManagement */
33+
$bulkManagement = $objectManager->get(BulkManagementInterface::class);
34+
$productIds = [(int)$productRepository->get('unique-simple-azaza')->getId()];
35+
$websiteId = (int)$websiteRepository->get('second_website')->getId();
36+
$bulkDescription = __('Update attributes for ' . 1 . ' selected products');
37+
$dataToEncode = [
38+
'meta_information' => 'Detach website',
39+
'product_ids' => $productIds,
40+
'store_id' => 0,
41+
'website_id' => $websiteId,
42+
'attributes' => [
43+
'website_assign' => [],
44+
'website_detach' => [$websiteId],
45+
],
46+
];
47+
$bulkUid = $identityService->generateId();
48+
$data = [
49+
'data' => [
50+
'bulk_uuid' => $bulkUid,
51+
'topic_name' => 'product_action_attribute.website.update',
52+
'serialized_data' => $jsonEncoder->serialize($dataToEncode),
53+
'status' => OperationInterface::STATUS_TYPE_OPEN,
54+
],
55+
];
56+
57+
$bulkManagement->scheduleBulk(
58+
$bulkUid,
59+
[$optionFactory->create($data)],
60+
$bulkDescription,
61+
1
62+
);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
use Magento\TestFramework\MysqlMq\DeleteTopicRelatedMessages;
9+
use Magento\TestFramework\Helper\Bootstrap;
10+
use Magento\TestFramework\Workaround\Override\Fixture\Resolver;
11+
12+
$objectManager = Bootstrap::getObjectManager();
13+
/** @var DeleteTopicRelatedMessages $deleteTopicRelatedMessages */
14+
$deleteTopicRelatedMessages = $objectManager->get(DeleteTopicRelatedMessages::class);
15+
$deleteTopicRelatedMessages->execute('product_action_attribute.website.update');
16+
17+
Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/product_with_two_websites_rollback.php');

0 commit comments

Comments
 (0)