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 @@ + + + + + + + + + + <description value="Imports a .csv file containing a configurable product with 3 child simple products that + have images. Verifies that products are imported successfully and that the images are attached to the + products as expected."/> + <severity value="MAJOR"/> + <group value="importExport"/> + <group value="remote_storage_aws_s3"/> + <skip> + <issueId value="MC-39280"/> + </skip> + </annotations> + + <before> + <!-- Enable AWS S3 Remote Storage --> + <magentoCLI command="setup:config:set {{RemoteStorageAwsS3ConfigData.enable_options}}" stepKey="enableRemoteStorage"/> + </before> + + <after> + <!-- Disable AWS S3 Remote Storage --> + <magentoCLI command="setup:config:set {{RemoteStorageAwsS3ConfigData.disable_options}}" stepKey="disableRemoteStorage"/> + </after> + </test> +</tests> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontNavigateToCategoryUrlActionGroup"> + <annotations> + <description>Goes to the Storefront Category page for the provided Category URL.</description> + </annotations> + <arguments> + <argument name="categoryUrl" type="string"/> + </arguments> + <amOnPage url="{{StorefrontCategoryPage.url(categoryUrl)}}" stepKey="goToStorefrontCategoryPage"/> + </actionGroup> +</actionGroups> 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 @@ </arguments> <selectOption selector="{{StorefrontProductInfoMainSection.productOptionSelect(attributeLabel)}}" userInput="{{optionLabel}}" stepKey="fillDropDownAttributeOption"/> + <waitForPageLoad stepKey="waitForPageLoad"/> </actionGroup> </actionGroups> 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 @@ <element name="imageFileUpload" type="input" selector="#fileupload"/> <element name="imageUploadButton" type="button" selector="div.image div.fileinput-button"/> <element name="imageFile" type="text" selector="//*[@id='media_gallery_content']//img[contains(@src, '{{url}}')]" parameterized="true"/> + <element name="imageElement" type="text" selector="#media_gallery_content img"/> <element name="removeImageButton" type="button" selector=".action-remove"/> <element name="removeImageButtonForExactImage" type="button" selector="[id='media_gallery_content'] img[src*='{{imageName}}'] + div[class='actions'] button[class='action-remove']" parameterized="true"/> <element name="modalOkBtn" type="button" selector="button.action-primary.action-accept"/> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="SaveAndContinueEditCatalogPriceRuleActionGroup"> + <annotations> + <description>Clicks on Save and Continue Edit. Validates that the Success Message is present and correct on the Admin Catalog Price Rule creation/edit page.</description> + </annotations> + + <waitForElementVisible selector="{{AdminNewCatalogPriceRule.saveAndContinue}}" stepKey="waitForSaveAndContinueEditButton"/> + <click selector="{{AdminNewCatalogPriceRule.saveAndContinue}}" stepKey="saveAndContinueEdit"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the rule." stepKey="assertSuccess"/> + </actionGroup> +</actionGroups> 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 @@ <virtualType name="systemConfigQueryLocker" type="Magento\Framework\Cache\LockGuardedCacheLoader"> <arguments> - <argument name="locker" xsi:type="object">Magento\Framework\Lock\Backend\Database</argument> + <argument name="locker" xsi:type="object">Magento\Framework\Lock\Proxy</argument> </arguments> </virtualType> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminClickCheckDataImportActionGroup"> + <annotations> + <description>Clicks the 'Check Data' button on the Admin Import page.</description> + </annotations> + <waitForElementVisible selector="{{AdminImportHeaderSection.checkDataButton}}" stepKey="waitForCheckDataButton"/> + <click selector="{{AdminImportHeaderSection.checkDataButton}}" stepKey="clickCheckDataButton"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminClickImportActionGroup"> + <annotations> + <description>Clicks the 'Import' button on the Admin Import page.</description> + </annotations> + <waitForElementVisible selector="{{AdminImportMainSection.importButton}}" stepKey="waitForImportButton"/> + <click selector="{{AdminImportMainSection.importButton}}" stepKey="clickImportButton"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <waitForElementVisible selector="{{AdminImportValidationMessagesSection.notice}}" stepKey="waitForNoticeMessage"/> + </actionGroup> +</actionGroups> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminFillImportFormActionGroup"> + <annotations> + <description>Fills the form on the System > Data Transfer > Import page.</description> + </annotations> + <arguments> + <argument name="entityType" defaultValue="Products" type="string"/> + <argument name="importBehavior" defaultValue="Add/Update" type="string"/> + <argument name="validationStrategy" defaultValue="Stop on Error" type="string" /> + <argument name="allowedErrorsCount" defaultValue="10" type="string" /> + <argument name="importFile" type="string"/> + </arguments> + <waitForElementVisible selector="{{AdminImportMainSection.entityType}}" stepKey="waitForEntityType"/> + <selectOption selector="{{AdminImportMainSection.entityType}}" userInput="{{entityType}}" stepKey="selectEntityType"/> + <waitForElementVisible selector="{{AdminImportMainSection.importBehavior}}" stepKey="waitForImportBehavior"/> + <selectOption selector="{{AdminImportMainSection.importBehavior}}" userInput="{{importBehavior}}" stepKey="selectImportBehaviorOption"/> + <selectOption selector="{{AdminImportMainSection.validationStrategy}}" userInput="{{validationStrategy}}" stepKey="selectValidationStrategyOption"/> + <fillField selector="{{AdminImportMainSection.allowedErrorsCount}}" userInput="{{allowedErrorsCount}}" stepKey="fillAllowedErrorsCountField"/> + <attachFile selector="{{AdminImportMainSection.selectFileToImport}}" userInput="{{importFile}}" stepKey="attachFileForImport"/> + </actionGroup> +</actionGroups> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminNavigateToImportHistoryPageActionGroup"> + <annotations> + <description>Navigates to the admin System > Data Transfer > Import History page.</description> + </annotations> + <amOnPage url="{{AdminImportHistoryPage.url}}" stepKey="navigateToImportHistoryPage"/> + </actionGroup> +</actionGroups> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminNavigateToImportPageActionGroup"> + <annotations> + <description>Navigates to the admin System > Data Transfer > Import page.</description> + </annotations> + <amOnPage url="{{AdminImportIndexPage.url}}" stepKey="navigateToImportPage"/> + </actionGroup> +</actionGroups> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <!-- Common Messages --> + <entity name="ImportCommonMessages"> + <data key="validFile">File is valid! To start import process press "Import" button</data> + <data key="success">Import successfully done</data> + </entity> + + <!-- Categories --> + <entity name="Import1" type="category"> + <data key="name">Import1</data> + <data key="name_lwr">import1</data> + <data key="is_active">true</data> + <data key="include_in_menu">true</data> + </entity> + + <!-- Products --> + <entity name="ImportSimple1" type="product"> + <data key="name">import-simple1</data> + <data key="sku">import-simple1</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="price">12.00</data> + <data key="visibility">4</data> + <data key="status">1</data> + <data key="quantity">100</data> + <data key="urlKey">import-simple1</data> + <data key="weight">12</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> + </entity> + <entity name="ImportSimple2" type="product"> + <data key="name">import-simple2</data> + <data key="sku">import-simple2</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="price">15.00</data> + <data key="visibility">4</data> + <data key="status">1</data> + <data key="quantity">100</data> + <data key="urlKey">import-simple2</data> + <data key="weight">12</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> + </entity> + <entity name="ImportSimple3" type="product"> + <data key="name">import-simple3</data> + <data key="sku">import-simple3</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="price">10.00</data> + <data key="visibility">4</data> + <data key="status">1</data> + <data key="quantity">100</data> + <data key="urlKey">import-simple3</data> + <data key="weight">12</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> + </entity> + <entity name="ImportConfigurable" type="product"> + <data key="fileName">import_configurable_product.csv</data> + <data key="importSummary">Created: 4, Updated: 0, Deleted: 0</data> + <data key="name">import-configurable</data> + <data key="sku">import-configurable</data> + <data key="type_id">configurable</data> + <data key="attribute_set_id">4</data> + <data key="price"/> + <data key="visibility">4</data> + <data key="status">1</data> + <data key="quantity"/> + <data key="urlKey">import-configurable</data> + <data key="weight">12</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> + </entity> + + <!-- Product Attributes --> + <entity name="ProductAttributeFrontendLabelImport1" type="FrontendLabel"> + <data key="store_id">0</data> + <data key="label">import_attribute1</data> + </entity> + <entity name="ProductAttributeWithThreeOptionsForImport" extends="productAttributeDropdownTwoOptions" type="ProductAttribute"> + <data key="attribute_code">import_attribute1</data> + <requiredEntity type="FrontendLabel">ProductAttributeFrontendLabelImport1</requiredEntity> + </entity> + <entity name="ProductAttributeOptionThreeForImport" extends="productAttributeOption3" type="ProductAttributeOption"> + <data key="label">option3</data> + </entity> + + <!-- Images --> + <entity name="TestImageImageContentExportImport" extends="TestImageContent" type="ImageContent"> + <data key="name">test_image.jpg</data> + </entity> + <entity name="ApiProductAttributeMediaGalleryForExportImport2" extends="ApiProductAttributeMediaGalleryEntryTestImage" type="ProductAttributeMediaGalleryEntry"> + <data key="label">Test Image</data> + <requiredEntity type="ImageContent">TestImageImageContentExportImport</requiredEntity> + </entity> +</entities> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminImportHistoryPage" url="admin/history" area="admin" module="Magento_ImportExport"/> +</pages> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminImportSimpleAndConfigurableProductsWithAssignedImagesTest"> + <annotations> + <features value="Import/Export"/> + <stories value="Import Products"/> + <title value="Import Configurable Product With Simple Child Products With Images"/> + <description value="Imports a .csv file containing a configurable product with 3 child simple products that + have images. Verifies that products are imported successfully and that the images are attached to the + products as expected."/> + <severity value="MAJOR"/> + <testCaseId value="MC-38222"/> + <group value="importExport"/> + </annotations> + + <before> + <!-- Create Product Attribute with 3 Options --> + <createData entity="ProductAttributeWithThreeOptionsForImport" stepKey="createImportProductAttribute"/> + <createData entity="ProductAttributeOptionOneForExportImport" stepKey="createImportProductAttributeOption1"> + <requiredEntity createDataKey="createImportProductAttribute"/> + </createData> + <createData entity="ProductAttributeOptionTwoForExportImport" stepKey="createImportProductAttributeOption2"> + <requiredEntity createDataKey="createImportProductAttribute"/> + </createData> + <createData entity="ProductAttributeOptionThreeForImport" stepKey="createImportProductAttributeOption3"> + <requiredEntity createDataKey="createImportProductAttribute"/> + </createData> + <createData entity="AddToDefaultSet" stepKey="addToProductAttributeSet"> + <requiredEntity createDataKey="createImportProductAttribute"/> + </createData> + + <!-- Create a Product & Attach a JPG & PNG --> + <createData entity="Import1" stepKey="createImportCategory"/> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="ApiSimpleProduct" stepKey="productForImages"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="ApiProductAttributeMediaGalleryForExportImport" stepKey="productImage1"> + <requiredEntity createDataKey="productForImages"/> + </createData> + <createData entity="ApiProductAttributeMediaGalleryForExportImport2" stepKey="productImage2"> + <requiredEntity createDataKey="productForImages"/> + </createData> + + <!-- Login as Admin --> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + + <after> + <!-- Delete Data --> + <deleteData createDataKey="createImportCategory" stepKey="deleteImportCategory"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="productForImages" stepKey="deleteProductForImages"/> + <deleteData url="/V1/products/{{ImportSimple1.urlKey}}" stepKey="deleteImportedSimpleProduct1"/> + <deleteData url="/V1/products/{{ImportSimple2.urlKey}}" stepKey="deleteImportedSimpleProduct2"/> + <deleteData url="/V1/products/{{ImportSimple3.urlKey}}" stepKey="deleteImportedSimpleProduct3"/> + <deleteData url="/V1/products/{{ImportConfigurable.urlKey}}" stepKey="deleteImportedConfigurableProduct"/> + <deleteData createDataKey="createImportProductAttribute" stepKey="deleteProductAttribute"/> + <actionGroup ref="NavigateToAndResetProductGridToDefaultViewActionGroup" stepKey="navigateToAndResetProductGridToDefaultView"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> + </after> + + <!-- Import Configurable Product with Simple Child Products & Assert No Errors --> + <actionGroup ref="AdminNavigateToImportPageActionGroup" stepKey="navigateToImportPage"/> + <actionGroup ref="AdminFillImportFormActionGroup" stepKey="fillImportForm"> + <argument name="importFile" value="{{ImportConfigurable.fileName}}"/> + </actionGroup> + <actionGroup ref="AdminClickCheckDataImportActionGroup" stepKey="clickCheckData"/> + <see selector="{{AdminImportValidationMessagesSection.success}}" userInput="{{ImportCommonMessages.validFile}}" stepKey="seeCheckDataResultMessage"/> + <dontSeeElementInDOM selector="{{AdminImportValidationMessagesSection.importErrorList}}" stepKey="dontSeeErrorMessage"/> + <actionGroup ref="AdminClickImportActionGroup" stepKey="clickImport"/> + <see selector="{{AdminImportValidationMessagesSection.notice}}" userInput="{{ImportConfigurable.importSummary}}" stepKey="seeNoticeMessage"/> + <see selector="{{AdminImportValidationMessagesSection.messageByType('success')}}" userInput="{{ImportCommonMessages.success}}" stepKey="seeImportMessage"/> + <dontSeeElementInDOM selector="{{AdminImportValidationMessagesSection.importErrorList}}" stepKey="dontSeeErrorMessage2"/> + + <!-- Reindex --> + <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> + <argument name="indices" value=""/> + </actionGroup> + + <!-- Admin: Verify Data on Import History Page --> + <actionGroup ref="AdminNavigateToImportHistoryPageActionGroup" stepKey="navigateToImportHistoryPage"/> + <actionGroup ref="AdminGridSortColumnDescendingActionGroup" stepKey="sortColumnByIdDescending"> + <argument name="columnLabel" value="history_id"/> + </actionGroup> + <see userInput="{{ImportConfigurable.fileName}}" selector="{{AdminDataGridTableSection.firstRow}}" stepKey="seeImportedFile"/> + <see userInput="{{ImportConfigurable.importSummary}}" selector="{{AdminDataGridTableSection.firstRow}}" stepKey="seeSummary"/> + + <!-- Admin: Verify Simple Product 1 on Product Index Page --> + <actionGroup ref="AssertProductOnAdminGridActionGroup" stepKey="assertSimpleProduct1InAdminGrid"> + <argument name="product" value="ImportSimple1"/> + </actionGroup> + <seeNumberOfElements selector="{{AdminProductGridSection.productGridRows}}" userInput="1" stepKey="seeOnly1Product"/> + + <!-- Admin: Verify Simple Product 1 on Edit Product Page --> + <actionGroup ref="AssertProductInfoOnEditPageActionGroup" stepKey="assertSimpleProduct1OnEditPage"> + <argument name="product" value="ImportSimple1"/> + </actionGroup> + <actionGroup ref="AssertProductImageAdminProductPageActionGroup" stepKey="assertProduct1ImageOnEditPage"> + <argument name="image" value="MagentoLogo"/> + </actionGroup> + + <!-- Admin: Verify Simple Product 2 on Product Index Page --> + <actionGroup ref="AssertProductOnAdminGridActionGroup" stepKey="assertSimpleProduct2InAdminGrid"> + <argument name="product" value="ImportSimple2"/> + </actionGroup> + <seeNumberOfElements selector="{{AdminProductGridSection.productGridRows}}" userInput="1" stepKey="seeOnly1Product2"/> + + <!-- Admin: Verify Simple Product 2 on Edit Product Page --> + <actionGroup ref="AssertProductInfoOnEditPageActionGroup" stepKey="assertSimpleProduct2OnEditPage"> + <argument name="product" value="ImportSimple2"/> + </actionGroup> + <actionGroup ref="AssertProductImageAdminProductPageActionGroup" stepKey="assertProduct2ImageOnEditPage"> + <argument name="image" value="TestImage"/> + </actionGroup> + + <!-- Admin: Verify Simple Product 3 on Product Index Page --> + <actionGroup ref="AssertProductOnAdminGridActionGroup" stepKey="assertSimpleProduct3InAdminGrid"> + <argument name="product" value="ImportSimple3"/> + </actionGroup> + <seeNumberOfElements selector="{{AdminProductGridSection.productGridRows}}" userInput="1" stepKey="seeOnly1Product3"/> + + <!-- Admin: Verify Simple Product 3 on Edit Product Page --> + <actionGroup ref="AssertProductInfoOnEditPageActionGroup" stepKey="assertSimpleProduct3OnEditPage"> + <argument name="product" value="ImportSimple3"/> + </actionGroup> + <actionGroup ref="ExpandAdminProductSectionActionGroup" stepKey="expandImageAndVideosSection"> + <argument name="sectionSelector" value="{{AdminProductImagesSection.productImagesToggle}}"/> + <argument name="sectionDependentSelector" value="{{AdminProductImagesSection.imageUploadButton}}"/> + </actionGroup> + <dontSeeElementInDOM selector="{{AdminProductImagesSection.imageElement}}" stepKey="dontSeeProductImage"/> + + <!-- Admin: Verify Configurable Product on Product Index Page --> + <actionGroup ref="AssertProductOnAdminGridActionGroup" stepKey="assertConfigurableProductInAdminGrid"> + <argument name="product" value="ImportConfigurable"/> + </actionGroup> + <seeNumberOfElements selector="{{AdminProductGridSection.productGridRows}}" userInput="1" stepKey="seeOnly1Product4"/> + + <!-- Admin: Verify Configurable Product on Edit Product Page --> + <actionGroup ref="AssertProductInfoOnEditPageActionGroup" stepKey="assertConfigurableProductOnEditPage"> + <argument name="product" value="ImportConfigurable"/> + </actionGroup> + <actionGroup ref="ExpandAdminProductSectionActionGroup" stepKey="expandImageAndVideosSection2"> + <argument name="sectionSelector" value="{{AdminProductImagesSection.productImagesToggle}}"/> + <argument name="sectionDependentSelector" value="{{AdminProductImagesSection.imageUploadButton}}"/> + </actionGroup> + <dontSeeElementInDOM selector="{{AdminProductImagesSection.imageElement}}" stepKey="dontSeeProductImage2"/> + + <!-- Storefront: Verify Configurable Product In Category --> + <actionGroup ref="StorefrontNavigateToCategoryUrlActionGroup" stepKey="goToCategoryPage"> + <argument name="categoryUrl" value="{{Import1.name_lwr}}"/> + </actionGroup> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productName}}" userInput="1" stepKey="seeOnly1Product5"/> + <see selector="{{StorefrontCategoryMainSection.productName}}" userInput="{{ImportConfigurable.name}}" stepKey="seeConfigurableProduct"/> + <dontSee selector="{{StorefrontCategoryMainSection.productName}}" userInput="{{ImportSimple1.name}}" stepKey="dontSeeSimpleProduct1"/> + <dontSee selector="{{StorefrontCategoryMainSection.productName}}" userInput="{{ImportSimple2.name}}" stepKey="dontSeeSimpleProduct2"/> + <dontSee selector="{{StorefrontCategoryMainSection.productName}}" userInput="{{ImportSimple3.name}}" stepKey="dontSeeSimpleProduct3"/> + + <!-- Storefront: Verify Configurable Product Info & Image --> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductStorefrontPage"> + <argument name="productUrl" value="{{ImportConfigurable.urlKey}}"/> + </actionGroup> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{ImportConfigurable.name}}" stepKey="seeProductName"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{ImportConfigurable.sku}}" stepKey="seeSku"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="As low as ${{ImportSimple3.price}}" stepKey="seePrice"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productImageSrc('placeholder')}}" stepKey="seePlaceholderImage"/> + + <!-- Storefront: Verify Configurable Product Option 1 Info & Image --> + <actionGroup ref="StorefrontProductPageSelectDropDownOptionValueActionGroup" stepKey="selectOption1"> + <argument name="attributeLabel" value="{{ProductAttributeFrontendLabelImport1.label}}"/> + <argument name="optionLabel" value="{{ProductAttributeOptionOneForExportImport.label}}"/> + </actionGroup> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{ImportConfigurable.name}}" stepKey="seeProductName2"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{ImportConfigurable.sku}}" stepKey="seeSku2"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="${{ImportSimple1.price}}" stepKey="seePrice2"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productImageSrc(MagentoLogo.filename)}}" stepKey="seeImage1"/> + + <!-- Storefront: Verify Configurable Product Option 2 Info & Image --> + <actionGroup ref="StorefrontProductPageSelectDropDownOptionValueActionGroup" stepKey="selectOption2"> + <argument name="attributeLabel" value="{{ProductAttributeFrontendLabelImport1.label}}"/> + <argument name="optionLabel" value="{{ProductAttributeOptionTwoForExportImport.label}}"/> + </actionGroup> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{ImportConfigurable.name}}" stepKey="seeProductName3"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{ImportConfigurable.sku}}" stepKey="seeSku3"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="${{ImportSimple2.price}}" stepKey="seePrice3"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productImageSrc(TestImage.filename)}}" stepKey="seeImage2"/> + + <!-- Storefront: Verify Configurable Product Option 3 Info & Image --> + <actionGroup ref="StorefrontProductPageSelectDropDownOptionValueActionGroup" stepKey="selectOption3"> + <argument name="attributeLabel" value="{{ProductAttributeFrontendLabelImport1.label}}"/> + <argument name="optionLabel" value="{{ProductAttributeOptionThreeForImport.label}}"/> + </actionGroup> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{ImportConfigurable.name}}" stepKey="seeProductName4"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{ImportConfigurable.sku}}" stepKey="seeSku4"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="${{ImportSimple3.price}}" stepKey="seePrice4"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productImageSrc('placeholder')}}" stepKey="seePlaceholderImage2"/> + </test> +</tests> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Assert the navigation arrows on Storefront Product page --> + <actionGroup name="AssertProductVideoNavigationArrowsActionGroup"> + <annotations> + <description>Validates the navigation arrows on the Storefront Product page.</description> + </annotations> + <arguments> + <argument name="videoType" type="string" defaultValue="vimeo"/> + </arguments> + + <dontSeeElement selector="{{StorefrontProductMediaSection.imagePrevButton}}" stepKey="dontSeePrevButton"/> + <moveMouseOver selector="{{StorefrontProductMediaSection.mainImageForJsActions}}" stepKey="hoverOverImage"/> + <seeElement selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="seeNextButton"/> + <click selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="clickNextButton"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productVideo(videoType)}}" stepKey="seeProductVideoDataType"/> + + <dontSeeElement selector="{{StorefrontProductInfoMainSection.clickCloseVideo}}" stepKey="dontSeeCloseVideo"/> + + <click selector="{{StorefrontProductInfoMainSection.clickPlayVideo}}" stepKey="clickToPlayVideo"/> + <wait stepKey="waitFiveSecondsToPlayVideo" time="5"/> + + <dontSeeElement selector="{{StorefrontProductMediaSection.imagePrevButton}}" stepKey="dontSeePrevButtonSecond"/> + <dontSeeElement selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="dontSeeNextButton"/> + + <seeElement selector="{{StorefrontProductInfoMainSection.clickCloseVideo}}" stepKey="seeCloseVideo"/> + <click selector="{{StorefrontProductInfoMainSection.clickCloseVideo}}" stepKey="clickToCloseVideo"/> + <wait stepKey="waitTwoSecondsToCloseVideo" time="2"/> + + <moveMouseOver selector="{{StorefrontProductMediaSection.mainImageForJsActions}}" stepKey="hoverOverImageSecond"/> + <seeElement selector="{{StorefrontProductMediaSection.imagePrevButton}}" stepKey="seePrevButton"/> + <click selector="{{StorefrontProductMediaSection.imagePrevButton}}" stepKey="clickPrevButton"/> + </actionGroup> +</actionGroups> 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 @@ <element name="videoPausedMode" type="button" selector="//*[contains(@class, 'paused-mode')]"/> <element name="videoPlayedMode" type="button" selector="//*[contains(@class,'playing-mode')]"/> <element name="frameVideo" type="button" selector="widget2"/> + <element name="clickPlayVideo" type="block" selector="//*[contains(@class,'fotorama__stage__shaft')]"/> + <element name="clickCloseVideo" type="block" selector="//*[@class='fotorama__video-close fotorama-show-control']"/> </section> </sections> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="VimeoVideoControlButtonsOnProductPageTest"> + <annotations> + <features value="ProductVideo"/> + <stories value="Navigation arrow buttons not visible after video starts on product image"/> + <title value="Navigation arrow buttons not visible after video starts on product image"/> + <description value="Navigation arrow buttons not visible after video starts on product image"/> + <severity value="MAJOR"/> + <testCaseId value="MC-40398"/> + <useCaseId value="MC-39759"/> + <group value="productVideo"/> + </annotations> + <before> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + <!-- Login to Admin page --> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <after> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <!-- Logout from Admin page --> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + </after> + + <!-- Open product edit page --> + <actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="goToProductEditPage"> + <argument name="productId" value="$createProduct.id$"/> + </actionGroup> + <!-- Add image to product --> + <actionGroup ref="AddProductImageActionGroup" stepKey="addImageForProduct"> + <argument name="image" value="MagentoLogo"/> + </actionGroup> + <!-- Add product video --> + <actionGroup ref="AddProductVideoActionGroup" stepKey="addProductVideo"> + <argument name="video" value="VimeoProductVideo"/> + </actionGroup> + <!-- Save product form --> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProductForm"/> + + <!-- Open storefront product page --> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="goToStorefrontProductPage"> + <argument name="productUrl" value="$createProduct.custom_attributes[url_key]$"/> + </actionGroup> + + <!-- Check the navigation arrows on Storefront Product page --> + <actionGroup ref="AssertProductVideoNavigationArrowsActionGroup" stepKey="assertProductVideoNavigationArrowsOnStorefrontProductPage"> + <argument name="videoType" value="vimeo"/> + </actionGroup> + </test> +</tests> 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 @@ <visible>false</visible> </settings> </column> - <column name="subtotal" class="Magento\Sales\Ui\Component\Listing\Column\PurchasedPrice"> + <column name="subtotal" class="Magento\Sales\Ui\Component\Listing\Column\Price"> <settings> <filter>textRange</filter> <label translate="true">Subtotal</label> <visible>false</visible> </settings> </column> - <column name="shipping_and_handling" class="Magento\Sales\Ui\Component\Listing\Column\PurchasedPrice"> + <column name="shipping_and_handling" class="Magento\Sales\Ui\Component\Listing\Column\Price"> <settings> <filter>textRange</filter> <label translate="true">Shipping & Handling</label> 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 @@ <visible>false</visible> </settings> </column> - <column name="subtotal" class="Magento\Sales\Ui\Component\Listing\Column\PurchasedPrice"> + <column name="subtotal" class="Magento\Sales\Ui\Component\Listing\Column\Price"> <settings> <filter>textRange</filter> <label translate="true">Subtotal</label> <visible>false</visible> </settings> </column> - <column name="shipping_and_handling" class="Magento\Sales\Ui\Component\Listing\Column\PurchasedPrice"> + <column name="shipping_and_handling" class="Magento\Sales\Ui\Component\Listing\Column\Price"> <settings> <filter>textRange</filter> <label translate="true">Shipping & Handling</label> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StoreFrontAssertDropDownSearchSuggestionActionGroup"> + <annotations> + <description>Fills the Storefront Quick Search field. Validates that the Search Suggestion is present</description> + </annotations> + <arguments> + <argument name="searchQuery" type="string"/> + </arguments> + + <waitForElementVisible selector="{{StorefrontQuickSearchResultsSection.searchTextBox}}" stepKey="waitForQuickSearchToBeVisible"/> + <fillField selector="{{StorefrontQuickSearchResultsSection.searchTextBox}}" userInput="{{searchQuery}}" stepKey="fillSearchInput"/> + <waitForElementVisible selector="{{StorefrontQuickSearchSection.searchDropDownSuggestion}}" stepKey="WaitForSearchDropDownSuggestion"/> + <click selector="//div[@class='panel wrapper']" stepKey="clickOnSomewhere"/> + <dontSee selector="{{StorefrontQuickSearchSection.searchDropDownSuggestion}}" stepKey="dontSeeDropDownSuggestion"/> + <click selector="{{StorefrontQuickSearchSection.searchPhrase}}" stepKey="clickOnSearchPhrase"/> + <pressKey selector="{{StorefrontQuickSearchSection.searchPhrase}}" parameterArray="[\Facebook\WebDriver\WebDriverKeys::DOWN]" stepKey="pressDown"/> + <waitForElementVisible selector="{{StorefrontQuickSearchSection.searchDropDownSuggestion}}" stepKey="WaitForSearchDropDownSuggestionSecond"/> + </actionGroup> +</actionGroups> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontVerifySearchSuggestionByControlButtonsTest"> + <annotations> + <stories value="Search Term"/> + <title value="Auto suggestion box not reappearing after clicking outside the text field"/> + <description value="Auto suggestion box not reappearing after clicking outside the text field"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-40466"/> + <useCaseId value="MC-40376"/> + </annotations> + + <before> + <!-- Create Simple Product --> + <createData entity="defaultSimpleProduct" stepKey="simpleProduct"/> + + <!-- Login as admin --> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + + <!-- Perform reindex and flush cache --> + <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> + <argument name="indices" value=""/> + </actionGroup> + <actionGroup ref="CliCacheFlushActionGroup" stepKey="flushCache"> + <argument name="tags" value=""/> + </actionGroup> + </before> + <after> + <!-- Delete create product --> + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + + <!-- Go to the catalog search term page --> + <actionGroup ref="AdminOpenCatalogSearchTermIndexPageActionGroup" stepKey="openAdminCatalogSearchTermIndexPage"/> + + <!-- Filter the search term --> + <actionGroup ref="AdminSearchTermFilterBySearchQueryActionGroup" stepKey="filterByThirdSearchQuery"> + <argument name="searchQuery" value="$simpleProduct.name$"/> + </actionGroup> + <!-- Delete created below search terms --> + <actionGroup ref="AdminDeleteSearchTermActionGroup" stepKey="deleteSearchTerms"/> + </after> + + <!-- Go to storefront home page --> + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openStoreFrontHomePage"/> + + <!-- Storefront quick search by product name --> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByProductName"> + <argument name="phrase" value="$simpleProduct.name$"/> + </actionGroup> + + <!-- Verify search suggestions and select the suggestion from dropdown options --> + <actionGroup ref="StoreFrontAssertDropDownSearchSuggestionActionGroup" stepKey="seeDropDownSearchSuggestion"> + <argument name="searchQuery" value="$simpleProduct.name$"/> + </actionGroup> + </test> +</tests> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminGridSortColumnAscendingActionGroup"> + <annotations> + <description>Sorts the specified column in ascending order on Admin Grid page.</description> + </annotations> + <arguments> + <argument name="columnLabel" type="string"/> + </arguments> + <conditionalClick selector="{{AdminDataGridHeaderSection.columnByLabel(columnLabel)}}" dependentSelector="{{AdminDataGridHeaderSection.columnNotSorted(columnLabel)}}" visible="true" stepKey="clickColumnIfNotSorted"/> + <waitForPageLoad stepKey="waitForGridLoad1"/> + <conditionalClick selector="{{AdminDataGridHeaderSection.columnByLabel(columnLabel)}}" dependentSelector="{{AdminDataGridHeaderSection.columnSortedDescending(columnLabel)}}" visible="true" stepKey="clickColumnIfDescending"/> + <waitForPageLoad stepKey="waitForGridLoad2"/> + <waitForElementVisible selector="{{AdminDataGridHeaderSection.columnSortedAscending(columnLabel)}}" stepKey="seeColumnAscending"/> + </actionGroup> +</actionGroups> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminGridSortColumnDescendingActionGroup"> + <annotations> + <description>Sorts the specified column in descending order on Admin Grid page.</description> + </annotations> + <arguments> + <argument name="columnLabel" type="string"/> + </arguments> + <conditionalClick selector="{{AdminDataGridHeaderSection.columnByLabel(columnLabel)}}" dependentSelector="{{AdminDataGridHeaderSection.columnNotSorted(columnLabel)}}" visible="true" stepKey="clickColumnIfNotSorted"/> + <waitForPageLoad stepKey="waitForGridLoad1"/> + <conditionalClick selector="{{AdminDataGridHeaderSection.columnByLabel(columnLabel)}}" dependentSelector="{{AdminDataGridHeaderSection.columnSortedAscending(columnLabel)}}" visible="true" stepKey="clickColumnIfAscending"/> + <waitForPageLoad stepKey="waitForGridLoad2"/> + <waitForElementVisible selector="{{AdminDataGridHeaderSection.columnSortedDescending(columnLabel)}}" stepKey="seeColumnDescending"/> + </actionGroup> +</actionGroups> 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 @@ <element name="columnCheckbox" type="checkbox" selector="//div[contains(@class,'admin__data-grid-action-columns')]//div[contains(@class, 'admin__field-option')]//label[text() = '{{column}}']/preceding-sibling::input" parameterized="true"/> <element name="perPage" type="select" selector="#product_attributes_listing.product_attributes_listing.listing_top.listing_paging_sizes"/> <element name="attributeName" type="input" selector="//div[text()='{{arg}}']/../preceding-sibling::td//input" parameterized="true"/> + <!--Sorting--> + <element name="columnByLabel" type="text" parameterized="true" selector="th[data-sort='{{columnName}}']"/> + <element name="columnNotSorted" type="text" parameterized="true" selector="th[data-sort='{{columnName}}'].not-sort"/> + <element name="columnSortedAscending" type="text" parameterized="true" selector="th[data-sort='{{columnName}}']._ascend"/> + <element name="columnSortedDescending" type="text" parameterized="true" selector="th[data-sort='{{columnName}}']._descend"/> </section> </sections> 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 = <<<QUERY +{ + products(filter: {sku: {eq: "{$productSku}"}}) + { + items { + related_products + { + sku + name + url_key + } + } + } +} +QUERY; + $response = $this->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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Alerts; + +use Magento\Catalog\Model\ResourceModel\Product as ProductResource; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Module\Manager; +use Magento\Framework\ObjectManagerInterface; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Base alert's tab test logic + */ +abstract class AbstractAlertTest extends TestCase +{ + /** @var ObjectManagerInterface */ + protected $objectManager; + + /** @var RequestInterface */ + protected $request; + + /** @var StoreManagerInterface */ + private $storeManager; + + /** @var ProductResource */ + private $productResource; + + /** + * @inheritdoc + */ + public static function setUpBeforeClass(): void + { + parent::setUpBeforeClass(); + + $objectManager = Bootstrap::getObjectManager(); + /** @var Manager $moduleManager */ + $moduleManager = $objectManager->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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Alerts; + +use Magento\Framework\View\LayoutInterface; + +/** + * Check price alert grid + * + * @see \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Alerts\Price + * + * @magentoAppArea adminhtml + */ +class PriceTest extends AbstractAlertTest +{ + /** @var Price */ + private $block; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + parent::setUp(); + + $this->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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Controller\Adminhtml\Category; + +use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * Test cases related to edit category. + * + * @see \Magento\Catalog\Controller\Adminhtml\Category\Edit + * @magentoAppArea adminhtml + * @magentoDbIsolation enabled + */ +class EditTest extends AbstractBackendController +{ + /** @var CollectionFactory */ + private $categoryCollectionFactory; + + /** @var StoreManagerInterface */ + private $storeManager; + + /** + * @inheritDoc + */ + protected function setUp(): void + { + parent::setUp(); + + $this->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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Controller\Adminhtml\Product; + +use Magento\Catalog\Model\ResourceModel\Product as ProductResource; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Xpath; +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * Class contains of base logic for alert controllers tests + */ +abstract class AbstractAlertTest extends AbstractBackendController +{ + /** @var ProductResource */ + private $productResource; + + /** @var StoreManagerInterface */ + private $storeManager; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + parent::setUp(); + + $this->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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Controller\Adminhtml\Product; + +/** + * Tests for price alert grid controller + * + * @see \Magento\Catalog\Controller\Adminhtml\Product\AlertsPriceGrid + * + * @magentoAppArea adminhtml + * @magentoDbIsolation disabled + */ +class AlertsPriceGridTest extends AbstractAlertTest +{ + /** + * @dataProvider priceLimitProvider + * + * @magentoDataFixture Magento/ProductAlert/_files/simple_product_with_two_alerts.php + * + * @param string $email + * @param int|null $limit + * @param $expectedCount + * @return void + */ + public function testExecute(string $email, ?int $limit, $expectedCount): void + { + $this->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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Controller\Adminhtml\Product; + +/** + * Tests for stock alert grid controller + * + * @see \Magento\Catalog\Controller\Adminhtml\Product\AlertsStockGrid + * + * @magentoAppArea adminhtml + * @magentoDbIsolation disabled + */ +class AlertsStockGridTest extends AbstractAlertTest +{ + /** + * @dataProvider stockLimitProvider + * + * @magentoDataFixture Magento/ProductAlert/_files/simple_product_with_two_alerts.php + * + * @param string $email + * @param int|null $limit + * @param int $expectedCount + * @return void + */ + public function testExecute(string $email, ?int $limit, int $expectedCount): void + { + $this->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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Attribute\Backend; + +use Magento\AsynchronousOperations\Api\Data\OperationInterface as OperationDataInterface; +use Magento\AsynchronousOperations\Model\ResourceModel\Operation\CollectionFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Action; +use Magento\Framework\Bulk\OperationInterface; +use Magento\Framework\DB\Adapter\DeadlockException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\MessageQueue\MessageEncoder; +use Magento\Framework\ObjectManagerInterface; +use Magento\MysqlMq\Model\Driver\Queue; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\MysqlMq\DeleteTopicRelatedMessages; +use PHPUnit\Framework\TestCase; + +/** + * Tests for Mysql website assigning consumer + * + * @see \Magento\Catalog\Model\Attribute\Backend\ConsumerWebsiteAssign + * + * @magentoDbIsolation disabled + * @magentoAppArea adminhtml + */ +class ConsumerWebsiteAssignTest extends TestCase +{ + private const TOPIC_NAME = 'product_action_attribute.website.update'; + + /** @var DeleteTopicRelatedMessages */ + private static $deleteTopicRelatedMessages; + + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var ConsumerWebsiteAssign */ + private $consumer; + + /** @var Queue */ + private $queue; + + /** @var MessageEncoder */ + private $messageEncoder; + + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** @var WebsiteRepositoryInterface */ + private $websiteRepository; + + /** @var CollectionFactory */ + private $operationCollectionFactory; + + /** + * @inheritdoc + */ + public static function setUpBeforeClass(): void + { + parent::setUpBeforeClass(); + + $objectManager = Bootstrap::getObjectManager(); + self::$deleteTopicRelatedMessages = $objectManager->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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Bulk\BulkManagementInterface; +use Magento\Framework\Bulk\OperationInterface; +use Magento\Framework\DataObject\IdentityGeneratorInterface; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +Resolver::getInstance()->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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\TestFramework\MysqlMq\DeleteTopicRelatedMessages; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +$objectManager = Bootstrap::getObjectManager(); +/** @var DeleteTopicRelatedMessages $deleteTopicRelatedMessages */ +$deleteTopicRelatedMessages = $objectManager->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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Catalog\Api\Data\ProductInterfaceFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Eav\Model\Config; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Catalog\Api\Data\ProductLinkInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Model\Category; +use Magento\Catalog\Model\CategoryFactory; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var CategoryFactory $categoryFactory */ +$categoryFactory = $objectManager->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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var CategoryRepositoryInterface $categoryRepository */ +$categoryRepository = $objectManager->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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Bulk\BulkManagementInterface; +use Magento\Framework\Bulk\OperationInterface; +use Magento\Framework\DataObject\IdentityGeneratorInterface; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +Resolver::getInstance()->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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\TestFramework\MysqlMq\DeleteTopicRelatedMessages; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +$objectManager = Bootstrap::getObjectManager(); +/** @var DeleteTopicRelatedMessages $deleteTopicRelatedMessages */ +$deleteTopicRelatedMessages = $objectManager->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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductInterfaceFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\ProductAlert\Model\ResourceModel\Price as PriceResource; +use Magento\ProductAlert\Model\PriceFactory; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +Resolver::getInstance()->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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\ProductAlert\Model\ResourceModel\Price as PriceResource; +use Magento\ProductAlert\Model\PriceFactory; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + + +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductRepositoryInterface $peoductRepository */ +$productRepository = $objectManager->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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Customer\Model\CustomerRegistry; +use Magento\ProductAlert\Model\PriceFactory; +use Magento\ProductAlert\Model\ResourceModel\Price as PriceResource; +use Magento\ProductAlert\Model\ResourceModel\Stock as StockResource; +use Magento\ProductAlert\Model\StockFactory; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +Resolver::getInstance()->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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +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; + +$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'); +/** @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 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Sales\Model\ResourceModel\Order\Creditmemo\Grid; - -use Magento\Framework\ObjectManagerInterface; -use Magento\TestFramework\Helper\Bootstrap; -use PHPUnit\Framework\TestCase; - -/** - * Test Magento\Sales\Model\ResourceModel\Order\Creditmemo\Grid - */ -class CollectionTest extends TestCase -{ - /** - * @var ObjectManagerInterface - */ - private $objectManager; - - /** - * @inheritDoc - */ - protected function setUp(): void - { - $this->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 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Sales\Model\ResourceModel\Order\Creditmemo\Order\Grid; - -use Magento\Framework\ObjectManagerInterface; -use Magento\TestFramework\Helper\Bootstrap; -use PHPUnit\Framework\TestCase; - -/** - * Test Magento\Sales\Model\ResourceModel\Order\Creditmemo\Grid\Order\Grid - */ -class CollectionTest extends TestCase -{ - /** - * @var ObjectManagerInterface - */ - private $objectManager; - - /** - * @inheritDoc - */ - protected function setUp(): void - { - $this->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)); + } }