diff --git a/app/code/Magento/AwsS3/Test/Mftf/Test/AwsS3AdminImportSimpleAndConfigurableProductsWithAssignedImagesTest.xml b/app/code/Magento/AwsS3/Test/Mftf/Test/AwsS3AdminImportSimpleAndConfigurableProductsWithAssignedImagesTest.xml
new file mode 100644
index 0000000000000..fe8c6c6f7f9ef
--- /dev/null
+++ b/app/code/Magento/AwsS3/Test/Mftf/Test/AwsS3AdminImportSimpleAndConfigurableProductsWithAssignedImagesTest.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
index 3f908663c8e5e..a247e6b09760b 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
@@ -916,7 +916,12 @@ public function addWebsiteFilter($websites = null)
}
$this->_productLimitationFilters['website_ids'] = $websites;
- $this->_applyProductLimitations();
+
+ if ($this->getStoreId() == Store::DEFAULT_STORE_ID) {
+ $this->_productLimitationJoinWebsite();
+ } else {
+ $this->_applyProductLimitations();
+ }
return $this;
}
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontNavigateToCategoryUrlActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontNavigateToCategoryUrlActionGroup.xml
new file mode 100644
index 0000000000000..f938627886540
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontNavigateToCategoryUrlActionGroup.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+ Goes to the Storefront Category page for the provided Category URL.
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageSelectDropDownOptionValueActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageSelectDropDownOptionValueActionGroup.xml
index 31b18e1f0d37e..9cb3f3faf2f33 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageSelectDropDownOptionValueActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageSelectDropDownOptionValueActionGroup.xml
@@ -18,5 +18,6 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml
index c2de91aadbc0c..0d26c43d2c34b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml
@@ -13,6 +13,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php
index 2bf504369b8a7..9a55e48cfb1b4 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php
@@ -38,6 +38,7 @@
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\Framework\Validator\UniversalFactory;
+use Magento\Store\Model\Store;
use Magento\Store\Model\StoreManagerInterface;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
@@ -93,6 +94,11 @@ class CollectionTest extends TestCase
*/
private $storeManager;
+ /**
+ * @var ProductLimitation|MockObject
+ */
+ private $productLimitationMock;
+
/**
* @var EntityFactory|MockObject
*/
@@ -192,7 +198,7 @@ protected function setUp(): void
$this->entityMock->expects($this->any())->method('getTable')->willReturnArgument(0);
$this->connectionMock->expects($this->atLeastOnce())->method('select')->willReturn($this->selectMock);
- $productLimitationMock = $this->createMock(
+ $this->productLimitationMock = $this->createMock(
ProductLimitation::class
);
$productLimitationFactoryMock = $this->getMockBuilder(
@@ -201,7 +207,7 @@ protected function setUp(): void
->setMethods(['create'])->getMock();
$productLimitationFactoryMock->method('create')
- ->willReturn($productLimitationMock);
+ ->willReturn($this->productLimitationMock);
$this->collection = $this->objectManager->getObject(
Collection::class,
[
@@ -432,4 +438,44 @@ public function testGetNewEmptyItem()
$secondItem = $this->collection->getNewEmptyItem();
$this->assertEquals($firstItem, $secondItem);
}
+
+ /**
+ * Test to add website filter in admin area
+ */
+ public function testAddWebsiteFilterOnAdminStore(): void
+ {
+ $websiteIds = [2];
+ $websiteTable = 'catalog_product_website';
+ $joinCondition = 'join condition';
+ $this->productLimitationMock->expects($this->atLeastOnce())
+ ->method('offsetSet')
+ ->with('website_ids', $websiteIds);
+ $this->productLimitationMock->method('offsetExists')
+ ->with('website_ids')
+ ->willReturn(true);
+ $this->productLimitationMock->method('offsetGet')
+ ->with('website_ids')
+ ->willReturn($websiteIds);
+ $this->connectionMock->expects($this->once())
+ ->method('quoteInto')
+ ->with('product_website.website_id IN(?)', $websiteIds, 'int')
+ ->willReturn($joinCondition);
+ $this->selectMock->method('getPart')->with(Select::FROM)->willReturn([]);
+ /** @var AbstractEntity|MockObject $eavEntity */
+ $eavEntity = $this->createMock(AbstractEntity::class);
+ $eavEntity->method('getTable')
+ ->with('catalog_product_website')
+ ->willReturn($websiteTable);
+ $this->selectMock->expects($this->once())
+ ->method('join')
+ ->with(
+ ['product_website' => $websiteTable],
+ 'product_website.product_id = e.entity_id AND ' . $joinCondition,
+ []
+ );
+
+ $this->collection->setEntity($eavEntity);
+ $this->collection->setStoreId(Store::DEFAULT_STORE_ID);
+ $this->collection->addWebsiteFilter($websiteIds);
+ }
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Deferred/Product.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Deferred/Product.php
index 22bbc991a78e2..8218c22b8056d 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Deferred/Product.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Deferred/Product.php
@@ -132,7 +132,7 @@ private function fetch() : array
$this->searchCriteriaBuilder->create(),
$this->attributeCodes,
false,
- true
+ false
);
/** @var \Magento\Catalog\Model\Product $product */
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php
index 3e955ae303453..30be41072242b 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php
@@ -89,7 +89,7 @@ public function getList(
$this->collectionPreProcessor->process($collection, $searchCriteria, $attributes, $context);
- if (!$isChildSearch) {
+ if ($isChildSearch) {
$visibilityIds = $isSearch
? $this->visibility->getVisibleInSearchIds()
: $this->visibility->getVisibleInCatalogIds();
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php
index 22a83671f630a..84cdc4608148c 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php
@@ -220,7 +220,7 @@ private function deleteProductsLinks(
if (!empty($linksToDelete) && Import::BEHAVIOR_APPEND === $importEntity->getBehavior()) {
foreach ($linksToDelete as $linkTypeId => $productIds) {
if (!empty($productIds)) {
- $whereLinkId = $importEntity->getConnection()->quoteInto('link_type_id', $linkTypeId);
+ $whereLinkId = $importEntity->getConnection()->quoteInto('link_type_id = ?', $linkTypeId);
$whereProductId = $importEntity->getConnection()->quoteInto(
'product_id IN (?)',
array_unique($productIds)
diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php
index 944710773123f..3e2301f34c4f8 100644
--- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php
+++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php
@@ -6,8 +6,8 @@
namespace Magento\CatalogRule\Model\Indexer;
-use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper;
use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
+use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper;
use Magento\CatalogRule\Model\Rule;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
@@ -18,6 +18,8 @@
*/
class ReindexRuleProduct
{
+ private const ADMIN_WEBSITE_ID = 0;
+
/**
* @var ResourceConnection
*/
@@ -38,22 +40,30 @@ class ReindexRuleProduct
*/
private $localeDate;
+ /**
+ * @var bool
+ */
+ private $useWebsiteTimezone;
+
/**
* @param ResourceConnection $resource
* @param ActiveTableSwitcher $activeTableSwitcher
* @param TableSwapper $tableSwapper
* @param TimezoneInterface $localeDate
+ * @param bool $useWebsiteTimezone
*/
public function __construct(
ResourceConnection $resource,
ActiveTableSwitcher $activeTableSwitcher,
TableSwapper $tableSwapper,
- TimezoneInterface $localeDate
+ TimezoneInterface $localeDate,
+ bool $useWebsiteTimezone = true
) {
$this->resource = $resource;
$this->activeTableSwitcher = $activeTableSwitcher;
$this->tableSwapper = $tableSwapper;
$this->localeDate = $localeDate;
+ $this->useWebsiteTimezone = $useWebsiteTimezone;
}
/**
@@ -95,18 +105,18 @@ public function execute(Rule $rule, $batchCount, $useAdditionalTable = false)
$actionOperator = $rule->getSimpleAction();
$actionAmount = $rule->getDiscountAmount();
$actionStop = $rule->getStopRulesProcessing();
+ $fromTimeInAdminTz = $this->parseDateByWebsiteTz((string)$rule->getFromDate(), self::ADMIN_WEBSITE_ID);
+ $toTimeInAdminTz = $this->parseDateByWebsiteTz((string)$rule->getToDate(), self::ADMIN_WEBSITE_ID);
$rows = [];
foreach ($websiteIds as $websiteId) {
- $scopeTz = new \DateTimeZone(
- $this->localeDate->getConfigTimezone(ScopeInterface::SCOPE_WEBSITE, $websiteId)
- );
- $fromTime = $rule->getFromDate()
- ? (new \DateTime($rule->getFromDate(), $scopeTz))->getTimestamp()
- : 0;
- $toTime = $rule->getToDate()
- ? (new \DateTime($rule->getToDate(), $scopeTz))->getTimestamp() + IndexBuilder::SECONDS_IN_DAY - 1
- : 0;
+ $fromTime = $this->useWebsiteTimezone
+ ? $this->parseDateByWebsiteTz((string)$rule->getFromDate(), (int)$websiteId)
+ : $fromTimeInAdminTz;
+ $toTime = $this->useWebsiteTimezone
+ ? $this->parseDateByWebsiteTz((string)$rule->getToDate(), (int)$websiteId)
+ + ($rule->getToDate() ? IndexBuilder::SECONDS_IN_DAY - 1 : 0)
+ : $toTimeInAdminTz;
foreach ($productIds as $productId => $validationByWebsite) {
if (empty($validationByWebsite[$websiteId])) {
@@ -140,4 +150,23 @@ public function execute(Rule $rule, $batchCount, $useAdditionalTable = false)
return true;
}
+
+ /**
+ * Parse date value by the timezone of the website
+ *
+ * @param string $date
+ * @param int $websiteId
+ * @return int
+ */
+ private function parseDateByWebsiteTz(string $date, int $websiteId): int
+ {
+ if (empty($date)) {
+ return 0;
+ }
+
+ $websiteTz = $this->localeDate->getConfigTimezone(ScopeInterface::SCOPE_WEBSITE, $websiteId);
+ $dateTime = new \DateTime($date, new \DateTimeZone($websiteTz));
+
+ return $dateTime->getTimestamp();
+ }
}
diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php
index 51869f1accbb3..ccc5352567ff0 100644
--- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php
+++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php
@@ -7,6 +7,7 @@
namespace Magento\CatalogRule\Model\Indexer;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
+use Magento\Store\Model\Store;
use Magento\Store\Model\StoreManagerInterface;
/**
@@ -39,25 +40,33 @@ class ReindexRuleProductPrice
*/
private $pricesPersistor;
+ /**
+ * @var bool
+ */
+ private $useWebsiteTimezone;
+
/**
* @param StoreManagerInterface $storeManager
* @param RuleProductsSelectBuilder $ruleProductsSelectBuilder
* @param ProductPriceCalculator $productPriceCalculator
* @param TimezoneInterface $localeDate
* @param RuleProductPricesPersistor $pricesPersistor
+ * @param bool $useWebsiteTimezone
*/
public function __construct(
StoreManagerInterface $storeManager,
RuleProductsSelectBuilder $ruleProductsSelectBuilder,
ProductPriceCalculator $productPriceCalculator,
TimezoneInterface $localeDate,
- RuleProductPricesPersistor $pricesPersistor
+ RuleProductPricesPersistor $pricesPersistor,
+ bool $useWebsiteTimezone = true
) {
$this->storeManager = $storeManager;
$this->ruleProductsSelectBuilder = $ruleProductsSelectBuilder;
$this->productPriceCalculator = $productPriceCalculator;
$this->localeDate = $localeDate;
$this->pricesPersistor = $pricesPersistor;
+ $this->useWebsiteTimezone = $useWebsiteTimezone;
}
/**
@@ -82,11 +91,9 @@ public function execute(int $batchCount, ?int $productId = null, bool $useAdditi
$prevKey = null;
$storeGroup = $this->storeManager->getGroup($website->getDefaultGroupId());
- $currentDate = $this->localeDate->scopeDate($storeGroup->getDefaultStoreId(), null, true);
- $previousDate = (clone $currentDate)->modify('-1 day');
- $previousDate->setTime(23, 59, 59);
- $nextDate = (clone $currentDate)->modify('+1 day');
- $nextDate->setTime(0, 0, 0);
+ $dateInterval = $this->useWebsiteTimezone
+ ? $this->getDateInterval((int)$storeGroup->getDefaultStoreId())
+ : $this->getDateInterval(Store::DEFAULT_STORE_ID);
while ($ruleData = $productsStmt->fetch()) {
$ruleProductId = $ruleData['product_id'];
@@ -107,7 +114,7 @@ public function execute(int $batchCount, ?int $productId = null, bool $useAdditi
/**
* Build prices for each day
*/
- foreach ([$previousDate, $currentDate, $nextDate] as $date) {
+ foreach ($dateInterval as $date) {
$time = $date->getTimestamp();
if (($ruleData['from_time'] == 0 ||
$time >= $ruleData['from_time']) && ($ruleData['to_time'] == 0 ||
@@ -157,4 +164,21 @@ public function execute(int $batchCount, ?int $productId = null, bool $useAdditi
return true;
}
+
+ /**
+ * Retrieve date sequence in store time zone
+ *
+ * @param int $storeId
+ * @return \DateTime[]
+ */
+ private function getDateInterval(int $storeId): array
+ {
+ $currentDate = $this->localeDate->scopeDate($storeId, null, true);
+ $previousDate = (clone $currentDate)->modify('-1 day');
+ $previousDate->setTime(23, 59, 59);
+ $nextDate = (clone $currentDate)->modify('+1 day');
+ $nextDate->setTime(0, 0, 0);
+
+ return [$previousDate, $currentDate, $nextDate];
+ }
}
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/SaveAndContinueEditCatalogPriceRuleActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/SaveAndContinueEditCatalogPriceRuleActionGroup.xml
new file mode 100644
index 0000000000000..cf8499ca1b466
--- /dev/null
+++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/SaveAndContinueEditCatalogPriceRuleActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ Clicks on Save and Continue Edit. Validates that the Success Message is present and correct on the Admin Catalog Price Rule creation/edit page.
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php
index 7f22634f9343d..230a6e3860950 100644
--- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php
+++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php
@@ -64,7 +64,8 @@ protected function setUp(): void
$this->ruleProductsSelectBuilderMock,
$this->productPriceCalculatorMock,
$this->localeDate,
- $this->pricesPersistorMock
+ $this->pricesPersistorMock,
+ true
);
}
diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php
index ddb6a85ed614a..50f4eb0805ed2 100644
--- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php
+++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php
@@ -21,6 +21,8 @@
class ReindexRuleProductTest extends TestCase
{
+ private const ADMIN_WEBSITE_ID = 0;
+
/**
* @var ReindexRuleProduct
*/
@@ -57,7 +59,8 @@ protected function setUp(): void
$this->resourceMock,
$this->activeTableSwitcherMock,
$this->tableSwapperMock,
- $this->localeDateMock
+ $this->localeDateMock,
+ true
);
}
@@ -85,6 +88,7 @@ public function testExecuteIfRuleWithoutWebsiteIds()
public function testExecute()
{
$websiteId = 3;
+ $adminTimeZone = 'America/Chicago';
$websiteTz = 'America/Los_Angeles';
$productIds = [
4 => [$websiteId => 1],
@@ -123,10 +127,11 @@ public function testExecute()
$ruleMock->expects($this->once())->method('getDiscountAmount')->willReturn(43);
$ruleMock->expects($this->once())->method('getStopRulesProcessing')->willReturn(true);
- $this->localeDateMock->expects($this->once())
- ->method('getConfigTimezone')
- ->with(ScopeInterface::SCOPE_WEBSITE, $websiteId)
- ->willReturn($websiteTz);
+ $this->localeDateMock->method('getConfigTimezone')
+ ->willReturnMap([
+ [ScopeInterface::SCOPE_WEBSITE, self::ADMIN_WEBSITE_ID, $adminTimeZone],
+ [ScopeInterface::SCOPE_WEBSITE, $websiteId, $websiteTz],
+ ]);
$batchRows = [
[
diff --git a/app/code/Magento/Config/etc/di.xml b/app/code/Magento/Config/etc/di.xml
index 4277ca0a6de26..76cfdbfa60b82 100644
--- a/app/code/Magento/Config/etc/di.xml
+++ b/app/code/Magento/Config/etc/di.xml
@@ -96,7 +96,7 @@
- Magento\Framework\Lock\Backend\Database
+ Magento\Framework\Lock\Proxy
diff --git a/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminClickCheckDataImportActionGroup.xml b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminClickCheckDataImportActionGroup.xml
new file mode 100644
index 0000000000000..4b072e0c60a31
--- /dev/null
+++ b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminClickCheckDataImportActionGroup.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ Clicks the 'Check Data' button on the Admin Import page.
+
+
+
+
+
+
diff --git a/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminClickImportActionGroup.xml b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminClickImportActionGroup.xml
new file mode 100644
index 0000000000000..2e64c35d8c822
--- /dev/null
+++ b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminClickImportActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ Clicks the 'Import' button on the Admin Import page.
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminFillImportFormActionGroup.xml b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminFillImportFormActionGroup.xml
new file mode 100644
index 0000000000000..a15c58b4d2b2e
--- /dev/null
+++ b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminFillImportFormActionGroup.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+ Fills the form on the System > Data Transfer > Import page.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminNavigateToImportHistoryPageActionGroup.xml b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminNavigateToImportHistoryPageActionGroup.xml
new file mode 100644
index 0000000000000..02a5990b9ad25
--- /dev/null
+++ b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminNavigateToImportHistoryPageActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+ Navigates to the admin System > Data Transfer > Import History page.
+
+
+
+
diff --git a/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminNavigateToImportPageActionGroup.xml b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminNavigateToImportPageActionGroup.xml
new file mode 100644
index 0000000000000..03c2fea998991
--- /dev/null
+++ b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminNavigateToImportPageActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+ Navigates to the admin System > Data Transfer > Import page.
+
+
+
+
diff --git a/app/code/Magento/ImportExport/Test/Mftf/Data/ImportData.xml b/app/code/Magento/ImportExport/Test/Mftf/Data/ImportData.xml
new file mode 100644
index 0000000000000..5a883af443348
--- /dev/null
+++ b/app/code/Magento/ImportExport/Test/Mftf/Data/ImportData.xml
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+ File is valid! To start import process press "Import" button
+ Import successfully done
+
+
+
+
+ Import1
+ import1
+ true
+ true
+
+
+
+
+ import-simple1
+ import-simple1
+ simple
+ 4
+ 12.00
+ 4
+ 1
+ 100
+ import-simple1
+ 12
+ EavStockItem
+ CustomAttributeCategoryIds
+
+
+ import-simple2
+ import-simple2
+ simple
+ 4
+ 15.00
+ 4
+ 1
+ 100
+ import-simple2
+ 12
+ EavStockItem
+ CustomAttributeCategoryIds
+
+
+ import-simple3
+ import-simple3
+ simple
+ 4
+ 10.00
+ 4
+ 1
+ 100
+ import-simple3
+ 12
+ EavStockItem
+ CustomAttributeCategoryIds
+
+
+ import_configurable_product.csv
+ Created: 4, Updated: 0, Deleted: 0
+ import-configurable
+ import-configurable
+ configurable
+ 4
+
+ 4
+ 1
+
+ import-configurable
+ 12
+ EavStockItem
+ CustomAttributeCategoryIds
+
+
+
+
+ 0
+ import_attribute1
+
+
+ import_attribute1
+ ProductAttributeFrontendLabelImport1
+
+
+ option3
+
+
+
+
+ test_image.jpg
+
+
+ Test Image
+ TestImageImageContentExportImport
+
+
diff --git a/app/code/Magento/ImportExport/Test/Mftf/Page/AdminImportHistoryPage.xml b/app/code/Magento/ImportExport/Test/Mftf/Page/AdminImportHistoryPage.xml
new file mode 100644
index 0000000000000..211d81582793f
--- /dev/null
+++ b/app/code/Magento/ImportExport/Test/Mftf/Page/AdminImportHistoryPage.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportSimpleAndConfigurableProductsWithAssignedImagesTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportSimpleAndConfigurableProductsWithAssignedImagesTest.xml
new file mode 100644
index 0000000000000..a046724f9ac8d
--- /dev/null
+++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportSimpleAndConfigurableProductsWithAssignedImagesTest.xml
@@ -0,0 +1,206 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AssertProductVideoNavigationArrowsActionGroup.xml b/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AssertProductVideoNavigationArrowsActionGroup.xml
new file mode 100644
index 0000000000000..d0673dfc1adb7
--- /dev/null
+++ b/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AssertProductVideoNavigationArrowsActionGroup.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+ Validates the navigation arrows on the Storefront Product page.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/ProductVideo/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
index e94d426ba7638..44c65c197f7db 100644
--- a/app/code/Magento/ProductVideo/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
+++ b/app/code/Magento/ProductVideo/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
@@ -14,5 +14,7 @@
+
+
diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Test/VimeoVideoControlButtonsOnProductPageTest.xml b/app/code/Magento/ProductVideo/Test/Mftf/Test/VimeoVideoControlButtonsOnProductPageTest.xml
new file mode 100644
index 0000000000000..a60a5526498ce
--- /dev/null
+++ b/app/code/Magento/ProductVideo/Test/Mftf/Test/VimeoVideoControlButtonsOnProductPageTest.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 acaf2afeb6c26..bfd685543d7f7 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
@@ -714,6 +714,7 @@ define([
}
$('.' + this.FTAR).addClass(this.isFullscreen ? 'fotorama__arr--shown' : 'fotorama__arr--hidden');
+ $('.' + this.FTVC).addClass('fotorama-show-control');
}
},
diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Creditmemo/Grid/Collection.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Creditmemo/Grid/Collection.php
index 46e26c97556e6..6960b34b1b32f 100644
--- a/app/code/Magento/Sales/Model/ResourceModel/Order/Creditmemo/Grid/Collection.php
+++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Creditmemo/Grid/Collection.php
@@ -33,32 +33,4 @@ public function __construct(
) {
parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $mainTable, $resourceModel);
}
-
- /**
- * @inheritDoc
- */
- protected function _translateCondition($field, $condition)
- {
- if ($field !== 'order_currency_code'
- && !isset($this->_map['fields'][$field])
- ) {
- $this->_map['fields'][$field] = 'main_table.' . $field;
- }
-
- return parent::_translateCondition($field, $condition);
- }
-
- /**
- * @inheritDoc
- */
- protected function _renderFiltersBefore()
- {
- $this->getSelect()->joinLeft(
- ['cgf' => $this->getTable('sales_order_grid')],
- 'main_table.order_id = cgf.entity_id',
- [
- 'order_currency_code' => 'order_currency_code',
- ]
- );
- }
}
diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Creditmemo/Order/Grid/Collection.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Creditmemo/Order/Grid/Collection.php
index 4d446b6c7620d..eab3998eebcbd 100644
--- a/app/code/Magento/Sales/Model/ResourceModel/Order/Creditmemo/Order/Grid/Collection.php
+++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Creditmemo/Order/Grid/Collection.php
@@ -33,32 +33,4 @@ public function __construct(
) {
parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $mainTable, $resourceModel);
}
-
- /**
- * @inheritDoc
- */
- protected function _translateCondition($field, $condition)
- {
- if ($field !== 'order_currency_code'
- && !isset($this->_map['fields'][$field])
- ) {
- $this->_map['fields'][$field] = 'main_table.' . $field;
- }
-
- return parent::_translateCondition($field, $condition);
- }
-
- /**
- * @inheritDoc
- */
- protected function _renderFiltersBefore()
- {
- $this->getSelect()->joinLeft(
- ['cgf' => $this->getTable('sales_order_grid')],
- 'main_table.order_id = cgf.entity_id',
- [
- 'order_currency_code' => 'order_currency_code',
- ]
- );
- }
}
diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Grid/Collection.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Grid/Collection.php
index 82c612c1a781d..094fac313d398 100644
--- a/app/code/Magento/Sales/Model/ResourceModel/Order/Grid/Collection.php
+++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Grid/Collection.php
@@ -5,16 +5,25 @@
*/
namespace Magento\Sales\Model\ResourceModel\Order\Grid;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Data\Collection\Db\FetchStrategyInterface as FetchStrategy;
use Magento\Framework\Data\Collection\EntityFactoryInterface as EntityFactory;
use Magento\Framework\Event\ManagerInterface as EventManager;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
+use Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult;
+use Magento\Sales\Model\ResourceModel\Order;
use Psr\Log\LoggerInterface as Logger;
/**
* Order grid collection
*/
-class Collection extends \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult
+class Collection extends SearchResult
{
+ /**
+ * @var TimezoneInterface
+ */
+ private $timeZone;
+
/**
* Initialize dependencies.
*
@@ -24,6 +33,7 @@ class Collection extends \Magento\Framework\View\Element\UiComponent\DataProvide
* @param EventManager $eventManager
* @param string $mainTable
* @param string $resourceModel
+ * @param TimezoneInterface|null $timeZone
*/
public function __construct(
EntityFactory $entityFactory,
@@ -31,9 +41,12 @@ public function __construct(
FetchStrategy $fetchStrategy,
EventManager $eventManager,
$mainTable = 'sales_order_grid',
- $resourceModel = \Magento\Sales\Model\ResourceModel\Order::class
+ $resourceModel = Order::class,
+ TimezoneInterface $timeZone = null
) {
parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $mainTable, $resourceModel);
+ $this->timeZone = $timeZone ?: ObjectManager::getInstance()
+ ->get(TimezoneInterface::class);
}
/**
@@ -50,4 +63,20 @@ protected function _initSelect()
return $this;
}
+
+ /**
+ * @inheritDoc
+ */
+ public function addFieldToFilter($field, $condition = null)
+ {
+ if ($field === 'created_at') {
+ if (is_array($condition)) {
+ foreach ($condition as $key => $value) {
+ $condition[$key] = $this->timeZone->convertConfigTimeToUtc($value);
+ }
+ }
+ }
+
+ return parent::addFieldToFilter($field, $condition);
+ }
}
diff --git a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_creditmemo_grid.xml b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_creditmemo_grid.xml
index a7e79f23b1cca..1fc8d41ce0900 100644
--- a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_creditmemo_grid.xml
+++ b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_creditmemo_grid.xml
@@ -194,14 +194,14 @@
false
-
+
textRange
false
-
+
textRange
diff --git a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_creditmemo_grid.xml b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_creditmemo_grid.xml
index 16a7e00b24bb3..09be15c5a3cf9 100644
--- a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_creditmemo_grid.xml
+++ b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_creditmemo_grid.xml
@@ -207,14 +207,14 @@
false
-
+
textRange
false
-
+
textRange
diff --git a/app/code/Magento/Search/Test/Mftf/ActionGroup/StoreFrontAssertDropDownSearchSuggestionActionGroup.xml b/app/code/Magento/Search/Test/Mftf/ActionGroup/StoreFrontAssertDropDownSearchSuggestionActionGroup.xml
new file mode 100644
index 0000000000000..226e30486251c
--- /dev/null
+++ b/app/code/Magento/Search/Test/Mftf/ActionGroup/StoreFrontAssertDropDownSearchSuggestionActionGroup.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+ Fills the Storefront Quick Search field. Validates that the Search Suggestion is present
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByControlButtonsTest.xml b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByControlButtonsTest.xml
new file mode 100644
index 0000000000000..b9c491705316c
--- /dev/null
+++ b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByControlButtonsTest.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Search/view/frontend/web/js/form-mini.js b/app/code/Magento/Search/view/frontend/web/js/form-mini.js
index b8034fead76d0..df651feb89d45 100644
--- a/app/code/Magento/Search/view/frontend/web/js/form-mini.js
+++ b/app/code/Magento/Search/view/frontend/web/js/form-mini.js
@@ -253,6 +253,8 @@ define([
}
this.element.val(this.responseList.selected.find('.qs-option-name').text());
this.element.attr('aria-activedescendant', this.responseList.selected.attr('id'));
+ this._updateAriaHasPopup(true);
+ this.autoComplete.show();
}
break;
@@ -269,6 +271,8 @@ define([
}
this.element.val(this.responseList.selected.find('.qs-option-name').text());
this.element.attr('aria-activedescendant', this.responseList.selected.attr('id'));
+ this._updateAriaHasPopup(true);
+ this.autoComplete.show();
}
break;
default:
diff --git a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridSortColumnAscendingActionGroup.xml b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridSortColumnAscendingActionGroup.xml
new file mode 100644
index 0000000000000..c11f0bb1dd720
--- /dev/null
+++ b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridSortColumnAscendingActionGroup.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+ Sorts the specified column in ascending order on Admin Grid page.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridSortColumnDescendingActionGroup.xml b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridSortColumnDescendingActionGroup.xml
new file mode 100644
index 0000000000000..0d1ab2a8ac8b5
--- /dev/null
+++ b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridSortColumnDescendingActionGroup.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+ Sorts the specified column in descending order on Admin Grid page.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridHeaderSection.xml b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridHeaderSection.xml
index 4ee38e30f98e6..15835aa439b08 100644
--- a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridHeaderSection.xml
+++ b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridHeaderSection.xml
@@ -27,5 +27,10 @@
+
+
+
+
+
diff --git a/dev/tests/acceptance/tests/_data/import_configurable_product.csv b/dev/tests/acceptance/tests/_data/import_configurable_product.csv
new file mode 100644
index 0000000000000..880e0bfb64c2e
--- /dev/null
+++ b/dev/tests/acceptance/tests/_data/import_configurable_product.csv
@@ -0,0 +1,5 @@
+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,deferred_stock_update,use_config_deferred_stock_update,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,associated_skus,downloadable_links,downloadable_samples,configurable_variations,configurable_variation_labels
+import-simple1,,Default,simple,Default Category/Import1,base,import-simple1,,,12,1,Taxable Goods,Not Visible Individually,12,,,,import-simple1,Conf11,Conf11,Conf11 ,/m/a/magento-logo.png,Magento Logo,/m/a/magento-logo.png,Magento Logo,/m/a/magento-logo.png,Magento Logo,,,"10/5/20, 4:58 PM","10/5/20, 4:58 PM",,,Block after Info Column,,,,No,,,,,,,,,"import_attribute1=option1,gift_wrapping_available=Yes",100,0,1,0,0,1,1,1,10000,1,1,1,1,1,1,1,1,1,0,0,0,0,1,,,,,,,/m/a/magento-logo.png,Magento Logo,,,,,,,,,,,,,
+import-simple2,,Default,simple,Default Category/Import1,base,import-simple2,,,12,1,Taxable Goods,Not Visible Individually,15,,,,import-simple2,Conf11,Conf11,Conf11 ,/t/e/test_image.jpg,Test Image,/t/e/test_image.jpg,Test Image,/t/e/test_image.jpg,Test Image,,,"10/5/20, 4:58 PM","10/5/20, 4:58 PM",,,Block after Info Column,,,,No,,,,,,,,,"import_attribute1=option2,gift_wrapping_available=Yes",100,0,1,0,0,1,1,1,10000,1,1,1,1,1,1,1,1,1,0,0,0,0,1,,,,,,,/t/e/test_image.jpg,Test Image,,,,,,,,,,,,,
+import-simple3,,Default,simple,Default Category/Import1,base,import-simple3,,,12,1,Taxable Goods,Not Visible Individually,10,,,,import-simple3,Conf11,Conf11,Conf11 ,,,,,,,,,"10/5/20, 4:58 PM","10/5/20, 4:58 PM",,,Block after Info Column,,,,No,,,,,,,,,"import_attribute1=option3,gift_wrapping_available=Yes",100,0,1,0,0,1,1,1,10000,1,1,1,1,1,1,1,1,1,0,0,0,0,1,,,,,,,,,,,,,,,,,,,,,
+import-configurable,,Default,configurable,Default Category/Import1,base,import-configurable,,,12,1,Taxable Goods,"Catalog, Search",,,,,import-configurable,Conf11,Conf11,Conf11 ,,,,,,,,,"10/5/20, 4:58 PM","10/5/20, 4:58 PM",,,Block after Info Column,,,,Use config,,,,,,,Use config,,gift_wrapping_available=Use config,0,0,1,0,0,1,1,1,10000,1,1,1,1,1,1,1,1,1,0,0,0,0,1,,,,,,,,",,",,,,,,,,,,,,"sku=import-simple1,import_attribute1=option1|sku=import-simple2,import_attribute1=option2|sku=import-simple3,import_attribute1=option3",import_attribute1=import_attribute1
\ No newline at end of file
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/RelatedProduct/GetRelatedProductsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/RelatedProduct/GetRelatedProductsTest.php
index f2cf90c95de18..8575f1d33c435 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/RelatedProduct/GetRelatedProductsTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/RelatedProduct/GetRelatedProductsTest.php
@@ -190,4 +190,40 @@ private function assertRelatedProducts(array $relatedProducts): void
self::assertEquals($product['url_key'], $productExpectedData['url_key']);
}
}
+
+ /**
+ * Test query with disabled linked product in the default store
+ *
+ * @magentoApiDataFixture Magento/Catalog/_files/products_related_disabled_in_store.php
+ *
+ * @return void
+ */
+ public function testQueryDisableRelatedProductInStore(): void
+ {
+ $productSku = 'simple_with_related';
+ $query = <<graphQlQuery($query, [], '', ['Store' => 'default']);
+
+ self::assertArrayHasKey('products', $response);
+ self::assertArrayHasKey('items', $response['products']);
+ self::assertCount(1, $response['products']['items']);
+ self::assertArrayHasKey(0, $response['products']['items']);
+ self::assertArrayHasKey('related_products', $response['products']['items'][0]);
+ $relatedProducts = $response['products']['items'][0]['related_products'];
+ self::assertCount(0, $relatedProducts);
+ }
}
diff --git a/dev/tests/api-functional/testsuite/Magento/Swatches/Api/ProductAttributeOptionManagementInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Swatches/Api/ProductAttributeOptionManagementInterfaceTest.php
index 39ca42b57511e..f4eeb12170f4a 100644
--- a/dev/tests/api-functional/testsuite/Magento/Swatches/Api/ProductAttributeOptionManagementInterfaceTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/Swatches/Api/ProductAttributeOptionManagementInterfaceTest.php
@@ -12,6 +12,7 @@
use Magento\Eav\Api\Data\AttributeOptionInterface;
use Magento\Eav\Api\Data\AttributeOptionLabelInterface;
use Magento\Eav\Model\AttributeRepository;
+use Magento\Framework\DataObject;
use Magento\Framework\Webapi\Rest\Request;
use Magento\Swatches\Model\ResourceModel\Swatch\Collection;
use Magento\Swatches\Model\ResourceModel\Swatch\CollectionFactory;
@@ -25,6 +26,7 @@
class ProductAttributeOptionManagementInterfaceTest extends WebapiAbstract
{
private const ATTRIBUTE_CODE = 'select_attribute';
+ private const SERVICE_NAME_UPDATE = 'catalogProductAttributeOptionUpdateV1';
private const SERVICE_NAME = 'catalogProductAttributeOptionManagementV1';
private const SERVICE_VERSION = 'V1';
private const RESOURCE_PATH = '/V1/products/attributes';
@@ -32,6 +34,7 @@ class ProductAttributeOptionManagementInterfaceTest extends WebapiAbstract
/**
* Test add option to swatch attribute
*
+ * @dataProvider addDataProvider
* @magentoApiDataFixture Magento/Catalog/Model/Product/Attribute/_files/select_attribute.php
* @param array $data
* @param array $payload
@@ -39,7 +42,7 @@ class ProductAttributeOptionManagementInterfaceTest extends WebapiAbstract
* @param string $expectedLabel
* @param string $expectedValue
*
- * @dataProvider addDataProvider
+ * @return void
*/
public function testAdd(
array $data,
@@ -47,7 +50,7 @@ public function testAdd(
int $expectedSwatchType,
string $expectedLabel,
string $expectedValue
- ) {
+ ): void {
$objectManager = Bootstrap::getObjectManager();
/** @var $attributeRepository AttributeRepository */
$attributeRepository = $objectManager->get(AttributeRepository::class);
@@ -74,7 +77,7 @@ public function testAdd(
);
$this->assertNotNull($response);
- $optionId = (int) ltrim($response, 'id_');
+ $optionId = (int)ltrim($response, 'id_');
$swatch = $this->getSwatch($optionId);
$this->assertEquals($expectedValue, $swatch->getValue());
$this->assertEquals($expectedSwatchType, $swatch->getType());
@@ -83,11 +86,47 @@ public function testAdd(
$this->assertEquals($expectedLabel, $options[2]->getLabel());
}
+ /**
+ * @magentoApiDataFixture Magento/Swatches/_files/text_swatch_attribute.php
+ * @return void
+ */
+ public function testUpdate(): void
+ {
+ $testAttributeCode = 'test_configurable';
+ $optionData = [
+ AttributeOptionInterface::LABEL => 'Fixture Option Changed',
+ AttributeOptionInterface::VALUE => 'option_value',
+ ];
+
+ $existOptionLabel = 'option 1';
+ $existAttributeOption = $this->getAttributeOption($testAttributeCode, $existOptionLabel);
+ $optionId = $existAttributeOption['value'];
+
+ $response = $this->webApiCallAttributeOptions(
+ $testAttributeCode,
+ Request::HTTP_METHOD_PUT,
+ 'update',
+ [
+ 'attributeCode' => $testAttributeCode,
+ 'optionId' => $optionId,
+ 'option' => $optionData,
+ ],
+ $optionId
+ );
+ $this->assertTrue($response);
+ $this->assertNotNull(
+ $this->getAttributeOption(
+ $testAttributeCode,
+ $optionData[AttributeOptionInterface::LABEL]
+ )
+ );
+ }
+
/**
* @return array
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
- public function addDataProvider()
+ public function addDataProvider(): array
{
return [
'visual swatch option with value' => [
@@ -212,14 +251,99 @@ public function addDataProvider()
* Get swatch model
*
* @param int $optionId
- * @return Swatch
+ * @return DataObject
*/
- private function getSwatch(int $optionId)
+ private function getSwatch(int $optionId): DataObject
{
/** @var Collection $collection */
$collection = Bootstrap::getObjectManager()->get(CollectionFactory::class)->create();
$collection->addFieldToFilter('option_id', $optionId);
$collection->setPageSize(1);
+
return $collection->getFirstItem();
}
+
+ /**
+ * Perform Web API call to the system under test
+ *
+ * @param string $attributeCode
+ * @param string $httpMethod
+ * @param string $soapMethod
+ * @param array $arguments
+ * @param null $storeCode
+ * @param null $optionId
+ * @return array|bool|float|int|string
+ */
+ private function webApiCallAttributeOptions(
+ string $attributeCode,
+ string $httpMethod,
+ string $soapMethod,
+ array $arguments = [],
+ $optionId = null,
+ $storeCode = null
+ ) {
+ $resourcePath = self::RESOURCE_PATH . "/{$attributeCode}/options";
+ if ($optionId) {
+ $resourcePath .= '/' . $optionId;
+ }
+ $serviceName = $soapMethod === 'update' ? self::SERVICE_NAME_UPDATE : self::SERVICE_NAME;
+ $serviceInfo = [
+ 'rest' => [
+ 'resourcePath' => $resourcePath,
+ 'httpMethod' => $httpMethod,
+ ],
+ 'soap' => [
+ 'service' => $serviceName,
+ 'serviceVersion' => self::SERVICE_VERSION,
+ 'operation' => $serviceName . $soapMethod,
+ ],
+ ];
+
+ return $this->_webApiCall($serviceInfo, $arguments, null, $storeCode);
+ }
+
+ /**
+ * Get Attribute options by attribute code
+ *
+ * @param string $testAttributeCode
+ * @param string|null $storeCode
+ * @return array|bool|float|int|string
+ */
+ private function getAttributeOptions(string $testAttributeCode, ?string $storeCode = null)
+ {
+ return $this->webApiCallAttributeOptions(
+ $testAttributeCode,
+ Request::HTTP_METHOD_GET,
+ 'getItems',
+ ['attributeCode' => $testAttributeCode],
+ null,
+ $storeCode
+ );
+ }
+
+ /**
+ * Get Attribute option by attribute code
+ *
+ * @param string $attributeCode
+ * @param string $optionLabel
+ * @param string|null $storeCode
+ * @return array|null
+ */
+ private function getAttributeOption(
+ string $attributeCode,
+ string $optionLabel,
+ ?string $storeCode = null
+ ): ?array {
+ $attributeOptions = $this->getAttributeOptions($attributeCode, $storeCode);
+ $option = null;
+ /** @var array $attributeOption */
+ foreach ($attributeOptions as $attributeOption) {
+ if ($attributeOption['label'] === $optionLabel) {
+ $option = $attributeOption;
+ break;
+ }
+ }
+
+ return $option;
+ }
}
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts/AbstractAlertTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts/AbstractAlertTest.php
new file mode 100644
index 0000000000000..8ac709c584648
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts/AbstractAlertTest.php
@@ -0,0 +1,91 @@
+get(Manager::class);
+ //This check is needed because module Magento_Catalog is independent of Magento_ProductAlert
+ if (!$moduleManager->isEnabled('Magento_ProductAlert')) {
+ self::markTestSkipped('Magento_ProductAlert module disabled.');
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->objectManager = Bootstrap::getObjectManager();
+ $this->storeManager = $this->objectManager->get(StoreManagerInterface::class);
+ $this->request = $this->objectManager->get(RequestInterface::class);
+ $this->productResource = $this->objectManager->get(ProductResource::class);
+ }
+
+ /**
+ * Prepare request
+ *
+ * @param string|null $sku
+ * @param string|null $storeCode
+ * @return void
+ */
+ protected function prepareRequest(?string $sku = null, ?string $storeCode = null): void
+ {
+ $productId = (int)$this->productResource->getIdBySku($sku);
+ $storeId = $storeCode ? (int)$this->storeManager->getStore($storeCode)->getId() : null;
+ $this->request->setParams(['id' => $productId, 'store' => $storeId]);
+ }
+
+ /**
+ * Assert grid url
+ *
+ * @param string $url
+ * @param string|null $storeCode
+ * @return void
+ */
+ protected function assertGridUrl(string $url, ?string $storeCode): void
+ {
+ $storeId = $storeCode ? (int)$this->storeManager->getStore($storeCode)->getId() : Store::DEFAULT_STORE_ID;
+ $this->assertStringContainsString(sprintf('/store/%s', $storeId), $url);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts/PriceTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts/PriceTest.php
new file mode 100644
index 0000000000000..38f03a4558507
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts/PriceTest.php
@@ -0,0 +1,99 @@
+block = $this->objectManager->get(LayoutInterface::class)->createBlock(Price::class);
+ }
+
+ /**
+ * @dataProvider alertsDataProvider
+ *
+ * @magentoDbIsolation disabled
+ *
+ * @magentoDataFixture Magento/ProductAlert/_files/product_alert.php
+ * @magentoDataFixture Magento/ProductAlert/_files/price_alert_on_second_website.php
+ *
+ * @param string $sku
+ * @param string $expectedEmail
+ * @param string|null $storeCode
+ * @return void
+ */
+ public function testGridCollectionWithStoreId(string $sku, string $expectedEmail, ?string $storeCode = null): void
+ {
+ $this->prepareRequest($sku, $storeCode);
+ $collection = $this->block->getPreparedCollection();
+ $this->assertCount(1, $collection);
+ $this->assertEquals($expectedEmail, $collection->getFirstItem()->getEmail());
+ }
+
+ /**
+ * @return array
+ */
+ public function alertsDataProvider(): array
+ {
+ return [
+ 'without_store_id_filter' => [
+ 'product_sku' => 'simple',
+ 'expected_customer_emails' => 'customer@example.com',
+ ],
+ 'with_store_id_filter' => [
+ 'product_sku' => 'simple_on_second_website_for_price_alert',
+ 'expected_customer_emails' => 'customer_second_ws_with_addr@example.com',
+ 'store_code' => 'fixture_third_store',
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider storeProvider
+ *
+ * @param string|null $storeCode
+ * @return void
+ */
+ public function testGetGridUrl(?string $storeCode): void
+ {
+ $this->prepareRequest(null, $storeCode);
+ $this->assertGridUrl($this->block->getGridUrl(), $storeCode);
+ }
+
+ /**
+ * @return array
+ */
+ public function storeProvider(): array
+ {
+ return [
+ 'without_store_id_param' => [
+ 'store_code' => null,
+ ],
+ 'with_store_id_param' => [
+ 'store_code' => 'default',
+ ],
+ ];
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts/StockTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts/StockTest.php
index b9ccfd6d52458..b221695a78e3b 100644
--- a/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts/StockTest.php
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts/StockTest.php
@@ -7,12 +7,7 @@
namespace Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Alerts;
-use Magento\Catalog\Api\ProductRepositoryInterface;
-use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\View\LayoutInterface;
-use Magento\Store\Model\StoreManagerInterface;
-use Magento\TestFramework\Helper\Bootstrap;
-use PHPUnit\Framework\TestCase;
/**
* Check stock alert grid
@@ -21,20 +16,11 @@
*
* @magentoAppArea adminhtml
*/
-class StockTest extends TestCase
+class StockTest extends AbstractAlertTest
{
- /** @var ObjectManagerInterface */
- private $objectManager;
-
/** @var Stock */
private $block;
- /** @var StoreManagerInterface */
- private $storeManager;
-
- /** @var ProductRepositoryInterface */
- private $productRepository;
-
/**
* @inheritdoc
*/
@@ -42,10 +28,7 @@ protected function setUp(): void
{
parent::setUp();
- $this->objectManager = Bootstrap::getObjectManager();
$this->block = $this->objectManager->get(LayoutInterface::class)->createBlock(Stock::class);
- $this->storeManager = $this->objectManager->get(StoreManagerInterface::class);
- $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
}
/**
@@ -62,9 +45,7 @@ protected function setUp(): void
*/
public function testGridCollectionWithStoreId(string $sku, string $expectedEmail, ?string $storeCode = null): void
{
- $productId = (int)$this->productRepository->get($sku)->getId();
- $storeId = $storeCode ? (int)$this->storeManager->getStore($storeCode)->getId() : null;
- $this->block->getRequest()->setParams(['id' => $productId, 'store' => $storeId]);
+ $this->prepareRequest($sku, $storeCode);
$collection = $this->block->getPreparedCollection();
$this->assertCount(1, $collection);
$this->assertEquals($expectedEmail, $collection->getFirstItem()->getEmail());
@@ -87,4 +68,31 @@ public function alertsDataProvider(): array
],
];
}
+
+ /**
+ * @dataProvider storeProvider
+ *
+ * @param string|null $storeCode
+ * @return void
+ */
+ public function testGetGridUrl(?string $storeCode): void
+ {
+ $this->prepareRequest(null, $storeCode);
+ $this->assertGridUrl($this->block->getGridUrl(), $storeCode);
+ }
+
+ /**
+ * @return array
+ */
+ public function storeProvider(): array
+ {
+ return [
+ 'without_store_id_param' => [
+ 'store_code' => null,
+ ],
+ 'with_store_id_param' => [
+ 'store_code' => 'default',
+ ],
+ ];
+ }
}
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/EditTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/EditTest.php
new file mode 100644
index 0000000000000..f8a22ad05172c
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/EditTest.php
@@ -0,0 +1,74 @@
+categoryCollectionFactory = $this->_objectManager->get(CollectionFactory::class);
+ $this->storeManager = $this->_objectManager->get(StoreManagerInterface::class);
+ }
+
+ /**
+ * @magentoDataFixture Magento/Catalog/_files/second_root_category.php
+ *
+ * @return void
+ */
+ public function testSwitchingStoreViewsCategory(): void
+ {
+ $this->getRequest()->setMethod(HttpRequest::METHOD_GET);
+ $id = (int)$this->getCategoryIdByName('Second Root Category');
+ $storeId = (int)$this->storeManager->getStore('default')->getId();
+ $this->getRequest()->setParams(['store' => $storeId, 'id' => $id]);
+ $this->dispatch('backend/catalog/category/edit');
+ $this->assertRedirect($this->stringContains('backend/catalog/category/index'));
+ $this->assertStringNotContainsString('/id/', $this->getResponse()->getHeader('Location')->getFieldValue());
+ }
+
+ /**
+ * Get category id by name
+ *
+ * @param string $name
+ * @return string|null
+ */
+ private function getCategoryIdByName(string $name): ?string
+ {
+ $categoryCollection = $this->categoryCollectionFactory->create();
+ $category = $categoryCollection
+ ->addAttributeToFilter(CategoryInterface::KEY_NAME, $name)
+ ->setPageSize(1)
+ ->getFirstItem();
+
+ return $category->getId();
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryTest.php
index 3a93161517301..7e921b5c7ac12 100644
--- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryTest.php
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryTest.php
@@ -15,6 +15,7 @@
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Message\MessageInterface;
use Magento\Store\Model\StoreManagerInterface;
+use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory;
/**
* Test cases for save category controller.
@@ -36,6 +37,9 @@ class SaveCategoryTest extends AbstractSaveCategoryTest
/** @var StoreManagerInterface */
private $storeManager;
+ /** @var CollectionFactory */
+ private $categoryCollectionFactory;
+
/**
* @inheritdoc
*/
@@ -46,6 +50,7 @@ protected function setUp(): void
$this->categoryRepository = $this->_objectManager->get(CategoryRepositoryInterface::class);
$this->getBlockByIdentifier = $this->_objectManager->get(GetBlockByIdentifierInterface::class);
$this->storeManager = $this->_objectManager->get(StoreManagerInterface::class);
+ $this->categoryCollectionFactory = $this->_objectManager->get(CollectionFactory::class);
}
/**
@@ -109,4 +114,37 @@ public function testTryToCreateCategoryWithEmptyValues(): void
);
$this->assertSessionMessages($this->containsEqual($message), MessageInterface::TYPE_ERROR);
}
+
+ /**
+ * @magentoDataFixture Magento/Catalog/_files/second_root_category.php
+ *
+ * @return void
+ */
+ public function testSwitchingStoreViewsCategory(): void
+ {
+ $this->getRequest()->setMethod(HttpRequest::METHOD_POST);
+ $id = (int)$this->getCategoryIdByName('Second Root Category');
+ $storeId = (int)$this->storeManager->getStore('default')->getId();
+ $this->getRequest()->setParams(['store' => $storeId, 'id' => $id]);
+ $this->dispatch('backend/catalog/category/save');
+ $this->assertRedirect($this->stringContains('backend/catalog/category/index'));
+ $this->assertStringNotContainsString('/id/', $this->getResponse()->getHeader('Location')->getFieldValue());
+ }
+
+ /**
+ * Get category id by name
+ *
+ * @param string $name
+ * @return string|null
+ */
+ private function getCategoryIdByName(string $name): ?string
+ {
+ $categoryCollection = $this->categoryCollectionFactory->create();
+ $category = $categoryCollection
+ ->addAttributeToFilter(CategoryInterface::KEY_NAME, $name)
+ ->setPageSize(1)
+ ->getFirstItem();
+
+ return $category->getId();
+ }
}
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AbstractAlertTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AbstractAlertTest.php
new file mode 100644
index 0000000000000..5eea3b214435d
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AbstractAlertTest.php
@@ -0,0 +1,76 @@
+productResource = $this->_objectManager->get(ProductResource::class);
+ $this->storeManager = $this->_objectManager->get(StoreManagerInterface::class);
+ }
+
+ /**
+ * Prepare request
+ *
+ * @param string $productSku
+ * @param string $storeCode
+ * @param int|null $limit
+ * @return void
+ */
+ protected function prepareRequest(string $productSku, string $storeCode, ?int $limit): void
+ {
+ $productId = $this->productResource->getIdBySku($productSku);
+ $storeId = $this->storeManager->getStore($storeCode)->getId();
+ $this->getRequest()->setMethod(HttpRequest::METHOD_GET);
+ $this->getRequest()->setParams(['id' => $productId, 'store' => $storeId, 'limit' => $limit]);
+ }
+
+ /**
+ * Assert alert grid records count related to provided email
+ *
+ * @param string $email
+ * @param int $expectedCount
+ * @return void
+ */
+ protected function assertGridRecords(string $email, int $expectedCount): void
+ {
+ $content = $this->getResponse()->getContent();
+ $this->assertEquals(
+ $expectedCount,
+ Xpath::getElementsCountForXpath(sprintf($this->getRecordXpathTemplate(), $email), $content)
+ );
+ }
+
+ /**
+ * Get alert grid record xpath template
+ *
+ * @return string
+ */
+ abstract protected function getRecordXpathTemplate(): string;
+}
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AlertsPriceGridTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AlertsPriceGridTest.php
new file mode 100644
index 0000000000000..f9eafaf2cdc19
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AlertsPriceGridTest.php
@@ -0,0 +1,63 @@
+prepareRequest('simple', 'default', $limit);
+ $this->dispatch('backend/catalog/product/alertsPriceGrid');
+ $this->assertGridRecords($email, $expectedCount);
+ }
+
+ /**
+ * @return array
+ */
+ public function priceLimitProvider(): array
+ {
+ return [
+ 'default_limit' => [
+ 'email' => 'customer@example.com',
+ 'limit' => null,
+ 'expected_count' => 2,
+ ],
+ 'limit_1' => [
+ 'email' => 'customer@example.com',
+ 'limit' => 1,
+ 'expected_count' => 1,
+ ],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function getRecordXpathTemplate(): string
+ {
+ return "//div[@id='alertPrice']//tbody/tr/td[contains(text(), '%s')]";
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AlertsStockGridTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AlertsStockGridTest.php
new file mode 100644
index 0000000000000..06e3fbda4c69e
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AlertsStockGridTest.php
@@ -0,0 +1,63 @@
+prepareRequest('simple', 'default', $limit);
+ $this->dispatch('backend/catalog/product/alertsStockGrid');
+ $this->assertGridRecords($email, $expectedCount);
+ }
+
+ /**
+ * @return array
+ */
+ public function stockLimitProvider(): array
+ {
+ return [
+ 'default_limit' => [
+ 'email' => 'customer@example.com',
+ 'limit' => null,
+ 'expected_count' => 2,
+ ],
+ 'limit_1' => [
+ 'email' => 'customer@example.com',
+ 'limit' => 1,
+ 'expected_count' => 1,
+ ],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function getRecordXpathTemplate(): string
+ {
+ return "//div[@id='alertStock']//tbody/tr/td[contains(text(), '%s')]";
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Attribute/Backend/ConsumerWebsiteAssignTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Attribute/Backend/ConsumerWebsiteAssignTest.php
new file mode 100644
index 0000000000000..0edddd1b34f6f
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Attribute/Backend/ConsumerWebsiteAssignTest.php
@@ -0,0 +1,262 @@
+get(DeleteTopicRelatedMessages::class);
+ self::$deleteTopicRelatedMessages->execute(self::TOPIC_NAME);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->objectManager = Bootstrap::getObjectManager();
+ $this->consumer = $this->objectManager->get(ConsumerWebsiteAssign::class);
+ $this->queue = $this->objectManager->create(
+ Queue::class,
+ ['queueName' => 'product_action_attribute.website.update']
+ );
+ $this->messageEncoder = $this->objectManager->get(MessageEncoder::class);
+ $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
+ $this->websiteRepository = $this->objectManager->get(WebsiteRepositoryInterface::class);
+ $this->operationCollectionFactory = $this->objectManager->get(CollectionFactory::class);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function tearDown(): void
+ {
+ $this->objectManager->removeSharedInstance(Action::class);
+ self::$deleteTopicRelatedMessages->execute(self::TOPIC_NAME);
+
+ parent::tearDown();
+ }
+
+ /**
+ * @magentoDataFixture Magento/Catalog/_files/update_product_website_quene_data.php
+ *
+ * @return void
+ */
+ public function testAddWebsite(): void
+ {
+ $this->processMessages();
+ $this->assertProductWebsites('simple2', ['base', 'test']);
+ $this->assertOperation(OperationInterface::STATUS_TYPE_COMPLETE);
+ }
+
+ /**
+ * @magentoDataFixture Magento/Catalog/_files/detach_product_website_quene_data.php
+ *
+ * @return void
+ */
+ public function testRemoveWebsite(): void
+ {
+ $this->processMessages();
+ $this->assertProductWebsites('unique-simple-azaza', ['base']);
+ $this->assertOperation(OperationInterface::STATUS_TYPE_COMPLETE);
+ }
+
+ /**
+ * @magentoDataFixture Magento/Catalog/_files/update_product_website_quene_data.php
+ *
+ * @return void
+ */
+ public function testAddWebsiteToDeletedProduct(): void
+ {
+ $expectedMessage = __('Something went wrong while adding products to websites.');
+ $this->productRepository->deleteById('simple2');
+ $this->processMessages();
+ $this->assertOperation(OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED, (string)$expectedMessage);
+ }
+
+ /**
+ * @dataProvider errorProvider
+ *
+ * @magentoDataFixture Magento/Catalog/_files/update_product_website_quene_data.php
+ *
+ * @param \Throwable $exception
+ * @param int $code
+ * @return void
+ */
+ public function testWithException(\Throwable $exception, int $code): void
+ {
+ $this->prepareMock($exception);
+ $this->processMessages();
+ $this->assertOperation($code, $exception->getMessage());
+ }
+
+ /**
+ * @return array
+ */
+ public function errorProvider(): array
+ {
+ return [
+ 'with_dead_lock_exception' => [
+ 'exception' => new DeadlockException('Test lock'),
+ 'code' => OperationDataInterface::STATUS_TYPE_RETRIABLY_FAILED,
+ ],
+ 'with_db_exception' => [
+ 'exception' => new \Zend_Db_Adapter_Exception(
+ (string)__(
+ 'Sorry, something went wrong during product attributes update. Please see log for details.'
+ )
+ ),
+ 'code' => OperationDataInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED,
+ ],
+ 'with_no_such_entity_exception' => [
+ 'exception' => new NoSuchEntityException(),
+ 'code' => OperationDataInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED,
+ ],
+ 'with_general_exception' => [
+ 'exception' => new \Exception(
+ (string)__(
+ 'Sorry, something went wrong during product attributes update. Please see log for details.'
+ )
+ ),
+ 'code' => OperationDataInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED,
+ ],
+ ];
+ }
+
+ /**
+ * Assert product website ids
+ *
+ * @param string $sku
+ * @param array $expectedWebsites
+ * @return void
+ */
+ private function assertProductWebsites(string $sku, array $expectedWebsites): void
+ {
+ $product = $this->productRepository->get($sku, false, null, true);
+ $websitesIds = $product->getWebsiteIds();
+ $this->assertCount(count($expectedWebsites), $websitesIds);
+
+ foreach ($expectedWebsites as $expectedWebsite) {
+ $expectedWebsiteId = $this->websiteRepository->get($expectedWebsite)->getId();
+ $this->assertContains($expectedWebsiteId, $websitesIds);
+ }
+ }
+
+ /**
+ * Process current consumer topic messages
+ *
+ * @return void
+ */
+ private function processMessages(): void
+ {
+ $envelope = $this->queue->dequeue();
+ $decodedMessage = $this->messageEncoder->decode(self::TOPIC_NAME, $envelope->getBody());
+ $this->consumer->process($decodedMessage);
+ }
+
+ /**
+ * Get last current topic related operation
+ *
+ * @return OperationDataInterface
+ */
+ private function getLastTopicOperation(): OperationDataInterface
+ {
+ $collection = $this->operationCollectionFactory->create();
+ $collection->addFieldToFilter('topic_name', self::TOPIC_NAME);
+ $collection->setPageSize(1)->setCurPage($collection->getLastPageNumber());
+
+ return $collection->getLastItem();
+ }
+
+ /**
+ * Assert performed operation
+ *
+ * @param int $status
+ * @param string|null $resultMessage
+ * @return void
+ */
+ private function assertOperation(int $status, ?string $resultMessage = null): void
+ {
+ $operation = $this->getLastTopicOperation();
+ $this->assertNotNull($operation->getData('id'));
+ $this->assertEquals($status, $operation->getStatus());
+ $this->assertEquals($resultMessage, $operation->getResultMessage());
+ }
+
+ /**
+ * Create mock with provided exception
+ *
+ * @param \Throwable $exception
+ * @return void
+ */
+ private function prepareMock(\Throwable $exception): void
+ {
+ $object = $this->createPartialMock(Action::class, ['updateWebsites']);
+ $object->method('updateWebsites')->willThrowException($exception);
+ $this->objectManager->addSharedInstance($object, Action::class);
+ $this->consumer = $this->objectManager->create(ConsumerWebsiteAssign::class);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Flat/Action/FullTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Flat/Action/FullTest.php
index 4313f95f24a8e..bc96b30d55c04 100644
--- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Flat/Action/FullTest.php
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Flat/Action/FullTest.php
@@ -3,40 +3,52 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Catalog\Model\Indexer\Product\Flat\Action;
use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Block\Product\ListProduct;
use Magento\Catalog\Model\CategoryFactory;
use Magento\Catalog\Model\Indexer\Product\Flat\Processor;
use Magento\Catalog\Model\Indexer\Product\Flat\State;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory;
+use Magento\Catalog\Model\ResourceModel\Product\Flat as FlatResource;
use Magento\CatalogSearch\Model\Indexer\Fulltext;
+use Magento\Eav\Api\AttributeOptionManagementInterface;
use Magento\Framework\Indexer\IndexerRegistry;
+use Magento\Framework\ObjectManagerInterface;
use Magento\Store\Model\StoreManagerInterface;
use Magento\TestFramework\Helper\Bootstrap;
-use Magento\TestFramework\ObjectManager;
+use Magento\TestFramework\Indexer\TestCase;
/**
* Full reindex Test
*/
-class FullTest extends \Magento\TestFramework\Indexer\TestCase
+class FullTest extends TestCase
{
- /**
- * @var State
- */
- protected $_state;
+ /** @var State */
+ private $state;
- /**
- * @var Processor
- */
- protected $_processor;
+ /** @var Processor */
+ private $processor;
- /**
- * @var ObjectManager
- */
+ /** @var ObjectManagerInterface */
private $objectManager;
+ /** @var FlatResource */
+ private $flatResource;
+
+ /** @var AttributeOptionManagementInterface */
+ private $optionManagement;
+
+ /** @var ProductRepositoryInterface */
+ private $productRepository;
+
+ /** @var Full */
+ private $action;
+
/**
* @inheritdoc
*/
@@ -58,9 +70,15 @@ public static function setUpBeforeClass(): void
*/
protected function setUp(): void
{
+ parent::setUp();
+
$this->objectManager = Bootstrap::getObjectManager();
- $this->_state = $this->objectManager->get(State::class);
- $this->_processor = $this->objectManager->get(Processor::class);
+ $this->state = $this->objectManager->get(State::class);
+ $this->processor = $this->objectManager->get(Processor::class);
+ $this->flatResource = $this->objectManager->get(FlatResource::class);
+ $this->optionManagement = $this->objectManager->get(AttributeOptionManagementInterface::class);
+ $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
+ $this->action = $this->objectManager->get(Full::class);
}
/**
@@ -68,11 +86,13 @@ protected function setUp(): void
* @magentoAppIsolation enabled
* @magentoConfigFixture current_store catalog/frontend/flat_catalog_product 1
* @magentoDataFixture Magento/Catalog/_files/product_simple.php
+ *
+ * @return void
*/
- public function testReindexAll()
+ public function testReindexAll(): void
{
- $this->assertTrue($this->_state->isFlatEnabled());
- $this->_processor->reindexAll();
+ $this->assertTrue($this->state->isFlatEnabled());
+ $this->processor->reindexAll();
$categoryFactory = $this->objectManager->get(CategoryFactory::class);
$listProduct = $this->objectManager->get(ListProduct::class);
@@ -98,11 +118,13 @@ public function testReindexAll()
* @magentoDataFixture Magento/Catalog/_files/product_simple_multistore.php
* @magentoConfigFixture current_store catalog/frontend/flat_catalog_product 1
* @magentoConfigFixture fixturestore_store catalog/frontend/flat_catalog_product 1
+ *
+ * @return void
*/
- public function testReindexAllMultipleStores()
+ public function testReindexAllMultipleStores(): void
{
- $this->assertTrue($this->_state->isFlatEnabled());
- $this->_processor->reindexAll();
+ $this->assertTrue($this->state->isFlatEnabled());
+ $this->processor->reindexAll();
/** @var ProductCollectionFactory $productCollectionFactory */
$productCollectionFactory = $this->objectManager->create(ProductCollectionFactory::class);
@@ -116,24 +138,76 @@ public function testReindexAllMultipleStores()
$store->getId() => 'StoreTitle',
];
- foreach ($expectedData as $storeId => $productName) {
- $storeManager->setCurrentStore($storeId);
- $productCollection = $productCollectionFactory->create();
-
- $this->assertTrue(
- $productCollection->isEnabledFlat(),
- 'Flat should be enabled for product collection.'
- );
+ try {
+ foreach ($expectedData as $storeId => $productName) {
+ $storeManager->setCurrentStore($storeId);
+ $productCollection = $productCollectionFactory->create();
+
+ $this->assertTrue(
+ $productCollection->isEnabledFlat(),
+ 'Flat should be enabled for product collection.'
+ );
+
+ $productCollection->addIdFilter(1)->addAttributeToSelect(ProductInterface::NAME);
+
+ $this->assertEquals(
+ $productName,
+ $productCollection->getFirstItem()->getName(),
+ 'Wrong product name specified per store.'
+ );
+ }
+ } finally {
+ $storeManager->setCurrentStore($currentStore);
+ }
+ }
- $productCollection->addIdFilter(1)->addAttributeToSelect(ProductInterface::NAME);
+ /**
+ * @magentoDbIsolation disabled
+ *
+ * @magentoConfigFixture current_store catalog/frontend/flat_catalog_product 1
+ * @magentoDataFixture Magento/Catalog/_files/dropdown_attribute.php
+ * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php
+ *
+ * @return void
+ */
+ public function testCheckDropdownAttributeInFlat(): void
+ {
+ $attributeCode = 'dropdown_attribute';
+ $options = $this->optionManagement->getItems($this->flatResource->getTypeId(), $attributeCode);
+ $attributeValue = $options[1]->getValue();
+ $this->updateProduct('simple2', $attributeCode, $attributeValue);
+ $this->action->execute();
+ $this->assertFlatColumnValue($attributeCode, $attributeValue);
+ }
- $this->assertEquals(
- $productName,
- $productCollection->getFirstItem()->getName(),
- 'Wrong product name specified per store.'
- );
- }
+ /**
+ * Assert if column exist and column value in flat table
+ *
+ * @param string $attributeCode
+ * @param string $value
+ * @return void
+ */
+ private function assertFlatColumnValue(string $attributeCode, string $value): void
+ {
+ $connect = $this->flatResource->getConnection();
+ $tableName = $this->flatResource->getFlatTableName();
+ $this->assertTrue($connect->tableColumnExists($tableName, $attributeCode));
+ $select = $connect->select()->from($tableName, $attributeCode);
+ $this->assertEquals($value, $connect->fetchOne($select));
+ }
- $storeManager->setCurrentStore($currentStore);
+ /**
+ * Update product
+ *
+ * @param string $sku
+ * @param string $attributeCode
+ * @param string $value
+ * @return void
+ */
+ private function updateProduct(string $sku, string $attributeCode, string $value): void
+ {
+ $product = $this->productRepository->get($sku);
+ $product->setData($attributeCode, $value);
+ $this->productRepository->save($product);
}
}
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Flat/ProcessorTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Flat/ProcessorTest.php
index 5c376517ed143..5b9266dc11371 100644
--- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Flat/ProcessorTest.php
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Flat/ProcessorTest.php
@@ -3,33 +3,58 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Catalog\Model\Indexer\Product\Flat;
use Magento\Catalog\Model\Product\Attribute\Repository;
+use Magento\Framework\Indexer\StateInterface;
+use Magento\Framework\Indexer\StateInterfaceFactory;
+use Magento\Indexer\Model\ResourceModel\Indexer\State as StateResource;
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\Indexer\TestCase;
+use Magento\TestFramework\ObjectManager;
/**
* Integration tests for \Magento\Catalog\Model\Indexer\Product\Flat\Processor.
*/
-class ProcessorTest extends \Magento\TestFramework\Indexer\TestCase
+class ProcessorTest extends TestCase
{
/**
- * @var \Magento\Catalog\Model\Indexer\Product\Flat\State
+ * @var ObjectManager
*/
- protected $_state;
+ private $objectManager;
/**
- * @var \Magento\Catalog\Model\Indexer\Product\Flat\Processor
+ * @var State
*/
- protected $_processor;
+ private $state;
+ /**
+ * @var Processor
+ */
+ private $processor;
+
+ /**
+ * @var StateResource
+ */
+ private $stateResource;
+
+ /**
+ * @var StateInterfaceFactory;
+ */
+ private $stateFactory;
+
+ /**
+ * @inheritdoc
+ */
protected function setUp(): void
{
- $this->_state = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(
- \Magento\Catalog\Model\Indexer\Product\Flat\State::class
- );
- $this->_processor = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(
- \Magento\Catalog\Model\Indexer\Product\Flat\Processor::class
- );
+ $this->objectManager = Bootstrap::getObjectManager();
+ $this->state = $this->objectManager->get(State::class);
+ $this->processor = $this->objectManager->get(Processor::class);
+ $this->stateResource = $this->objectManager->get(StateResource::class);
+ $this->stateFactory = $this->objectManager->get(StateInterfaceFactory::class);
}
/**
@@ -37,11 +62,13 @@ protected function setUp(): void
* @magentoAppIsolation enabled
* @magentoAppArea adminhtml
* @magentoConfigFixture current_store catalog/frontend/flat_catalog_product 1
+ *
+ * @return void
*/
- public function testEnableProductFlat()
+ public function testEnableProductFlat(): void
{
- $this->assertTrue($this->_state->isFlatEnabled());
- $this->assertTrue($this->_processor->getIndexer()->isInvalid());
+ $this->assertTrue($this->state->isFlatEnabled());
+ $this->assertTrue($this->processor->getIndexer()->isInvalid());
}
/**
@@ -50,8 +77,10 @@ public function testEnableProductFlat()
* @magentoAppArea adminhtml
* @magentoDataFixture Magento/Catalog/_files/multiple_products.php
* @magentoConfigFixture current_store catalog/frontend/flat_catalog_product 1
+ *
+ * @return void
*/
- public function testSaveAttribute()
+ public function testSaveAttribute(): void
{
/** @var $product \Magento\Catalog\Model\Product */
$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
@@ -61,8 +90,7 @@ public function testSaveAttribute()
/** @var \Magento\Catalog\Model\ResourceModel\Product $productResource */
$productResource = $product->getResource();
$productResource->getAttribute('sku')->setData('used_for_sort_by', 1)->save();
-
- $this->assertTrue($this->_processor->getIndexer()->isInvalid());
+ $this->assertTrue($this->processor->getIndexer()->isInvalid());
}
/**
@@ -71,8 +99,10 @@ public function testSaveAttribute()
* @magentoAppArea adminhtml
* @magentoDataFixture Magento/Catalog/_files/product_simple_with_custom_attribute_in_flat.php
* @magentoConfigFixture current_store catalog/frontend/flat_catalog_product 1
+ *
+ * @return void
*/
- public function testDeleteAttribute()
+ public function testDeleteAttribute(): void
{
/** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $model */
$model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
@@ -83,8 +113,7 @@ public function testDeleteAttribute()
$productAttrubute = $productAttributeRepository->get('flat_attribute');
$productAttributeId = $productAttrubute->getAttributeId();
$model->load($productAttributeId)->delete();
-
- $this->assertTrue($this->_processor->getIndexer()->isInvalid());
+ $this->assertTrue($this->processor->getIndexer()->isInvalid());
}
/**
@@ -93,10 +122,12 @@ public function testDeleteAttribute()
* @magentoAppArea adminhtml
* @magentoDataFixture Magento/Store/_files/core_fixturestore.php
* @magentoConfigFixture current_store catalog/frontend/flat_catalog_product 1
+ *
+ * @return void
*/
- public function testAddNewStore()
+ public function testAddNewStore(): void
{
- $this->assertTrue($this->_processor->getIndexer()->isInvalid());
+ $this->assertTrue($this->processor->getIndexer()->isInvalid());
}
/**
@@ -104,8 +135,10 @@ public function testAddNewStore()
* @magentoAppIsolation enabled
* @magentoAppArea adminhtml
* @magentoConfigFixture current_store catalog/frontend/flat_catalog_product 1
+ *
+ * @return void
*/
- public function testAddNewStoreGroup()
+ public function testAddNewStoreGroup(): void
{
/** @var \Magento\Store\Model\Group $storeGroup */
$storeGroup = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
@@ -115,6 +148,45 @@ public function testAddNewStoreGroup()
['website_id' => 1, 'name' => 'New Store Group', 'root_category_id' => 2, 'group_id' => null]
);
$storeGroup->save();
- $this->assertTrue($this->_processor->getIndexer()->isInvalid());
+ $this->assertTrue($this->processor->getIndexer()->isInvalid());
+ }
+
+ /**
+ * @magentoDbIsolation disabled
+ * @magentoConfigFixture current_store catalog/frontend/flat_catalog_product 0
+ *
+ * @return void
+ */
+ public function testReindexAllWithProductFlatDisabled(): void
+ {
+ $this->updateIndexerStatus();
+ $this->processor->reindexAll();
+ $state = $this->getIndexerState();
+ $this->assertEquals(StateInterface::STATUS_INVALID, $state->getStatus());
+ }
+
+ /**
+ * Update status for indexer
+ *
+ * @param string $status
+ * @return void
+ */
+ private function updateIndexerStatus(string $status = StateInterface::STATUS_INVALID): void
+ {
+ $state = $this->getIndexerState();
+ $state->setStatus($status);
+ $this->stateResource->save($state);
+ }
+
+ /**
+ * Get Indexer state
+ *
+ * @return StateInterface
+ */
+ private function getIndexerState(): StateInterface
+ {
+ $state = $this->stateFactory->create();
+
+ return $state->loadByIndexer(State::INDEXER_ID);
}
}
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AlertsTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AlertsTest.php
index 96ddc66c875b7..8516916ba8cb3 100644
--- a/dev/tests/integration/testsuite/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AlertsTest.php
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AlertsTest.php
@@ -40,6 +40,7 @@ protected function setUp(): void
/**
* @magentoConfigFixture current_store catalog/productalert/allow_stock 1
+ * @magentoConfigFixture current_store catalog/productalert/allow_price 1
*
* @return void
*/
@@ -47,10 +48,15 @@ public function testModifyMeta(): void
{
$meta = $this->stockAlertsModifier->modifyMeta([]);
$this->assertArrayHasKey('alerts', $meta);
- $content = $meta['alerts']['children'][Alerts::DATA_SCOPE_STOCK]['arguments']['data']['config']['content'];
+ $stockContent = $meta['alerts']['children'][Alerts::DATA_SCOPE_STOCK]['arguments']['data']['config']['content'];
$this->assertEquals(
1,
- Xpath::getElementsCountForXpath("//div[@data-grid-id='alertStock']", $content)
+ Xpath::getElementsCountForXpath("//div[@data-grid-id='alertStock']", $stockContent)
+ );
+ $priceContent = $meta['alerts']['children'][Alerts::DATA_SCOPE_PRICE]['arguments']['data']['config']['content'];
+ $this->assertEquals(
+ 1,
+ Xpath::getElementsCountForXpath("//div[@data-grid-id='alertPrice']", $priceContent)
);
}
}
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/detach_product_website_quene_data.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/detach_product_website_quene_data.php
new file mode 100644
index 0000000000000..e862e95cbd3e4
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/detach_product_website_quene_data.php
@@ -0,0 +1,62 @@
+requireDataFixture('Magento/Catalog/_files/product_with_two_websites.php');
+
+$objectManager = Bootstrap::getObjectManager();
+/** @var ProductRepositoryInterface $productRepository */
+$productRepository = $objectManager->get(ProductRepositoryInterface::class);
+$productRepository->cleanCache();
+/** @var WebsiteRepositoryInterface $websiteRepository */
+$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class);
+/** @var IdentityGeneratorInterface $identityService */
+$identityService = $objectManager->get(IdentityGeneratorInterface::class);
+/** @var SerializerInterface $jsonEncoder */
+$jsonEncoder = $objectManager->get(SerializerInterface::class);
+/** @var OperationInterfaceFactory $optionFactory */
+$optionFactory = $objectManager->get(OperationInterfaceFactory::class);
+/** @var BulkManagementInterface $bulkManagement */
+$bulkManagement = $objectManager->get(BulkManagementInterface::class);
+$productIds = [(int)$productRepository->get('unique-simple-azaza')->getId()];
+$websiteId = (int)$websiteRepository->get('second_website')->getId();
+$bulkDescription = __('Update attributes for ' . 1 . ' selected products');
+$dataToEncode = [
+ 'meta_information' => 'Detach website',
+ 'product_ids' => $productIds,
+ 'store_id' => 0,
+ 'website_id' => $websiteId,
+ 'attributes' => [
+ 'website_assign' => [],
+ 'website_detach' => [$websiteId],
+ ],
+];
+$bulkUid = $identityService->generateId();
+$data = [
+ 'data' => [
+ 'bulk_uuid' => $bulkUid,
+ 'topic_name' => 'product_action_attribute.website.update',
+ 'serialized_data' => $jsonEncoder->serialize($dataToEncode),
+ 'status' => OperationInterface::STATUS_TYPE_OPEN,
+ ],
+];
+
+$bulkManagement->scheduleBulk(
+ $bulkUid,
+ [$optionFactory->create($data)],
+ $bulkDescription,
+ 1
+);
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/detach_product_website_quene_data_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/detach_product_website_quene_data_rollback.php
new file mode 100644
index 0000000000000..df62ecc22c623
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/detach_product_website_quene_data_rollback.php
@@ -0,0 +1,17 @@
+get(DeleteTopicRelatedMessages::class);
+$deleteTopicRelatedMessages->execute('product_action_attribute.website.update');
+
+Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/product_with_two_websites_rollback.php');
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_related_disabled_in_store.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_related_disabled_in_store.php
new file mode 100644
index 0000000000000..b8e6d4702b44f
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_related_disabled_in_store.php
@@ -0,0 +1,70 @@
+get(WebsiteRepositoryInterface::class);
+$baseWebsite = $websiteRepository->get('base');
+/** @var StoreManagerInterface $storeManager */
+$storeManager = $objectManager->get(StoreManagerInterface::class);
+$defaultAttributeSet = $objectManager->get(Config::class)->getEntityType(Product::ENTITY)->getDefaultAttributeSetId();
+$productRepository = $objectManager->get(ProductRepositoryInterface::class);
+/** @var ProductInterfaceFactory $productInterfaceFactory */
+$productInterfaceFactory = $objectManager->get(ProductInterfaceFactory::class);
+
+/** @var Product $product */
+$product = $productInterfaceFactory->create();
+$product->setTypeId(Type::TYPE_SIMPLE)
+ ->setAttributeSetId($defaultAttributeSet)
+ ->setStoreId($storeManager->getDefaultStoreView()->getId())
+ ->setWebsiteIds([$baseWebsite->getId()])
+ ->setName('Simple Product')
+ ->setSku('simple')
+ ->setPrice(10)
+ ->setWeight(18)
+ ->setStockData(['use_config_manage_stock' => 0])
+ ->setVisibility(Visibility::VISIBILITY_BOTH)
+ ->setStatus(Status::STATUS_ENABLED);
+
+$simple = $productRepository->save($product);
+$simple->setStoreId($storeManager->getDefaultStoreView()->getId())
+->setStatus(Status::STATUS_DISABLED);
+$productRepository->save($simple);
+/** @var ProductLinkInterface $productLink */
+$productLink = $objectManager->create(ProductLinkInterface::class);
+$productLink->setSku('simple_with_related');
+$productLink->setLinkedProductSku('simple');
+$productLink->setPosition(1);
+$productLink->setLinkType('related');
+
+/** @var Product $product */
+$product = $productInterfaceFactory->create();
+$product->setTypeId(Type::TYPE_SIMPLE)
+ ->setAttributeSetId($defaultAttributeSet)
+ ->setStoreId($storeManager->getDefaultStoreView()->getId())
+ ->setWebsiteIds([$baseWebsite->getId()])
+ ->setName('Simple Product With Related Product')
+ ->setSku('simple_with_related')
+ ->setPrice(10)
+ ->setWeight(18)
+ ->setProductLinks([$productLink])
+ ->setStockData(['use_config_manage_stock' => 0])
+ ->setVisibility(Visibility::VISIBILITY_BOTH)
+ ->setStatus(Status::STATUS_ENABLED);
+
+$productRepository->save($product);
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_related_disabled_in_store_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_related_disabled_in_store_rollback.php
new file mode 100644
index 0000000000000..6ca27b71b9057
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_related_disabled_in_store_rollback.php
@@ -0,0 +1,33 @@
+get(\Magento\Framework\Registry::class);
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+
+/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */
+$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+
+try {
+ $firstProduct = $productRepository->get('simple', false, null, true);
+ $productRepository->delete($firstProduct);
+} catch (\Magento\Framework\Exception\NoSuchEntityException $exception) {
+ //Product already removed
+}
+
+try {
+ $secondProduct = $productRepository->get('simple_with_related', false, null, true);
+ $productRepository->delete($secondProduct);
+} catch (\Magento\Framework\Exception\NoSuchEntityException $exception) {
+ //Product already removed
+}
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', false);
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/second_root_category.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/second_root_category.php
new file mode 100644
index 0000000000000..7a38cd6eeb52c
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/second_root_category.php
@@ -0,0 +1,22 @@
+get(CategoryFactory::class);
+/** @var CategoryRepositoryInterface $categoryRepository */
+$categoryRepository = $objectManager->get(CategoryRepositoryInterface::class);
+$rootCategory = $categoryFactory->create();
+$rootCategory->setName('Second Root Category')
+ ->setParentId(Category::TREE_ROOT_ID)
+ ->setIsActive(true);
+$categoryRepository->save($rootCategory);
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/second_root_category_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/second_root_category_rollback.php
new file mode 100644
index 0000000000000..142ebf5412c10
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/second_root_category_rollback.php
@@ -0,0 +1,34 @@
+get(CategoryRepositoryInterface::class);
+/** @var CollectionFactory $categoryCollectionFactory */
+$categoryCollectionFactory = $objectManager->get(CollectionFactory::class);
+
+/** @var Registry $registry */
+$registry = $objectManager->get(Registry::class);
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+$categoryCollection = $categoryCollectionFactory->create();
+$category = $categoryCollection
+ ->addAttributeToFilter(CategoryInterface::KEY_NAME, 'Second Root Category')
+ ->setPageSize(1)
+ ->getFirstItem();
+if ($category->getId()) {
+ $categoryRepository->delete($category);
+}
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', false);
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/update_product_website_quene_data.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/update_product_website_quene_data.php
new file mode 100644
index 0000000000000..6feffc2a5fb3a
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/update_product_website_quene_data.php
@@ -0,0 +1,63 @@
+requireDataFixture('Magento/Store/_files/second_website_with_two_stores.php');
+Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/second_product_simple.php');
+
+$objectManager = Bootstrap::getObjectManager();
+/** @var ProductRepositoryInterface $productRepository */
+$productRepository = $objectManager->get(ProductRepositoryInterface::class);
+$productRepository->cleanCache();
+/** @var WebsiteRepositoryInterface $websiteRepository */
+$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class);
+/** @var IdentityGeneratorInterface $identityService */
+$identityService = $objectManager->get(IdentityGeneratorInterface::class);
+/** @var SerializerInterface $jsonEncoder */
+$jsonEncoder = $objectManager->get(SerializerInterface::class);
+/** @var OperationInterfaceFactory $optionFactory */
+$optionFactory = $objectManager->get(OperationInterfaceFactory::class);
+/** @var BulkManagementInterface $bulkManagement */
+$bulkManagement = $objectManager->get(BulkManagementInterface::class);
+$productIds = [(int)$productRepository->get('simple2')->getId()];
+$websiteId = (int)$websiteRepository->get('test')->getId();
+$bulkDescription = __('Update attributes for ' . 1 . ' selected products');
+$dataToEncode = [
+ 'meta_information' => 'Update website assign',
+ 'product_ids' => $productIds,
+ 'store_id' => 0,
+ 'website_id' => $websiteId,
+ 'attributes' => [
+ 'website_assign' => [$websiteId],
+ 'website_detach' => [],
+ ],
+];
+$bulkUid = $identityService->generateId();
+$data = [
+ 'data' => [
+ 'bulk_uuid' => $bulkUid,
+ 'topic_name' => 'product_action_attribute.website.update',
+ 'serialized_data' => $jsonEncoder->serialize($dataToEncode),
+ 'status' => OperationInterface::STATUS_TYPE_OPEN,
+ ],
+];
+
+$bulkManagement->scheduleBulk(
+ $bulkUid,
+ [$optionFactory->create($data)],
+ $bulkDescription,
+ 1
+);
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/update_product_website_quene_data_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/update_product_website_quene_data_rollback.php
new file mode 100644
index 0000000000000..85336635f2e08
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/update_product_website_quene_data_rollback.php
@@ -0,0 +1,18 @@
+get(DeleteTopicRelatedMessages::class);
+$deleteTopicRelatedMessages->execute('product_action_attribute.website.update');
+
+Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/second_product_simple_rollback.php');
+Resolver::getInstance()->requireDataFixture('Magento/Store/_files/second_website_with_two_stores_rollback.php');
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php
index 53e32483ee3d6..ceb07e3445c0e 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php
@@ -1721,15 +1721,23 @@ public function testValidateUrlKeysMultipleStores()
}
/**
+ * Test import product with product links and empty value
+ *
+ * @param string $pathToFile
+ * @param bool $expectedResultCrossell
+ * @param bool $expectedResultUpsell
+ *
* @magentoDataFixture Magento/CatalogImportExport/_files/product_export_with_product_links_data.php
* @magentoAppArea adminhtml
* @magentoDbIsolation enabled
* @magentoAppIsolation enabled
+ * @dataProvider getEmptyLinkedData
*/
- public function testProductLinksWithEmptyValue()
- {
- // import data from CSV file
- $pathToFile = __DIR__ . '/_files/products_to_import_with_product_links_with_empty_value.csv';
+ public function testProductLinksWithEmptyValue(
+ string $pathToFile,
+ bool $expectedResultCrossell,
+ bool $expectedResultUpsell
+ ): void {
$filesystem = BootstrapHelper::getObjectManager()->create(Filesystem::class);
$directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
@@ -1759,8 +1767,29 @@ public function testProductLinksWithEmptyValue()
$product = BootstrapHelper::getObjectManager()->create(Product::class);
$product->load($productId);
- $this->assertEmpty($product->getCrossSellProducts());
- $this->assertEmpty($product->getUpSellProducts());
+ $this->assertEquals(empty($product->getCrossSellProducts()), $expectedResultCrossell);
+ $this->assertEquals(empty($product->getUpSellProducts()), $expectedResultUpsell);
+ }
+
+ /**
+ * Get data for empty linked product
+ *
+ * @return array[]
+ */
+ public function getEmptyLinkedData(): array
+ {
+ return [
+ [
+ __DIR__ . '/_files/products_to_import_with_product_links_with_empty_value.csv',
+ true,
+ true,
+ ],
+ [
+ __DIR__ . '/_files/products_to_import_with_product_links_with_empty_data.csv',
+ false,
+ true,
+ ],
+ ];
}
/**
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_product_links_with_empty_data.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_product_links_with_empty_data.csv
new file mode 100644
index 0000000000000..d8812defa0828
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_product_links_with_empty_data.csv
@@ -0,0 +1,2 @@
+sku,crosssell_skus,crosssell_position,upsell_skus,upsell_position
+simple,,,__EMPTY__VALUE__,__EMPTY__VALUE__
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_product_links_data.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_product_links_data.php
index 5dde578a1341f..8ee35d747ea1a 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_product_links_data.php
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_product_links_data.php
@@ -18,7 +18,26 @@
$objectManager = Bootstrap::getObjectManager();
/** @var ProductRepositoryInterface $productRepository */
$productRepository = $objectManager->create(ProductRepositoryInterface::class);
-$product = $productRepository->get('simple_ms_1');
+/** @var \Magento\Catalog\Api\Data\ProductLinkInterface $productLink */
+$productCrosssellLink = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+ ->create(\Magento\Catalog\Api\Data\ProductLinkInterface::class);
+$productCrosssellLink->setSku('simple');
+$productCrosssellLink->setLinkedProductSku('simple_ms_1');
+$productCrosssellLink->setPosition(2);
+$productCrosssellLink->setLinkType('crosssell');
+$productUpsellLink = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+ ->create(\Magento\Catalog\Api\Data\ProductLinkInterface::class);
+$productUpsellLink->setSku('simple');
+$productUpsellLink->setLinkedProductSku('simple_ms_1');
+$productUpsellLink->setPosition(1);
+$productUpsellLink->setLinkType('upsell');
+$productRelatedLink = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+ ->create(\Magento\Catalog\Api\Data\ProductLinkInterface::class);
+$productRelatedLink->setSku('simple');
+$productRelatedLink->setLinkedProductSku('simple_ms_1');
+$productRelatedLink->setPosition(3);
+$productRelatedLink->setLinkType('related');
+
$productModel = $objectManager->create(
\Magento\Catalog\Model\Product::class
);
@@ -51,10 +70,6 @@
true
)->setCategoryIds(
[333]
-)->setUpSellLinkData(
- [$product->getId() => ['position' => 1]]
-)->setCrossSellLinkData(
- [$product->getId() => ['position' => 2]]
-)->setRelatedLinkData(
- [$product->getId() => ['position' => 3]]
+)->setProductLinks(
+ [$productCrosssellLink, $productUpsellLink, $productRelatedLink]
)->save();
diff --git a/dev/tests/integration/testsuite/Magento/ProductAlert/_files/price_alert_on_second_website.php b/dev/tests/integration/testsuite/Magento/ProductAlert/_files/price_alert_on_second_website.php
new file mode 100644
index 0000000000000..7bbcbd37bc0e7
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/ProductAlert/_files/price_alert_on_second_website.php
@@ -0,0 +1,66 @@
+requireDataFixture('Magento/Customer/_files/customer_for_second_website_with_address.php');
+
+$objectManager = Bootstrap::getObjectManager();
+/** @var StoreManagerInterface $storeManager */
+$storeManager = $objectManager->get(StoreManagerInterface::class);
+$secondWebsite = $storeManager->getWebsite('test');
+/** @var ProductInterfaceFactory $productFactory */
+$productFactory = $objectManager->get(ProductInterfaceFactory::class);
+/** @var ProductRepositoryInterface $peoductRepository */
+$productRepository = $objectManager->get(ProductRepositoryInterface::class);
+$productRepository->cleanCache();
+/** @var PriceFactory $priceFactory */
+$priceFactory = $objectManager->get(PriceFactory::class);
+/** @var PriceResource $priceResource */
+$priceResource = $objectManager->get(PriceResource::class);
+/** @var CustomerRepositoryInterface $customerRepository */
+$customerRepository = $objectManager->get(CustomerRepositoryInterface::class);
+$customer = $customerRepository->get('customer_second_ws_with_addr@example.com', (int)$secondWebsite->getId());
+
+
+$product = $productFactory->create();
+$product
+ ->setTypeId('simple')
+ ->setAttributeSetId($product->getDefaultAttributeSetId())
+ ->setWebsiteIds([(int)$secondWebsite->getId()])
+ ->setName('Simple Product2')
+ ->setSku('simple_on_second_website_for_price_alert')
+ ->setPrice(10)
+ ->setMetaTitle('meta title2')
+ ->setMetaKeyword('meta keyword2')
+ ->setMetaDescription('meta description2')
+ ->setVisibility(Visibility::VISIBILITY_BOTH)
+ ->setStatus(Status::STATUS_ENABLED)
+ ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]);
+
+$productRepository->save($product);
+
+$priceAlert = $priceFactory->create();
+$priceAlert->setCustomerId(
+ $customer->getId()
+)->setProductId(
+ (int)$productRepository->get($product->getSku())->getId()
+)->setWebsiteId(
+ (int)$secondWebsite->getId()
+)->setStoreId(
+ (int)$storeManager->getStore('fixture_third_store')->getId()
+);
+$priceResource->save($priceAlert);
diff --git a/dev/tests/integration/testsuite/Magento/ProductAlert/_files/price_alert_on_second_website_rollback.php b/dev/tests/integration/testsuite/Magento/ProductAlert/_files/price_alert_on_second_website_rollback.php
new file mode 100644
index 0000000000000..fce542dca24a1
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/ProductAlert/_files/price_alert_on_second_website_rollback.php
@@ -0,0 +1,53 @@
+get(ProductRepositoryInterface::class);
+$productRepository->cleanCache();
+/** @var PriceFactory $priceFactory */
+$priceFactory = $objectManager->get(PriceFactory::class);
+/** @var PriceResource $stockResource */
+$stockResource = $objectManager->get(PriceResource::class);
+/** @var StoreManagerInterface $storeManager */
+$storeManager = $objectManager->get(StoreManagerInterface::class);
+$secondWebsite = $storeManager->getWebsite('test');
+/** @var CustomerRepositoryInterface $customerRepository */
+$customerRepository = $objectManager->get(CustomerRepositoryInterface::class);
+$customer = $customerRepository->get('customer_second_ws_with_addr@example.com', (int)$secondWebsite->getId());
+/** @var Registry $registry */
+$registry = $objectManager->get(Registry::class);
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+
+try {
+ $productRepository->deleteById('simple_on_second_website_for_price_alert');
+} catch (NoSuchEntityException $e) {
+ //already removed
+}
+
+
+$priceAlert = $priceFactory->create();
+$priceAlert->deleteCustomer((int)$customer->getId(), (int)$secondWebsite->getId());
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', false);
+
+Resolver::getInstance()
+ ->requireDataFixture('Magento/Customer/_files/customer_for_second_website_with_address_rollback.php');
diff --git a/dev/tests/integration/testsuite/Magento/ProductAlert/_files/product_alert_rollback.php b/dev/tests/integration/testsuite/Magento/ProductAlert/_files/product_alert_rollback.php
index e9c4900ded341..3db4933f8a12c 100644
--- a/dev/tests/integration/testsuite/Magento/ProductAlert/_files/product_alert_rollback.php
+++ b/dev/tests/integration/testsuite/Magento/ProductAlert/_files/product_alert_rollback.php
@@ -7,6 +7,7 @@
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Framework\Registry;
+use Magento\ProductAlert\Model\PriceFactory;
use Magento\ProductAlert\Model\StockFactory;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\Workaround\Override\Fixture\Resolver;
@@ -14,6 +15,8 @@
$objectManager = Bootstrap::getObjectManager();
/** @var StockFactory $stockFactory */
$stockFactory = $objectManager->get(StockFactory::class);
+/** @var PriceFactory $priceFactory */
+$priceFactory = $objectManager->get(PriceFactory::Class);
/** @var CustomerRepositoryInterface $customerRepository */
$customerRepository = $objectManager->get(CustomerRepositoryInterface::class);
$customer = $customerRepository->get('customer@example.com');
@@ -26,6 +29,9 @@
$stockAlert = $stockFactory->create();
$stockAlert->deleteCustomer((int)$customer->getId());
+$priceAlert = $priceFactory->create();
+$priceAlert->deleteCustomer(($customer->getId()));
+
$registry->unregister('isSecureArea');
$registry->register('isSecureArea', false);
diff --git a/dev/tests/integration/testsuite/Magento/ProductAlert/_files/simple_product_with_two_alerts.php b/dev/tests/integration/testsuite/Magento/ProductAlert/_files/simple_product_with_two_alerts.php
new file mode 100644
index 0000000000000..70f9e2fa97f7f
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/ProductAlert/_files/simple_product_with_two_alerts.php
@@ -0,0 +1,59 @@
+requireDataFixture('Magento/Customer/_files/customer.php');
+Resolver::getInstance()->requireDataFixture('Magento/ProductAlert/_files/product_alert.php');
+
+$objectManager = Bootstrap::getObjectManager();
+/** @var ProductRepositoryInterface $productRepository */
+$productRepository = $objectManager->create(ProductRepositoryInterface::class);
+$productRepository->cleanCache();
+$product = $productRepository->get('simple');
+/** @var WebsiteRepositoryInterface $websiteRepository */
+$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class);
+$baseWebsiteId = (int)$websiteRepository->get('base')->getId();
+/** @var CustomerRegistry $customerRegistry */
+$customerRegistry = $objectManager->create(CustomerRegistry::class);
+$customer = $customerRegistry->retrieve(1);
+/** @var PriceFactory $priceFactory */
+$priceFactory = $objectManager->get(PriceFactory::class);
+/** @var PriceResource $priceResource */
+$priceResource = $objectManager->get(PriceResource::class);
+$priceAlert = $priceFactory->create();
+$priceAlert->setCustomerId(
+ $customer->getId()
+)->setProductId(
+ $product->getId()
+)->setPrice(
+ $product->getPrice()+1
+)->setWebsiteId(
+ $baseWebsiteId
+);
+$priceResource->save($priceAlert);
+/** @var StockFactory $stockFactory */
+$stockFactory = $objectManager->get(StockFactory::class);
+/** @var StockResource $stockResource */
+$stockResource = $objectManager->get(StockResource::class);
+$stockAlert = $stockFactory->create();
+$stockAlert->setCustomerId(
+ $customer->getId()
+)->setProductId(
+ $product->getId()
+)->setWebsiteId(
+ $baseWebsiteId
+);
+$stockResource->save($stockAlert);
diff --git a/dev/tests/integration/testsuite/Magento/ProductAlert/_files/simple_product_with_two_alerts_rollback.php b/dev/tests/integration/testsuite/Magento/ProductAlert/_files/simple_product_with_two_alerts_rollback.php
new file mode 100644
index 0000000000000..93614e974931d
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/ProductAlert/_files/simple_product_with_two_alerts_rollback.php
@@ -0,0 +1,39 @@
+get(StockFactory::class);
+/** @var PriceFactory $priceFactory */
+$priceFactory = $objectManager->get(PriceFactory::Class);
+/** @var CustomerRepositoryInterface $customerRepository */
+$customerRepository = $objectManager->get(CustomerRepositoryInterface::class);
+$customer = $customerRepository->get('customer@example.com');
+/** @var Registry $registry */
+$registry = $objectManager->get(Registry::class);
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+
+$stockAlert = $stockFactory->create();
+$stockAlert->deleteCustomer((int)$customer->getId());
+
+$priceAlert = $priceFactory->create();
+$priceAlert->deleteCustomer(($customer->getId()));
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', false);
+
+Resolver::getInstance()->requireDataFixture('Magento/Customer/_files/customer_rollback.php');
+Resolver::getInstance()->requireDataFixture('Magento/ProductAlert/_files/product_alert_rollback.php');
diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Creditmemo/Grid/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Creditmemo/Grid/CollectionTest.php
deleted file mode 100644
index 4a96a381309db..0000000000000
--- a/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Creditmemo/Grid/CollectionTest.php
+++ /dev/null
@@ -1,46 +0,0 @@
-objectManager = Bootstrap::getObjectManager();
- }
-
- /**
- * @magentoDataFixture Magento/Sales/_files/creditmemo_list.php
- *
- * @return void
- */
- public function testCollectionOrderCurrencyCodeExist(): void
- {
- /** @var $collection Collection */
- $collection = $this->objectManager->get(Collection::class);
- $collection->addFieldToFilter('increment_id', ['eq' => '456']);
- foreach ($collection as $item) {
- $this->assertNotNull($item->getOrderCurrencyCode());
- }
- }
-}
diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Creditmemo/Order/Grid/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Creditmemo/Order/Grid/CollectionTest.php
deleted file mode 100644
index 0b7908a122293..0000000000000
--- a/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Creditmemo/Order/Grid/CollectionTest.php
+++ /dev/null
@@ -1,46 +0,0 @@
-objectManager = Bootstrap::getObjectManager();
- }
-
- /**
- * @magentoDataFixture Magento/Sales/_files/creditmemo_list.php
- *
- * @return void
- */
- public function testCollectionOrderCurrencyCodeExist(): void
- {
- /** @var $collection Collection */
- $collection = $this->objectManager->get(Collection::class);
- $collection->addFieldToFilter('increment_id', ['eq' => '456']);
- foreach ($collection as $item) {
- $this->assertNotNull($item->getOrderCurrencyCode());
- }
- }
-}
diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Grid/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Grid/CollectionTest.php
index d2d71ab880026..abf845aca0d88 100644
--- a/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Grid/CollectionTest.php
+++ b/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Grid/CollectionTest.php
@@ -7,10 +7,26 @@
namespace Magento\Sales\Model\ResourceModel\Order\Grid;
+use Magento\Framework\ObjectManagerInterface;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use Magento\TestFramework\Helper\Bootstrap;
+use PHPUnit\Framework\TestCase;
-class CollectionTest extends \PHPUnit\Framework\TestCase
+class CollectionTest extends TestCase
{
+ /**
+ * @var ObjectManagerInterface
+ */
+ private $objectManager;
+
+ /**
+ * @inheritDoc
+ */
+ protected function setUp(): void
+ {
+ $this->objectManager = Bootstrap::getObjectManager();
+ }
+
/**
* Tests collection properties.
*
@@ -19,10 +35,8 @@ class CollectionTest extends \PHPUnit\Framework\TestCase
*/
public function testCollectionCreate(): void
{
- $objectManager = Bootstrap::getObjectManager();
-
/** @var Collection $gridCollection */
- $gridCollection = $objectManager->get(Collection::class);
+ $gridCollection = $this->objectManager->get(Collection::class);
$tableDescription = $gridCollection->getConnection()
->describeTable($gridCollection->getMainTable());
@@ -42,4 +56,25 @@ public function testCollectionCreate(): void
self::assertStringContainsString('main_table.', $mappedName);
}
}
+
+ /**
+ * Verifies that filter condition date is being converted to config timezone before select sql query
+ *
+ * @return void
+ */
+ public function testAddFieldToFilter(): void
+ {
+ $filterDate = "2021-01-19 00:00:00";
+ /** @var TimezoneInterface $timeZone */
+ $timeZone = $this->objectManager->get(TimezoneInterface::class);
+ /** @var Collection $gridCollection */
+ $gridCollection = $this->objectManager->get(Collection::class);
+ $convertedDate = $timeZone->convertConfigTimeToUtc($filterDate);
+
+ $collection = $gridCollection->addFieldToFilter('created_at', ['qteq' => $filterDate]);
+ $expectedSelect = "SELECT `main_table`.* FROM `sales_order_grid` AS `main_table` " .
+ "WHERE (((`main_table`.`created_at` = '{$convertedDate}')))";
+
+ $this->assertEquals($expectedSelect, $collection->getSelectSql(true));
+ }
}