Skip to content

Commit 327970b

Browse files
author
Serhii Balko
committed
MC-41917: Issue with 'catalog_product_alert' cron
1 parent 89024af commit 327970b

File tree

4 files changed

+203
-76
lines changed

4 files changed

+203
-76
lines changed

app/code/Magento/ProductAlert/Model/Mailing/Publisher.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public function execute(string $alertType, array $customerIds, int $websiteId):
101101
[
102102
'data' => [
103103
'bulk_uuid' => $bulkUuid,
104-
'topic_name' => 'product_alert.mailing',
104+
'topic_name' => 'product_alert',
105105
'serialized_data' => $serializedData,
106106
'status' => OperationInterface::STATUS_TYPE_OPEN,
107107
]

app/code/Magento/ProductAlert/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"require": {
88
"php": "~7.3.0||~7.4.0",
99
"magento/framework": "*",
10+
"magento/module-asynchronous-operations": "*",
1011
"magento/module-backend": "*",
1112
"magento/module-catalog": "*",
1213
"magento/module-customer": "*",
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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\ProductAlert\Model\Mailing;
9+
10+
use Magento\Customer\Api\AccountManagementInterface;
11+
use Magento\Customer\Model\Session;
12+
use Magento\Framework\App\Area;
13+
use Magento\Framework\Locale\Resolver;
14+
use Magento\Framework\Module\Dir\Reader;
15+
use Magento\Framework\Phrase;
16+
use Magento\Framework\Phrase\Renderer\Translate as PhraseRendererTranslate;
17+
use Magento\Framework\Phrase\RendererInterface;
18+
use Magento\Framework\Translate;
19+
use Magento\Store\Model\StoreRepository;
20+
use Magento\TestFramework\Helper\Bootstrap;
21+
use Magento\TestFramework\Mail\Template\TransportBuilderMock;
22+
use Magento\TestFramework\ObjectManager;
23+
use PHPUnit\Framework\TestCase;
24+
25+
/**
26+
* Test for Product Alert observer
27+
*
28+
* @magentoAppIsolation enabled
29+
* @magentoAppArea frontend
30+
*/
31+
class AlertProcessorTest extends TestCase
32+
{
33+
/**
34+
* @var ObjectManager
35+
*/
36+
private $objectManager;
37+
38+
/**
39+
* @var Publisher
40+
*/
41+
private $publisher;
42+
43+
/**
44+
* @var AlertProcessor
45+
*/
46+
private $alertProcessor;
47+
48+
/**
49+
* @var TransportBuilderMock
50+
*/
51+
private $transportBuilder;
52+
53+
/**
54+
* @inheritDoc
55+
*/
56+
protected function setUp(): void
57+
{
58+
$this->objectManager = Bootstrap::getObjectManager();
59+
$this->publisher = $this->objectManager->get(Publisher::class);
60+
$this->alertProcessor = $this->objectManager->get(AlertProcessor::class);
61+
62+
$this->transportBuilder = $this->objectManager->get(TransportBuilderMock::class);
63+
$service = $this->objectManager->create(AccountManagementInterface::class);
64+
$customer = $service->authenticate('customer@example.com', 'password');
65+
$customerSession = $this->objectManager->get(Session::class);
66+
$customerSession->setCustomerDataAsLoggedIn($customer);
67+
}
68+
69+
/**
70+
* @magentoConfigFixture current_store catalog/productalert/allow_price 1
71+
* @magentoDataFixture Magento/ProductAlert/_files/product_alert.php
72+
*/
73+
public function testProcess()
74+
{
75+
$this->processAlerts();
76+
77+
/** Checking is the email was sent */
78+
$this->assertStringContainsString(
79+
'John Smith,',
80+
$this->transportBuilder->getSentMessage()->getBody()->getParts()[0]->getRawContent()
81+
);
82+
}
83+
84+
/**
85+
* Check translations for product alerts
86+
*
87+
* @magentoDbIsolation disabled
88+
* @magentoDataFixture Magento/Catalog/_files/category.php
89+
* @magentoConfigFixture current_store catalog/productalert/allow_price 1
90+
* @magentoDataFixture Magento/Store/_files/second_store.php
91+
* @magentoConfigFixture fixture_second_store_store general/locale/code pt_BR
92+
* @magentoDataFixture Magento/ProductAlert/_files/product_alert_with_store.php
93+
*/
94+
public function testProcessPortuguese()
95+
{
96+
// get second store
97+
$storeRepository = $this->objectManager->create(StoreRepository::class);
98+
$secondStore = $storeRepository->get('fixture_second_store');
99+
100+
// check if Portuguese language is specified for the second store
101+
$storeResolver = $this->objectManager->get(Resolver::class);
102+
$storeResolver->emulate($secondStore->getId());
103+
$this->assertEquals('pt_BR', $storeResolver->getLocale());
104+
105+
// set translation data and check it
106+
$modulesReader = $this->createPartialMock(Reader::class, ['getModuleDir']);
107+
$modulesReader->method('getModuleDir')
108+
->willReturn(dirname(__DIR__) . '/../_files/i18n');
109+
/** @var Translate $translator */
110+
$translator = $this->objectManager->create(Translate::class, ['modulesReader' => $modulesReader]);
111+
$translation = [
112+
'Price change alert! We wanted you to know that prices have changed for these products:' =>
113+
'Alerta de mudanca de preco! Queriamos que voce soubesse que os precos mudaram para esses produtos:'
114+
];
115+
$translator->loadData();
116+
$this->assertEquals($translation, $translator->getData());
117+
$this->objectManager->addSharedInstance($translator, Translate::class);
118+
$this->objectManager->removeSharedInstance(PhraseRendererTranslate::class);
119+
Phrase::setRenderer($this->objectManager->create(RendererInterface::class));
120+
121+
// dispatch process() method and check sent message
122+
$this->processAlerts();
123+
$message = $this->transportBuilder->getSentMessage();
124+
$messageContent = $message->getBody()->getParts()[0]->getRawContent();
125+
$expectedText = array_shift($translation);
126+
$this->assertStringContainsString('/frontend/Magento/luma/pt_BR/', $messageContent);
127+
$this->assertStringContainsString(substr($expectedText, 0, 50), $messageContent);
128+
}
129+
130+
/**
131+
* Process price alerts
132+
*/
133+
private function processAlerts(): void
134+
{
135+
$alertType = AlertProcessor::ALERT_TYPE_PRICE;
136+
$customerId = 1;
137+
$websiteId = 1;
138+
139+
$this->publisher->execute($alertType, [$customerId], $websiteId);
140+
$this->alertProcessor->process($alertType, [$customerId], $websiteId);
141+
}
142+
}

dev/tests/integration/testsuite/Magento/ProductAlert/Model/ObserverTest.php

Lines changed: 59 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -7,57 +7,39 @@
77

88
namespace Magento\ProductAlert\Model;
99

10-
use Magento\Customer\Api\AccountManagementInterface;
11-
use Magento\Customer\Model\Session;
12-
use Magento\Framework\App\Area;
13-
use Magento\Framework\Locale\Resolver;
14-
use Magento\Framework\Module\Dir\Reader;
15-
use Magento\Framework\Phrase;
16-
use Magento\Framework\Phrase\Renderer\Translate as PhraseRendererTranslate;
17-
use Magento\Framework\Phrase\RendererInterface;
18-
use Magento\Framework\Translate;
19-
use Magento\Store\Model\StoreRepository;
10+
use Magento\Framework\DB\Select;
11+
use Magento\ProductAlert\Model\ResourceModel\Price as PriceResource;
2012
use Magento\TestFramework\Helper\Bootstrap;
21-
use Magento\TestFramework\Helper\CacheCleaner;
22-
use Magento\TestFramework\Mail\Template\TransportBuilderMock;
23-
use Magento\TestFramework\ObjectManager;
13+
use Magento\TestFramework\MessageQueue\EnvironmentPreconditionException;
14+
use Magento\TestFramework\MessageQueue\PreconditionFailedException;
15+
use Magento\TestFramework\MessageQueue\PublisherConsumerController;
16+
use PHPUnit\Framework\TestCase;
2417

2518
/**
26-
* Test for Magento\ProductAlert\Model\Observer
19+
* Test Product Alert observer
2720
*
28-
* @magentoAppIsolation enabled
29-
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
21+
* @magentoDbIsolation disabled
3022
*/
31-
class ObserverTest extends \PHPUnit\Framework\TestCase
23+
class ObserverTest extends TestCase
3224
{
33-
/**
34-
* @var ObjectManager
35-
*/
36-
protected $_objectManager;
37-
3825
/**
3926
* @var Observer
4027
*/
4128
private $observer;
4229

4330
/**
44-
* @var TransportBuilderMock
31+
* @var PriceResource
4532
*/
46-
private $transportBuilder;
33+
private $priceResource;
4734

4835
/**
4936
* @inheritDoc
5037
*/
5138
protected function setUp(): void
5239
{
53-
Bootstrap::getInstance()->loadArea(Area::AREA_FRONTEND);
54-
$this->_objectManager = Bootstrap::getObjectManager();
55-
$this->observer = $this->_objectManager->get(Observer::class);
56-
$this->transportBuilder = $this->_objectManager->get(TransportBuilderMock::class);
57-
$service = $this->_objectManager->create(AccountManagementInterface::class);
58-
$customer = $service->authenticate('customer@example.com', 'password');
59-
$customerSession = $this->_objectManager->get(Session::class);
60-
$customerSession->setCustomerDataAsLoggedIn($customer);
40+
$objectManager = Bootstrap::getObjectManager();
41+
$this->observer = $objectManager->get(Observer::class);
42+
$this->priceResource = $objectManager->create(PriceResource::class);
6143
}
6244

6345
/**
@@ -69,57 +51,59 @@ protected function setUp(): void
6951
public function testProcess()
7052
{
7153
$this->observer->process();
72-
$this->assertStringContainsString(
73-
'John Smith,',
74-
$this->transportBuilder->getSentMessage()->getBody()->getParts()[0]->getRawContent()
75-
);
54+
$this->assertProcessAlertByConsumer();
7655
}
7756

7857
/**
79-
* Check translations for product alerts
58+
* Waiting for execute consumer
8059
*
81-
* @magentoDbIsolation disabled
82-
* @magentoAppArea frontend
83-
* @magentoDataFixture Magento/Catalog/_files/category.php
84-
* @magentoConfigFixture current_store catalog/productalert/allow_price 1
85-
* @magentoDataFixture Magento/Store/_files/second_store.php
86-
* @magentoConfigFixture fixture_second_store_store general/locale/code pt_BR
87-
* @magentoDataFixture Magento/ProductAlert/_files/product_alert_with_store.php
60+
* @return void
61+
* @throws PreconditionFailedException
8862
*/
89-
public function testProcessPortuguese()
63+
private function assertProcessAlertByConsumer(): void
9064
{
91-
// get second store
92-
$storeRepository = $this->_objectManager->create(StoreRepository::class);
93-
$secondStore = $storeRepository->get('fixture_second_store');
65+
/** @var PublisherConsumerController $publisherConsumerController */
66+
$publisherConsumerController = Bootstrap::getObjectManager()->create(
67+
PublisherConsumerController::class,
68+
[
69+
'consumers' => ['product_alert'],
70+
'logFilePath' => TESTS_TEMP_DIR . "/MessageQueueTestLog.txt",
71+
'maxMessages' => 1,
72+
'appInitParams' => Bootstrap::getInstance()->getAppInitParams()
73+
]
74+
);
75+
try {
76+
$publisherConsumerController->startConsumers();
77+
} catch (EnvironmentPreconditionException $e) {
78+
$this->markTestSkipped($e->getMessage());
79+
} catch (PreconditionFailedException $e) {
80+
$this->fail(
81+
$e->getMessage()
82+
);
83+
}
84+
85+
sleep(15); // timeout to processing Magento queue
9486

95-
// check if Portuguese language is specified for the second store
96-
$storeResolver = $this->_objectManager->get(Resolver::class);
97-
$storeResolver->emulate($secondStore->getId());
98-
$this->assertEquals('pt_BR', $storeResolver->getLocale());
87+
$publisherConsumerController->waitForAsynchronousResult(
88+
function () {
89+
return $this->loadLastPriceAlertStatus();
90+
},
91+
[]
92+
);
93+
}
9994

100-
// set translation data and check it
101-
$modulesReader = $this->createPartialMock(Reader::class, ['getModuleDir']);
102-
$modulesReader->expects($this->any())
103-
->method('getModuleDir')
104-
->willReturn(dirname(__DIR__) . '/_files/i18n');
105-
/** @var Translate $translator */
106-
$translator = $this->_objectManager->create(Translate::class, ['modulesReader' => $modulesReader]);
107-
$translation = [
108-
'Price change alert! We wanted you to know that prices have changed for these products:' =>
109-
'Alerta de mudanca de preco! Queriamos que voce soubesse que os precos mudaram para esses produtos:'
110-
];
111-
$translator->loadData();
112-
$this->assertEquals($translation, $translator->getData());
113-
$this->_objectManager->addSharedInstance($translator, Translate::class);
114-
$this->_objectManager->removeSharedInstance(PhraseRendererTranslate::class);
115-
Phrase::setRenderer($this->_objectManager->create(RendererInterface::class));
95+
/**
96+
* Load last created price alert
97+
*
98+
* @return bool
99+
*/
100+
private function loadLastPriceAlertStatus(): bool
101+
{
102+
$select = $this->priceResource->getConnection()->select();
103+
$select->from($this->priceResource->getMainTable(), ['status'])
104+
->order($this->priceResource->getIdFieldName() . ' ' . Select::SQL_DESC)
105+
->limit(1);
116106

117-
// dispatch process() method and check sent message
118-
$this->observer->process();
119-
$message = $this->transportBuilder->getSentMessage();
120-
$messageContent = $message->getBody()->getParts()[0]->getRawContent();
121-
$expectedText = array_shift($translation);
122-
$this->assertStringContainsString('/frontend/Magento/luma/pt_BR/', $messageContent);
123-
$this->assertStringContainsString(substr($expectedText, 0, 50), $messageContent);
107+
return (bool)$this->priceResource->getConnection()->fetchOne($select);
124108
}
125109
}

0 commit comments

Comments
 (0)