From bde57a3a0c018cfb9924f150f4d651600ed92406 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov Date: Tue, 14 Apr 2020 17:55:56 -0500 Subject: [PATCH 01/24] MC-29755: X-Magento-Tags header too large --- .../Frontend/ProductIdentitiesExtender.php | 48 ------------------- .../Magento/ConfigurableProduct/etc/di.xml | 3 -- 2 files changed, 51 deletions(-) delete mode 100644 app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtender.php diff --git a/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtender.php b/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtender.php deleted file mode 100644 index 92b7ab0d88ea8..0000000000000 --- a/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtender.php +++ /dev/null @@ -1,48 +0,0 @@ -configurableType = $configurableType; - } - - /** - * Add child identities to product identities - * - * @param Product $subject - * @param array $identities - * @return array - */ - public function afterGetIdentities(Product $subject, array $identities): array - { - foreach ($this->configurableType->getChildrenIds($subject->getId()) as $childIds) { - foreach ($childIds as $childId) { - $identities[] = Product::CACHE_TAG . '_' . $childId; - } - } - - return array_unique($identities); - } -} diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml index c8a278df92dc6..10ffcd98eed44 100644 --- a/app/code/Magento/ConfigurableProduct/etc/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/di.xml @@ -219,9 +219,6 @@ - - - From b0071aff2c0289d72431e05f22ac353236242107 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov Date: Wed, 15 Apr 2020 10:44:26 -0500 Subject: [PATCH 02/24] MC-29755: X-Magento-Tags header too large --- app/code/Magento/ConfigurableProduct/etc/frontend/di.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml b/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml index b2d50f54f5334..f60234453dc60 100644 --- a/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml @@ -10,9 +10,6 @@ - - - From d2e252cf2696247715b927833aad7c22d8565ab6 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov Date: Wed, 15 Apr 2020 11:38:24 -0500 Subject: [PATCH 03/24] MC-29755: X-Magento-Tags header too large --- .../ProductIdentitiesExtenderTest.php | 77 ------------------- 1 file changed, 77 deletions(-) delete mode 100644 app/code/Magento/ConfigurableProduct/Test/Unit/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php deleted file mode 100644 index b4fb5ccfaa558..0000000000000 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php +++ /dev/null @@ -1,77 +0,0 @@ -product = $this->getMockBuilder(Product::class) - ->disableOriginalConstructor() - ->setMethods(['getId']) - ->getMock(); - - $this->configurableTypeMock = $this->getMockBuilder(Configurable::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->plugin = new ProductIdentitiesExtender($this->configurableTypeMock); - } - - public function testAfterGetIdentities() - { - $identities = [ - 'SomeCacheId', - 'AnotherCacheId', - ]; - $productId = 12345; - $childIdentities = [ - 0 => [1, 2, 5, 100500] - ]; - $expectedIdentities = [ - 'SomeCacheId', - 'AnotherCacheId', - Product::CACHE_TAG . '_' . 1, - Product::CACHE_TAG . '_' . 2, - Product::CACHE_TAG . '_' . 5, - Product::CACHE_TAG . '_' . 100500, - ]; - - $this->product->expects($this->once()) - ->method('getId') - ->willReturn($productId); - - $this->configurableTypeMock->expects($this->once()) - ->method('getChildrenIds') - ->with($productId) - ->willReturn($childIdentities); - - $productIdentities = $this->plugin->afterGetIdentities($this->product, $identities); - $this->assertEquals($expectedIdentities, $productIdentities); - } -} From 219024d611ebeef5234901d2f3d784c9fc355721 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov Date: Wed, 15 Apr 2020 12:57:37 -0500 Subject: [PATCH 04/24] MC-29755: X-Magento-Tags header too large --- .../ProductIdentitiesExtenderTest.php | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php index 1fffd701c509f..f484dd936e8ab 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php @@ -19,20 +19,7 @@ class ProductIdentitiesExtenderTest extends TestCase { /** - * Check, product identities extender plugin is registered for storefront. - * - * @magentoAppArea frontend - * @return void - */ - public function testIdentitiesExtenderIsRegistered(): void - { - $pluginInfo = Bootstrap::getObjectManager()->get(PluginList::class) - ->get(\Magento\Catalog\Model\Product::class, []); - $this->assertSame(ProductIdentitiesExtender::class, $pluginInfo['product_identities_extender']['instance']); - } - - /** - * Check plugin will add children ids to configurable product identities on storefront. + * Check that no children identities are added to the parent product in frontend area * * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php * @magentoAppArea frontend @@ -42,20 +29,16 @@ public function testGetIdentitiesForConfigurableProductOnStorefront(): void { $productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); $configurableProduct = $productRepository->get('configurable'); - $simpleProduct1 = $productRepository->get('simple_10'); - $simpleProduct2 = $productRepository->get('simple_20'); $expectedIdentities = [ 'cat_p_' . $configurableProduct->getId(), - 'cat_p', - 'cat_p_' . $simpleProduct1->getId(), - 'cat_p_' . $simpleProduct2->getId(), + 'cat_p' ]; $this->assertEquals($expectedIdentities, $configurableProduct->getIdentities()); } /** - * Check plugin won't add children ids to configurable product identities in admin area. + * Check that no children identities are added to the parent product in frontend area * * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php * @magentoAppArea adminhtml From b6206fec5a4a1c88dd3b46c512c1e6314e7451c9 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov Date: Wed, 15 Apr 2020 13:05:11 -0500 Subject: [PATCH 05/24] MC-29755: X-Magento-Tags header too large --- .../ProductIdentitiesExtenderTest.php | 56 ------------------- .../ConfigurableProduct/Model/ProductTest.php | 40 ++++++++++++- 2 files changed, 38 insertions(+), 58 deletions(-) delete mode 100644 dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php deleted file mode 100644 index f484dd936e8ab..0000000000000 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php +++ /dev/null @@ -1,56 +0,0 @@ -get(ProductRepositoryInterface::class); - $configurableProduct = $productRepository->get('configurable'); - $expectedIdentities = [ - 'cat_p_' . $configurableProduct->getId(), - 'cat_p' - - ]; - $this->assertEquals($expectedIdentities, $configurableProduct->getIdentities()); - } - - /** - * Check that no children identities are added to the parent product in frontend area - * - * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php - * @magentoAppArea adminhtml - * @return void - */ - public function testGetIdentitiesForConfigurableProductInAdminArea(): void - { - $productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); - $configurableProduct = $productRepository->get('configurable'); - $expectedIdentities = [ - 'cat_p_' . $configurableProduct->getId(), - ]; - $this->assertEquals($expectedIdentities, $configurableProduct->getIdentities()); - } -} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ProductTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ProductTest.php index 223c9fbe708e2..f4ab37cca1656 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ProductTest.php @@ -22,7 +22,43 @@ public function testGetIdentities() $simple10Product = $productRepository->get('simple_10'); $simple20Product = $productRepository->get('simple_20'); - $this->assertEmpty(array_diff($confProduct->getIdentities(), $simple10Product->getIdentities())); - $this->assertEmpty(array_diff($confProduct->getIdentities(), $simple20Product->getIdentities())); + $this->assertNotEmpty(array_diff($confProduct->getIdentities(), $simple10Product->getIdentities())); + $this->assertNotEmpty(array_diff($confProduct->getIdentities(), $simple20Product->getIdentities())); + } + + /** + * Check that no children identities are added to the parent product in frontend area + * + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @magentoAppArea frontend + * @return void + */ + public function testGetIdentitiesForConfigurableProductOnStorefront(): void + { + $productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); + $configurableProduct = $productRepository->get('configurable'); + $expectedIdentities = [ + 'cat_p_' . $configurableProduct->getId(), + 'cat_p' + + ]; + $this->assertEquals($expectedIdentities, $configurableProduct->getIdentities()); + } + + /** + * Check that no children identities are added to the parent product in frontend area + * + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @magentoAppArea adminhtml + * @return void + */ + public function testGetIdentitiesForConfigurableProductInAdminArea(): void + { + $productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); + $configurableProduct = $productRepository->get('configurable'); + $expectedIdentities = [ + 'cat_p_' . $configurableProduct->getId(), + ]; + $this->assertEquals($expectedIdentities, $configurableProduct->getIdentities()); } } From e85903535b71e3e6fe534123d60a259099ce6aa5 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov Date: Wed, 15 Apr 2020 14:50:29 -0500 Subject: [PATCH 06/24] MC-29755: X-Magento-Tags header too large --- app/code/Magento/ConfigurableProduct/etc/di.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml index 10ffcd98eed44..c8a278df92dc6 100644 --- a/app/code/Magento/ConfigurableProduct/etc/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/di.xml @@ -219,6 +219,9 @@ + + + From ca59b15fe42e18cd26ea8cfc093fdd5e4c6773a8 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov Date: Wed, 15 Apr 2020 15:13:53 -0500 Subject: [PATCH 07/24] MC-29755: X-Magento-Tags header too large --- .../testsuite/Magento/ConfigurableProduct/Model/ProductTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ProductTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ProductTest.php index f4ab37cca1656..80abfc7a84e09 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ProductTest.php @@ -40,7 +40,6 @@ public function testGetIdentitiesForConfigurableProductOnStorefront(): void $expectedIdentities = [ 'cat_p_' . $configurableProduct->getId(), 'cat_p' - ]; $this->assertEquals($expectedIdentities, $configurableProduct->getIdentities()); } From 520590a2db348a1f8e6d4c1a8274a4f248fcdcb8 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov Date: Fri, 17 Apr 2020 11:22:23 -0500 Subject: [PATCH 08/24] MC-29755: X-Magento-Tags header too large --- .../ConfigurableProduct/Model/ProductTest.php | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ProductTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ProductTest.php index 80abfc7a84e09..c59818aca5191 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ProductTest.php @@ -12,20 +12,6 @@ class ProductTest extends \PHPUnit\Framework\TestCase { - /** - * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php - */ - public function testGetIdentities() - { - $productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); - $confProduct = $productRepository->get('configurable'); - $simple10Product = $productRepository->get('simple_10'); - $simple20Product = $productRepository->get('simple_20'); - - $this->assertNotEmpty(array_diff($confProduct->getIdentities(), $simple10Product->getIdentities())); - $this->assertNotEmpty(array_diff($confProduct->getIdentities(), $simple20Product->getIdentities())); - } - /** * Check that no children identities are added to the parent product in frontend area * From 927b848d1506a3c78e0c5ce2e15ee269b4ee3295 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov Date: Wed, 13 May 2020 13:36:16 -0500 Subject: [PATCH 09/24] MC-29755: X-Magento-Tags header too large --- .../Unit/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 app/code/Magento/ConfigurableProduct/Test/Unit/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php deleted file mode 100644 index e69de29bb2d1d..0000000000000 From 219151efc5d3303883b9d5365f41c97aca3c9bb0 Mon Sep 17 00:00:00 2001 From: "rostyslav.hymon" Date: Wed, 20 May 2020 10:42:01 +0300 Subject: [PATCH 10/24] MC-34257: [Magento Cloud] "sales_order_shipment_track_save_commit_after" not triggering for orders created with the API. --- app/etc/di.xml | 1 + .../Interception/PluginList/PluginList.php | 13 +++- .../Test/Unit/PluginList/PluginListTest.php | 76 +++++++++++++++++-- .../Test/Unit/_files/reader_mock_map.php | 49 ++++++++---- 4 files changed, 116 insertions(+), 23 deletions(-) diff --git a/app/etc/di.xml b/app/etc/di.xml index a94adaae5cf06..9f9b04b91da65 100644 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -429,6 +429,7 @@ Magento\Framework\ObjectManager\Config\Reader\Dom\Proxy plugin-list + primary global diff --git a/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php b/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php index bf1372dc007a1..610ae9473a725 100644 --- a/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php +++ b/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php @@ -281,9 +281,18 @@ protected function _loadScopedData() { $scope = $this->_configScope->getCurrentScope(); if (false == isset($this->_loadedScopes[$scope])) { - if (false == in_array($scope, $this->_scopePriorityScheme)) { - $this->_scopePriorityScheme[] = $scope; + $index = array_search($scope, $this->_scopePriorityScheme); + /** + * Force current scope to be at the end of the scheme to ensure that default priority scopes are loaded. + * Mostly happens when the current scope is primary. + * For instance if the default scope priority scheme is [primary, global] and current scope is primary, + * the resulted scheme will be [global, primary] so global scope is loaded. + */ + if ($index !== false) { + unset($this->_scopePriorityScheme[$index]); } + $this->_scopePriorityScheme[] = $scope; + $cacheId = implode('|', $this->_scopePriorityScheme) . "|" . $this->_cacheId; $data = $this->_cache->load($cacheId); if ($data) { diff --git a/lib/internal/Magento/Framework/Interception/Test/Unit/PluginList/PluginListTest.php b/lib/internal/Magento/Framework/Interception/Test/Unit/PluginList/PluginListTest.php index 0122e0ae0507b..56740268026c2 100644 --- a/lib/internal/Magento/Framework/Interception/Test/Unit/PluginList/PluginListTest.php +++ b/lib/internal/Magento/Framework/Interception/Test/Unit/PluginList/PluginListTest.php @@ -68,7 +68,7 @@ class PluginListTest extends TestCase private $serializerMock; /** - * @var ObjectManagerInterface||\PHPUnit\Framework\MockObject\MockObject + * @var ObjectManagerInterface|MockObject */ private $objectManagerMock; @@ -172,15 +172,23 @@ public function testGetPlugin() } /** - * @param $expectedResult - * @param $type - * @param $method - * @param $scopeCode + * @param array $expectedResult + * @param string $type + * @param string $method + * @param string $scopeCode * @param string $code + * @param array $scopePriorityScheme * @dataProvider getPluginsDataProvider */ - public function testGetPlugins($expectedResult, $type, $method, $scopeCode, $code = '__self') - { + public function testGetPlugins( + ?array $expectedResult, + string $type, + string $method, + string $scopeCode, + string $code = '__self', + array $scopePriorityScheme = ['global'] + ): void { + $this->setScopePriorityScheme($scopePriorityScheme); $this->configScopeMock->expects( $this->any() )->method( @@ -245,7 +253,47 @@ public function getPluginsDataProvider() ItemContainer::class, 'getName', 'backend' - ] + ], + [ + // even though the scope is primary, both primary and global scopes are loaded + // because global is in default priority scheme + [ + 4 => [ + 'primary_plugin', + 'simple_plugin', + ] + ], + \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item::class, + 'getName', + 'primary', + '__self', + ['primary', 'global'] + ], + [ + [ + 4 => [ + 'primary_plugin', + 'simple_plugin', + ] + ], + \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item::class, + 'getName', + 'global', + '__self', + ['primary', 'global'] + ], + [ + [ + 4 => [ + 'primary_plugin', + ] + ], + \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item::class, + 'getName', + 'frontend', + '__self', + ['primary', 'global'] + ], ]; } @@ -348,4 +396,16 @@ public function testLoadScopeDataWithEmptyData() ) ); } + + /** + * @param array $areaCodes + * @throws \ReflectionException + */ + private function setScopePriorityScheme(array $areaCodes): void + { + $reflection = new \ReflectionClass($this->object); + $reflection_property = $reflection->getProperty('_scopePriorityScheme'); + $reflection_property->setAccessible(true); + $reflection_property->setValue($this->object, $areaCodes); + } } diff --git a/lib/internal/Magento/Framework/Interception/Test/Unit/_files/reader_mock_map.php b/lib/internal/Magento/Framework/Interception/Test/Unit/_files/reader_mock_map.php index 7698210126af7..d542faec95c69 100644 --- a/lib/internal/Magento/Framework/Interception/Test/Unit/_files/reader_mock_map.php +++ b/lib/internal/Magento/Framework/Interception/Test/Unit/_files/reader_mock_map.php @@ -3,19 +3,38 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item; +use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item\Enhanced; +use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemContainer; use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemContainerPlugin\Simple; +use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemPlugin\Advanced; +use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemPlugin\Simple as ItemPluginSimple; +use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\StartingBackslash; use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\StartingBackslash\Plugin; return [ + [ + 'primary', + [ + Item::class => [ + 'plugins' => [ + 'primary_plugin' => [ + 'sortOrder' => 1, + 'instance' => ItemPluginSimple::class, + ], + ], + ] + ], + ], [ 'global', [ - \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item::class => [ + Item::class => [ 'plugins' => [ 'simple_plugin' => [ 'sortOrder' => 10, - 'instance' => - \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemPlugin\Simple::class, + 'instance' => ItemPluginSimple::class, ], ], ] @@ -24,16 +43,15 @@ [ 'backend', [ - \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item::class => [ + Item::class => [ 'plugins' => [ 'advanced_plugin' => [ 'sortOrder' => 5, - 'instance' => - \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemPlugin\Advanced::class, + 'instance' => Advanced::class, ], ], ], - \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemContainer::class => [ + ItemContainer::class => [ 'plugins' => [ 'simple_plugin' => [ 'sortOrder' => 15, @@ -41,7 +59,7 @@ ], ], ], - \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\StartingBackslash::class => [ + StartingBackslash::class => [ 'plugins' => [ 'simple_plugin' => [ 'sortOrder' => 20, @@ -53,14 +71,19 @@ ], [ 'frontend', - [\Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item::class => [ - 'plugins' => ['simple_plugin' => ['disabled' => true]], - ], \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item\Enhanced::class => [ + [ + Item::class => [ + 'plugins' => [ + 'simple_plugin' => [ + 'disabled' => true + ] + ], + ], + Enhanced::class => [ 'plugins' => [ 'advanced_plugin' => [ 'sortOrder' => 5, - 'instance' => - \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemPlugin\Advanced::class, + 'instance' => Advanced::class, ], ], ], From 7e7eb07c48bdaf21c416e8073d8ee8576ce827ee Mon Sep 17 00:00:00 2001 From: Vasya Tsviklinskyi Date: Wed, 27 May 2020 15:09:12 +0300 Subject: [PATCH 11/24] MC-34483: Moving a store view to a different website resets URLs --- .../Model/Category/Plugin/Store/View.php | 99 ++++++++++--------- .../Model/Category/Plugin/Store/ViewTest.php | 4 +- .../Plugin/Cms/Model/Store/View.php | 2 +- .../Plugin/Cms/Model/Store/ViewTest.php | 94 +++++++++++++++++- 4 files changed, 144 insertions(+), 55 deletions(-) diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/View.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/View.php index f8bf00b16cf37..4a445c5b7e14b 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/View.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/View.php @@ -3,14 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\CatalogUrlRewrite\Model\Category\Plugin\Store; use Magento\Catalog\Model\Category; use Magento\Catalog\Model\CategoryFactory; +use Magento\Catalog\Model\Product; use Magento\Catalog\Model\ProductFactory; use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator; use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; use Magento\Framework\Model\AbstractModel; +use Magento\Store\Model\ResourceModel\Store; use Magento\UrlRewrite\Model\UrlPersistInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; @@ -19,32 +23,31 @@ * * @see \Magento\Store\Model\ResourceModel\Store * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - * @package Magento\CatalogUrlRewrite\Model\Category\Plugin\Store */ class View { /** - * @var \Magento\UrlRewrite\Model\UrlPersistInterface + * @var UrlPersistInterface */ protected $urlPersist; /** - * @var \Magento\Catalog\Model\CategoryFactory + * @var CategoryFactory */ protected $categoryFactory; /** - * @var \Magento\Catalog\Model\ProductFactory + * @var ProductFactory */ protected $productFactory; /** - * @var \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator + * @var CategoryUrlRewriteGenerator */ protected $categoryUrlRewriteGenerator; /** - * @var \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator + * @var ProductUrlRewriteGenerator */ protected $productUrlRewriteGenerator; @@ -75,114 +78,116 @@ public function __construct( } /** - * @param \Magento\Store\Model\ResourceModel\Store $object + * Set original store before saving + * + * @param Store $object * @param AbstractModel $store * @return void * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function beforeSave( - \Magento\Store\Model\ResourceModel\Store $object, + Store $object, AbstractModel $store - ) { + ): void { $this->origStore = $store; } /** * Regenerate urls on store after save * - * @param \Magento\Store\Model\ResourceModel\Store $object - * @param \Magento\Store\Model\ResourceModel\Store $store - * @return \Magento\Store\Model\ResourceModel\Store + * @param Store $object + * @param Store $store + * @return Store * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function afterSave( - \Magento\Store\Model\ResourceModel\Store $object, - \Magento\Store\Model\ResourceModel\Store $store - ) { + Store $object, + Store $store + ): Store { if ($this->origStore->isObjectNew() || $this->origStore->dataHasChangedFor('group_id')) { if (!$this->origStore->isObjectNew()) { - $this->urlPersist->deleteByData([UrlRewrite::STORE_ID => $this->origStore->getId()]); + $this->urlPersist->deleteByData([ + UrlRewrite::STORE_ID => $this->origStore->getId(), + UrlRewrite::ENTITY_TYPE => [ + CategoryUrlRewriteGenerator::ENTITY_TYPE, + ProductUrlRewriteGenerator::ENTITY_TYPE, + ], + ]); } $this->urlPersist->replace( - $this->generateCategoryUrls($this->origStore->getRootCategoryId(), $this->origStore->getId()) + $this->generateCategoryUrls((int)$this->origStore->getRootCategoryId(), (int)$this->origStore->getId()) ); $this->urlPersist->replace( - $this->generateProductUrls( - $this->origStore->getWebsiteId(), - $this->origStore->getOrigData('website_id'), - $this->origStore->getId() - ) + $this->generateProductUrls((int)$this->origStore->getId()) ); } + return $store; } /** - * Generate url rewrites for products assigned to website + * Generate url rewrites for products assigned to store * - * @param int $websiteId - * @param int $originWebsiteId * @param int $storeId * @return array */ - protected function generateProductUrls($websiteId, $originWebsiteId, $storeId) + protected function generateProductUrls(int $storeId): array { $urls = []; - $websiteIds = $websiteId != $originWebsiteId && $originWebsiteId !== null - ? [$websiteId, $originWebsiteId] - : [$websiteId]; + $rewrites = []; $collection = $this->productFactory->create() ->getCollection() ->addCategoryIds() ->addAttributeToSelect(['name', 'url_path', 'url_key', 'visibility']) - ->addWebsiteFilter($websiteIds); + ->addStoreFilter($storeId); foreach ($collection as $product) { $product->setStoreId($storeId); - /** @var \Magento\Catalog\Model\Product $product */ - $urls = array_merge( - $urls, - $this->productUrlRewriteGenerator->generate($product) - ); + /** @var Product $product */ + $rewrites[] = $this->productUrlRewriteGenerator->generate($product); } + $urls = array_merge($urls, ...$rewrites); + return $urls; } /** + * Generate url rewrites for categories assigned to store + * * @param int $rootCategoryId * @param int $storeId * @return array */ - protected function generateCategoryUrls($rootCategoryId, $storeId) + protected function generateCategoryUrls(int $rootCategoryId, int $storeId): array { $urls = []; + $rewrites = []; $categories = $this->categoryFactory->create()->getCategories($rootCategoryId, 1, false, true); foreach ($categories as $category) { - /** @var \Magento\Catalog\Model\Category $category */ + /** @var Category $category */ $category->setStoreId($storeId); - $urls = array_merge( - $urls, - $this->categoryUrlRewriteGenerator->generate($category) - ); + $rewrites[] = $this->categoryUrlRewriteGenerator->generate($category); } + $urls = array_merge($urls, ...$rewrites); + return $urls; } /** * Delete unused url rewrites * - * @param \Magento\Store\Model\ResourceModel\Store $subject - * @param \Magento\Store\Model\ResourceModel\Store $result + * @param Store $subject + * @param Store $result * @param AbstractModel $store - * @return \Magento\Store\Model\ResourceModel\Store + * @return Store * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function afterDelete( - \Magento\Store\Model\ResourceModel\Store $subject, - \Magento\Store\Model\ResourceModel\Store $result, + Store $subject, + Store $result, AbstractModel $store - ) { + ): Store { $this->urlPersist->deleteByData([UrlRewrite::STORE_ID => $store->getId()]); return $result; diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/Store/ViewTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/Store/ViewTest.php index 4471ea0e59887..6e861667be77a 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/Store/ViewTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/Store/ViewTest.php @@ -121,7 +121,7 @@ protected function setUp(): void ->getMock(); $this->productCollectionMock = $this->getMockBuilder(ProductCollection::class) ->disableOriginalConstructor() - ->setMethods(['addCategoryIds', 'addAttributeToSelect', 'addWebsiteFilter', 'getIterator']) + ->setMethods(['addCategoryIds', 'addAttributeToSelect', 'getIterator', 'addStoreFilter']) ->getMock(); $this->productMock = $this->getMockBuilder(Product::class) ->disableOriginalConstructor() @@ -174,7 +174,7 @@ public function testAfterSave() ->method('addAttributeToSelect') ->willReturn($this->productCollectionMock); $this->productCollectionMock->expects($this->once()) - ->method('addWebsiteFilter') + ->method('addStoreFilter') ->willReturn($this->productCollectionMock); $iterator = new \ArrayIterator([$this->productMock]); $this->productCollectionMock->expects($this->once()) diff --git a/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php b/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php index e56225cbe2548..257f8ba9bfe53 100644 --- a/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php +++ b/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php @@ -72,7 +72,7 @@ public function __construct( */ public function afterSave(ResourceStore $object, ResourceStore $result, AbstractModel $store): void { - if ($store->isObjectNew() || $store->dataHasChangedFor('group_id')) { + if ($store->isObjectNew()) { $this->urlPersist->replace( $this->generateCmsPagesUrls((int)$store->getId()) ); diff --git a/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php b/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php index a5934dd98e2a6..8aad6049125a8 100644 --- a/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php +++ b/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php @@ -7,19 +7,30 @@ namespace Magento\CmsUrlRewrite\Plugin\Cms\Model\Store; +use Magento\Cms\Api\Data\PageInterface; +use Magento\Cms\Api\PageRepositoryInterface; +use Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator; +use Magento\CmsUrlRewrite\Model\CmsPageUrlRewriteGenerator; +use Magento\Framework\Api\Filter; +use Magento\Framework\Api\Search\FilterGroup; +use Magento\Framework\Api\SearchCriteriaInterface; use Magento\Framework\ObjectManagerInterface; use Magento\Store\Model\Store; use Magento\Store\Model\StoreFactory; use Magento\TestFramework\Helper\Bootstrap; use Magento\UrlRewrite\Model\UrlFinderInterface; +use Magento\UrlRewrite\Model\UrlPersistInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory; +use PHPUnit\Framework\TestCase; /** * Test for plugin which is listening store resource model and on save replace cms page url rewrites * * @magentoAppArea adminhtml + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class ViewTest extends \PHPUnit\Framework\TestCase +class ViewTest extends TestCase { /** * @var UrlFinderInterface @@ -36,6 +47,26 @@ class ViewTest extends \PHPUnit\Framework\TestCase */ private $storeFactory; + /** + * @var UrlPersistInterface + */ + private $urlPersist; + + /** + * @var UrlRewriteFactory + */ + private $urlRewriteFactory; + + /** + * @var PageRepositoryInterface + */ + private $pageRepository; + + /** + * @var CmsPageUrlPathGenerator + */ + private $cmsPageUrlPathGenerator; + /** * @inheritdoc */ @@ -44,6 +75,10 @@ protected function setUp(): void $this->objectManager = Bootstrap::getObjectManager(); $this->urlFinder = $this->objectManager->create(UrlFinderInterface::class); $this->storeFactory = $this->objectManager->create(StoreFactory::class); + $this->urlPersist = $this->objectManager->create(UrlPersistInterface::class); + $this->urlRewriteFactory = $this->objectManager->create(UrlRewriteFactory::class); + $this->pageRepository = $this->objectManager->create(PageRepositoryInterface::class); + $this->cmsPageUrlPathGenerator = $this->objectManager->create(CmsPageUrlPathGenerator::class); } /** @@ -54,21 +89,25 @@ protected function setUp(): void public function testUrlRewritesChangesAfterStoreSave() { $storeId = $this->createStore(); - $this->assertUrlRewritesCount($storeId, 1); + $this->assertUrlRewritesCount($storeId, 'page100', 1); + $this->editUrlRewrite($storeId, 'page100'); + $this->saveStore($storeId); + $this->assertUrlRewritesCount($storeId, 'page100-test', 1); $this->deleteStore($storeId); - $this->assertUrlRewritesCount($storeId, 0); + $this->assertUrlRewritesCount($storeId, 'page100', 0); } /** * Assert url rewrites count by store id * * @param int $storeId + * @param string $pageIdentifier * @param int $expectedCount */ - private function assertUrlRewritesCount(int $storeId, int $expectedCount): void + private function assertUrlRewritesCount(int $storeId, string $pageIdentifier, int $expectedCount): void { $data = [ - UrlRewrite::REQUEST_PATH => 'page100', + UrlRewrite::REQUEST_PATH => $pageIdentifier, UrlRewrite::STORE_ID => $storeId ]; $urlRewrites = $this->urlFinder->findAllByData($data); @@ -105,4 +144,49 @@ private function deleteStore(int $storeId): void $store->delete(); } } + + /** + * Edit url rewrite + * + * @param int $storeId + * @param string $pageIdentifier + */ + private function editUrlRewrite(int $storeId, string $pageIdentifier): void + { + $filter = $this->objectManager->create(Filter::class); + $filter->setField('identifier')->setValue($pageIdentifier); + $filterGroup = $this->objectManager->create(FilterGroup::class); + $filterGroup->setFilters([$filter]); + $searchCriteria = $this->objectManager->create(SearchCriteriaInterface::class); + $searchCriteria->setFilterGroups([$filterGroup]); + $pageSearchResults = $this->pageRepository->getList($searchCriteria); + $pages = $pageSearchResults->getItems(); + /** @var PageInterface $page */ + $cmsPage = array_values($pages)[0]; + + $urlRewrite = $this->urlRewriteFactory->create()->setStoreId($storeId) + ->setEntityType(CmsPageUrlRewriteGenerator::ENTITY_TYPE) + ->setEntityId($cmsPage->getId()) + ->setRequestPath($cmsPage->getIdentifier() . '-test') + ->setTargetPath($this->cmsPageUrlPathGenerator->getCanonicalUrlPath($cmsPage)) + ->setIsAutogenerated(0) + ->setRedirectType(0); + + $this->urlPersist->replace([$urlRewrite]); + } + + /** + * Edit test store + * + * @param int $storeId + * @return void + */ + private function saveStore(int $storeId): void + { + $store = $this->storeFactory->create(); + $store->load($storeId); + if ($store !== null) { + $store->save(); + } + } } From 6b35c2ebf687ef6c7910f170f486b44ff0f6826e Mon Sep 17 00:00:00 2001 From: "rostyslav.hymon" Date: Mon, 1 Jun 2020 09:37:41 +0300 Subject: [PATCH 12/24] MC-25062: 1213 Deadlock found when trying to get lock --- .../Model/ResourceModel/Indexer/Price.php | 20 ++-- .../Indexer/Product/Price/AbstractAction.php | 8 +- .../Indexer/Product/Price/Action/Full.php | 2 +- .../Indexer/Product/Price/ModeSwitcher.php | 13 +-- .../Indexer/Product/Price/TableMaintainer.php | 91 +++++-------------- .../Indexer/Price/SimpleProductPrice.php | 5 +- .../Model/Plugin/PriceIndexUpdater.php | 79 ---------------- .../Model/ResourceModel/Stock/Item.php | 46 +++++++--- app/code/Magento/CatalogInventory/etc/di.xml | 3 - .../Product/Indexer/Price/Configurable.php | 8 +- .../Model/ResourceModel/Indexer/Price.php | 13 +-- .../Product/Indexer/Price/Grouped.php | 15 ++- .../Model/ResourceModel/AbstractResource.php | 7 +- 13 files changed, 95 insertions(+), 215 deletions(-) delete mode 100644 app/code/Magento/CatalogInventory/Model/Plugin/PriceIndexUpdater.php diff --git a/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price.php b/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price.php index a2fff5739f2f9..96d68d7e74117 100644 --- a/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price.php +++ b/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price.php @@ -377,8 +377,7 @@ private function prepareBundlePriceByType($priceType, array $dimensions, $entity ] ); - $query = $select->insertFromSelect($this->getBundlePriceTable()); - $connection->query($query); + $this->tableMaintainer->insertFromSelect($select, $this->getBundlePriceTable(), []); } /** @@ -418,8 +417,7 @@ private function calculateBundleOptionPrice($priceTable, $dimensions) ] ); - $query = $select->insertFromSelect($this->getBundleOptionTable()); - $connection->query($query); + $this->tableMaintainer->insertFromSelect($select, $this->getBundleOptionTable(), []); $this->getConnection()->delete($priceTable->getTableName()); $this->applyBundlePrice($priceTable); @@ -575,8 +573,7 @@ private function calculateFixedBundleSelectionPrice() 'tier_price' => $tierExpr, ] ); - $query = $select->insertFromSelect($this->getBundleSelectionTable()); - $connection->query($query); + $this->tableMaintainer->insertFromSelect($select, $this->getBundleSelectionTable(), []); $this->applyFixedBundleSelectionPrice(); } @@ -627,8 +624,7 @@ private function calculateDynamicBundleSelectionPrice($dimensions) 'tier_price' => $tierExpr, ] ); - $query = $select->insertFromSelect($this->getBundleSelectionTable()); - $connection->query($query); + $this->tableMaintainer->insertFromSelect($select, $this->getBundleSelectionTable(), []); } /** @@ -697,8 +693,7 @@ private function prepareTierPriceIndex($dimensions, $entityIds) $select->where($this->dimensionToFieldMapper[$dimension->getName()] . ' = ?', $dimension->getValue()); } - $query = $select->insertFromSelect($this->getTable('catalog_product_index_tier_price')); - $connection->query($query); + $this->tableMaintainer->insertFromSelect($select, $this->getTable('catalog_product_index_tier_price'), []); } /** @@ -725,8 +720,7 @@ private function applyBundlePrice($priceTable): void ] ); - $query = $select->insertFromSelect($priceTable->getTableName()); - $this->getConnection()->query($query); + $this->tableMaintainer->insertFromSelect($select, $priceTable->getTableName(), []); } /** @@ -785,7 +779,7 @@ private function getMainTable($dimensions) if ($this->fullReindexAction) { return $this->tableMaintainer->getMainReplicaTable($dimensions); } - return $this->tableMaintainer->getMainTable($dimensions); + return $this->tableMaintainer->getMainTableByDimensions($dimensions); } /** diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php index e9a907f0b5097..e24b0c46f5503 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php @@ -173,7 +173,7 @@ protected function _syncData(array $processIds = []) } } - $query = $insertSelect->insertFromSelect($this->tableMaintainer->getMainTable($dimensions)); + $query = $insertSelect->insertFromSelect($this->tableMaintainer->getMainTableByDimensions($dimensions)); $this->getConnection()->query($query); } return $this; @@ -385,7 +385,7 @@ protected function _reindexRows($changedIds = []) // copy to index $this->_insertFromTable( $temporaryTable, - $this->tableMaintainer->getMainTable($dimensions) + $this->tableMaintainer->getMainTableByDimensions($dimensions) ); } } else { @@ -408,7 +408,7 @@ private function deleteIndexData(array $entityIds) { foreach ($this->dimensionCollectionFactory->create() as $dimensions) { $select = $this->getConnection()->select()->from( - ['index_price' => $this->tableMaintainer->getMainTable($dimensions)], + ['index_price' => $this->tableMaintainer->getMainTableByDimensions($dimensions)], null )->where('index_price.entity_id IN (?)', $entityIds); $query = $select->deleteFromSelect('index_price'); @@ -476,7 +476,7 @@ private function getIndexTargetTableByDimension(array $dimensions) { $indexTargetTable = $this->getIndexTargetTable(); if ($indexTargetTable === self::getIndexTargetTable()) { - $indexTargetTable = $this->tableMaintainer->getMainTable($dimensions); + $indexTargetTable = $this->tableMaintainer->getMainTableByDimensions($dimensions); } if ($indexTargetTable === self::getIndexTargetTable() . '_replica') { $indexTargetTable = $this->tableMaintainer->getMainReplicaTable($dimensions); diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php index b30c85cfc52f0..390c9784b50de 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php @@ -425,7 +425,7 @@ private function switchTables(): void $mainTablesByDimension = []; foreach ($this->dimensionCollectionFactory->create() as $dimensions) { - $mainTablesByDimension[] = $this->dimensionTableMaintainer->getMainTable($dimensions); + $mainTablesByDimension[] = $this->dimensionTableMaintainer->getMainTableByDimensions($dimensions); //Move data from indexers with old realisation $this->moveDataFromReplicaTableToReplicaTables($dimensions); diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcher.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcher.php index c418f2e1f253b..974bd0e8f7860 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcher.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcher.php @@ -15,6 +15,8 @@ /** * Class to prepare new tables for new indexer mode + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ModeSwitcher implements \Magento\Indexer\Model\ModeSwitcherInterface { @@ -98,7 +100,6 @@ public function switchMode(string $currentMode, string $previousMode) * Create new tables * * @param string $currentMode - * * @return void * @throws \Zend_Db_Exception */ @@ -116,7 +117,6 @@ public function createTables(string $currentMode) * * @param string $currentMode * @param string $previousMode - * * @return void */ public function moveData(string $currentMode, string $previousMode) @@ -125,17 +125,17 @@ public function moveData(string $currentMode, string $previousMode) $dimensionsArrayForPreviousMode = $this->getDimensionsArray($previousMode); foreach ($dimensionsArrayForCurrentMode as $dimensionsForCurrentMode) { - $newTable = $this->tableMaintainer->getMainTable($dimensionsForCurrentMode); + $newTable = $this->tableMaintainer->getMainTableByDimensions($dimensionsForCurrentMode); if (empty($dimensionsForCurrentMode)) { // new mode is 'none' foreach ($dimensionsArrayForPreviousMode as $dimensionsForPreviousMode) { - $oldTable = $this->tableMaintainer->getMainTable($dimensionsForPreviousMode); + $oldTable = $this->tableMaintainer->getMainTableByDimensions($dimensionsForPreviousMode); $this->insertFromOldTablesToNew($newTable, $oldTable); } } else { // new mode is not 'none' foreach ($dimensionsArrayForPreviousMode as $dimensionsForPreviousMode) { - $oldTable = $this->tableMaintainer->getMainTable($dimensionsForPreviousMode); + $oldTable = $this->tableMaintainer->getMainTableByDimensions($dimensionsForPreviousMode); $this->insertFromOldTablesToNew($newTable, $oldTable, $dimensionsForCurrentMode); } } @@ -146,7 +146,6 @@ public function moveData(string $currentMode, string $previousMode) * Drop old tables * * @param string $previousMode - * * @return void */ public function dropTables(string $previousMode) @@ -164,7 +163,6 @@ public function dropTables(string $previousMode) * Get dimensions array * * @param string $mode - * * @return \Magento\Framework\Indexer\MultiDimensionProvider */ private function getDimensionsArray(string $mode): \Magento\Framework\Indexer\MultiDimensionProvider @@ -184,7 +182,6 @@ private function getDimensionsArray(string $mode): \Magento\Framework\Indexer\Mu * @param string $newTable * @param string $oldTable * @param Dimension[] $dimensions - * * @return void */ private function insertFromOldTablesToNew(string $newTable, string $oldTable, array $dimensions = []) diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/TableMaintainer.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/TableMaintainer.php index e3077baaeb7a6..5eed262352c76 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/TableMaintainer.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/TableMaintainer.php @@ -7,36 +7,27 @@ namespace Magento\Catalog\Model\Indexer\Product\Price; -use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Indexer\Table\StrategyInterface; +use Magento\Framework\Model\ResourceModel\Db\Context as DbContext; use Magento\Framework\Search\Request\Dimension; -use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\Search\Request\IndexScopeResolverInterface as TableResolver; +use Magento\Indexer\Model\ResourceModel\AbstractResource as AbstractIndexerResource; /** * Class encapsulate logic of work with tables per store in Product Price indexer */ -class TableMaintainer +class TableMaintainer extends AbstractIndexerResource { /** * Catalog product price index table name */ const MAIN_INDEX_TABLE = 'catalog_product_index_price'; - /** - * @var ResourceConnection - */ - private $resource; - /** * @var TableResolver */ private $tableResolver; - /** - * @var AdapterInterface - */ - private $connection; - /** * Catalog tmp category index table name */ @@ -53,47 +44,27 @@ class TableMaintainer private $mainTmpTable; /** - * @var null|string - */ - private $connectionName; - - /** - * @param ResourceConnection $resource + * @param DbContext $context + * @param StrategyInterface $tableStrategy * @param TableResolver $tableResolver - * @param null $connectionName + * @param string|null $connectionName */ public function __construct( - ResourceConnection $resource, + DbContext $context, + StrategyInterface $tableStrategy, TableResolver $tableResolver, $connectionName = null ) { - $this->resource = $resource; + parent::__construct($context, $tableStrategy, $connectionName); $this->tableResolver = $tableResolver; - $this->connectionName = $connectionName; - } - - /** - * Get connection for work with price indexer - * - * @return AdapterInterface - */ - public function getConnection(): AdapterInterface - { - if (null === $this->connection) { - $this->connection = $this->resource->getConnection($this->connectionName); - } - return $this->connection; } /** - * Return validated table name - * - * @param string $table - * @return string + * @inheritDoc */ - private function getTable(string $table): string + protected function _construct() { - return $this->resource->getTableName($table); + $this->_init(self::MAIN_INDEX_TABLE, 'entity_id'); } /** @@ -101,9 +72,7 @@ private function getTable(string $table): string * * @param string $mainTableName * @param string $newTableName - * * @return void - * * @throws \Zend_Db_Exception */ private function createTable(string $mainTableName, string $newTableName) @@ -119,7 +88,6 @@ private function createTable(string $mainTableName, string $newTableName) * Drop table * * @param string $tableName - * * @return void */ private function dropTable(string $tableName) @@ -133,7 +101,6 @@ private function dropTable(string $tableName) * Truncate table * * @param string $tableName - * * @return void */ private function truncateTable(string $tableName) @@ -147,7 +114,6 @@ private function truncateTable(string $tableName) * Get array key for tmp table * * @param Dimension[] $dimensions - * * @return string */ private function getArrayKeyForTmpTable(array $dimensions): string @@ -160,13 +126,12 @@ private function getArrayKeyForTmpTable(array $dimensions): string } /** - * Return main index table name + * Return main index table name using dimensions * * @param Dimension[] $dimensions - * * @return string */ - public function getMainTable(array $dimensions): string + public function getMainTableByDimensions(array $dimensions): string { return $this->tableResolver->resolve(self::MAIN_INDEX_TABLE, $dimensions); } @@ -175,14 +140,12 @@ public function getMainTable(array $dimensions): string * Create main and replica index tables for dimensions * * @param Dimension[] $dimensions - * * @return void - * * @throws \Zend_Db_Exception */ public function createTablesForDimensions(array $dimensions) { - $mainTableName = $this->getMainTable($dimensions); + $mainTableName = $this->getMainTableByDimensions($dimensions); //Create index table for dimensions based on main replica table //Using main replica table is necessary for backward capability and TableResolver plugin work $this->createTable( @@ -190,7 +153,7 @@ public function createTablesForDimensions(array $dimensions) $mainTableName ); - $mainReplicaTableName = $this->getMainTable($dimensions) . $this->additionalTableSuffix; + $mainReplicaTableName = $this->getMainTableByDimensions($dimensions) . $this->additionalTableSuffix; //Create replica table for dimensions based on main replica table $this->createTable( $this->getTable(self::MAIN_INDEX_TABLE . $this->additionalTableSuffix), @@ -202,15 +165,14 @@ public function createTablesForDimensions(array $dimensions) * Drop main and replica index tables for dimensions * * @param Dimension[] $dimensions - * * @return void */ public function dropTablesForDimensions(array $dimensions) { - $mainTableName = $this->getMainTable($dimensions); + $mainTableName = $this->getMainTableByDimensions($dimensions); $this->dropTable($mainTableName); - $mainReplicaTableName = $this->getMainTable($dimensions) . $this->additionalTableSuffix; + $mainReplicaTableName = $this->getMainTableByDimensions($dimensions) . $this->additionalTableSuffix; $this->dropTable($mainReplicaTableName); } @@ -218,15 +180,14 @@ public function dropTablesForDimensions(array $dimensions) * Truncate main and replica index tables for dimensions * * @param Dimension[] $dimensions - * * @return void */ public function truncateTablesForDimensions(array $dimensions) { - $mainTableName = $this->getMainTable($dimensions); + $mainTableName = $this->getMainTableByDimensions($dimensions); $this->truncateTable($mainTableName); - $mainReplicaTableName = $this->getMainTable($dimensions) . $this->additionalTableSuffix; + $mainReplicaTableName = $this->getMainTableByDimensions($dimensions) . $this->additionalTableSuffix; $this->truncateTable($mainReplicaTableName); } @@ -234,26 +195,24 @@ public function truncateTablesForDimensions(array $dimensions) * Return replica index table name * * @param Dimension[] $dimensions - * * @return string */ public function getMainReplicaTable(array $dimensions): string { - return $this->getMainTable($dimensions) . $this->additionalTableSuffix; + return $this->getMainTableByDimensions($dimensions) . $this->additionalTableSuffix; } /** * Create temporary index table for dimensions * * @param Dimension[] $dimensions - * * @return void */ public function createMainTmpTable(array $dimensions) { // Create temporary table based on template table catalog_product_index_price_tmp without indexes - $templateTableName = $this->resource->getTableName(self::MAIN_INDEX_TABLE . '_tmp'); - $temporaryTableName = $this->getMainTable($dimensions) . $this->tmpTableSuffix; + $templateTableName = $this->_resources->getTableName(self::MAIN_INDEX_TABLE . '_tmp'); + $temporaryTableName = $this->getMainTableByDimensions($dimensions) . $this->tmpTableSuffix; $this->getConnection()->createTemporaryTableLike($temporaryTableName, $templateTableName, true); $this->mainTmpTable[$this->getArrayKeyForTmpTable($dimensions)] = $temporaryTableName; } @@ -262,9 +221,7 @@ public function createMainTmpTable(array $dimensions) * Return temporary index table name * * @param Dimension[] $dimensions - * * @return string - * * @throws \LogicException */ public function getMainTmpTable(array $dimensions): string diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/SimpleProductPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/SimpleProductPrice.php index 5a055e5ed9603..47365929e159e 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/SimpleProductPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/SimpleProductPrice.php @@ -62,7 +62,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritDoc */ public function executeByDimensions(array $dimensions, \Traversable $entityIds) { @@ -81,8 +81,7 @@ public function executeByDimensions(array $dimensions, \Traversable $entityIds) 'tierPriceField' => 'tier_price', ]); $select = $this->baseFinalPrice->getQuery($dimensions, $this->productType, iterator_to_array($entityIds)); - $query = $select->insertFromSelect($temporaryPriceTable->getTableName(), [], false); - $this->tableMaintainer->getConnection()->query($query); + $this->tableMaintainer->insertFromSelect($select, $temporaryPriceTable->getTableName(), []); $this->basePriceModifier->modifyPrice($temporaryPriceTable, iterator_to_array($entityIds)); } diff --git a/app/code/Magento/CatalogInventory/Model/Plugin/PriceIndexUpdater.php b/app/code/Magento/CatalogInventory/Model/Plugin/PriceIndexUpdater.php deleted file mode 100644 index c061c459bfb49..0000000000000 --- a/app/code/Magento/CatalogInventory/Model/Plugin/PriceIndexUpdater.php +++ /dev/null @@ -1,79 +0,0 @@ -priceIndexProcessor = $priceIndexProcessor; - } - - /** - * @param Item $subject - * @param Item $result - * @param AbstractModel $model - * @return Item - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function afterSave(Item $subject, Item $result, AbstractModel $model): Item - { - $fields = [ - 'is_in_stock', - 'use_config_manage_stock', - 'manage_stock', - ]; - foreach ($fields as $field) { - if ($model->dataHasChangedFor($field)) { - $this->priceIndexProcessor->reindexRow($model->getProductId()); - break; - } - } - - return $result; - } - - /** - * @param Item $subject - * @param mixed $result - * @param int $websiteId - * @return void - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function afterUpdateSetOutOfStock(Item $subject, $result, int $websiteId) - { - $this->priceIndexProcessor->markIndexerAsInvalid(); - } - - /** - * @param Item $subject - * @param mixed $result - * @param int $websiteId - * @return void - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function afterUpdateSetInStock(Item $subject, $result, int $websiteId) - { - $this->priceIndexProcessor->markIndexerAsInvalid(); - } -} diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Item.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Item.php index edccad60231ec..dc9233e77d3a9 100644 --- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Item.php +++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Item.php @@ -5,14 +5,13 @@ */ namespace Magento\CatalogInventory\Model\ResourceModel\Stock; -use Magento\CatalogInventory\Api\Data\StockItemInterface; +use Magento\Catalog\Model\Indexer\Product\Price\Processor as PriceIndexProcessor; use Magento\CatalogInventory\Api\StockConfigurationInterface; use Magento\CatalogInventory\Model\Stock; use Magento\CatalogInventory\Model\Indexer\Stock\Processor; use Magento\Framework\Model\AbstractModel; use Magento\Framework\Model\ResourceModel\Db\Context; use Magento\Framework\DB\Select; -use Magento\Framework\App\ObjectManager; use Magento\Framework\Stdlib\DateTime\DateTime; /** @@ -42,27 +41,33 @@ class Item extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb */ private $dateTime; + /** + * @var PriceIndexProcessor + */ + private $priceIndexProcessor; + /** * @param Context $context * @param Processor $processor - * @param string $connectionName * @param StockConfigurationInterface $stockConfiguration * @param DateTime $dateTime + * @param PriceIndexProcessor $priceIndexProcessor + * @param string $connectionName */ public function __construct( Context $context, Processor $processor, - $connectionName = null, - StockConfigurationInterface $stockConfiguration = null, - DateTime $dateTime = null + StockConfigurationInterface $stockConfiguration, + DateTime $dateTime, + PriceIndexProcessor $priceIndexProcessor, + $connectionName = null ) { $this->stockIndexerProcessor = $processor; parent::__construct($context, $connectionName); - $this->stockConfiguration = $stockConfiguration ?? - ObjectManager::getInstance()->get(StockConfigurationInterface::class); - $this->dateTime = $dateTime ?? - ObjectManager::getInstance()->get(DateTime::class); + $this->stockConfiguration = $stockConfiguration; + $this->dateTime = $dateTime; + $this->priceIndexProcessor = $priceIndexProcessor; } /** @@ -144,10 +149,25 @@ protected function _prepareDataForTable(\Magento\Framework\DataObject $object, $ protected function _afterSave(AbstractModel $object) { parent::_afterSave($object); - /** @var StockItemInterface $object */ + + $productId = $object->getProductId(); if ($this->processIndexEvents) { - $this->stockIndexerProcessor->reindexRow($object->getProductId()); + $this->stockIndexerProcessor->reindexRow($productId); } + $fields = [ + 'is_in_stock', + 'use_config_manage_stock', + 'manage_stock', + ]; + foreach ($fields as $field) { + if ($object->dataHasChangedFor($field)) { + $this->addCommitCallback(function () use ($productId) { + $this->priceIndexProcessor->reindexRow($productId); + }); + break; + } + } + return $this; } @@ -196,6 +216,7 @@ public function updateSetOutOfStock(int $websiteId) $connection->update($this->getMainTable(), $values, $where); $this->stockIndexerProcessor->markIndexerAsInvalid(); + $this->priceIndexProcessor->markIndexerAsInvalid(); } /** @@ -228,6 +249,7 @@ public function updateSetInStock(int $websiteId) $connection->update($this->getMainTable(), $values, $where); $this->stockIndexerProcessor->markIndexerAsInvalid(); + $this->priceIndexProcessor->markIndexerAsInvalid(); } /** diff --git a/app/code/Magento/CatalogInventory/etc/di.xml b/app/code/Magento/CatalogInventory/etc/di.xml index 78a0c2b734315..b050c6ae3b6ca 100644 --- a/app/code/Magento/CatalogInventory/etc/di.xml +++ b/app/code/Magento/CatalogInventory/etc/di.xml @@ -129,9 +129,6 @@ - - - diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php index b7bbf7aa1871c..9080314126ee1 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php @@ -134,8 +134,7 @@ public function executeByDimensions(array $dimensions, \Traversable $entityIds) \Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE, iterator_to_array($entityIds) ); - $query = $select->insertFromSelect($temporaryPriceTable->getTableName(), [], false); - $this->tableMaintainer->getConnection()->query($query); + $this->tableMaintainer->insertFromSelect($select, $temporaryPriceTable->getTableName(), []); $this->basePriceModifier->modifyPrice($temporaryPriceTable, iterator_to_array($entityIds)); $this->applyConfigurableOption($temporaryPriceTable, $dimensions, iterator_to_array($entityIds)); @@ -224,8 +223,7 @@ private function fillTemporaryOptionsTable(string $temporaryOptionsTableName, ar if ($entityIds !== null) { $select->where('le.entity_id IN (?)', $entityIds); } - $query = $select->insertFromSelect($temporaryOptionsTableName); - $this->getConnection()->query($query); + $this->tableMaintainer->insertFromSelect($select, $temporaryOptionsTableName, []); } /** @@ -269,7 +267,7 @@ private function getMainTable($dimensions) if ($this->fullReindexAction) { return $this->tableMaintainer->getMainReplicaTable($dimensions); } - return $this->tableMaintainer->getMainTable($dimensions); + return $this->tableMaintainer->getMainTableByDimensions($dimensions); } /** diff --git a/app/code/Magento/Downloadable/Model/ResourceModel/Indexer/Price.php b/app/code/Magento/Downloadable/Model/ResourceModel/Indexer/Price.php index 90b458ff6348e..e50213f139ba1 100644 --- a/app/code/Magento/Downloadable/Model/ResourceModel/Indexer/Price.php +++ b/app/code/Magento/Downloadable/Model/ResourceModel/Indexer/Price.php @@ -100,10 +100,7 @@ public function __construct( } /** - * {@inheritdoc} - * @param array $dimensions - * @param \Traversable $entityIds - * @throws \Exception + * @inheritDoc */ public function executeByDimensions(array $dimensions, \Traversable $entityIds) { @@ -205,8 +202,7 @@ private function fillTemporaryTable(string $temporaryDownloadableTableName, arra 'max_price' => new \Zend_Db_Expr('SUM(' . $ifPrice . ')'), ] ); - $query = $select->insertFromSelect($temporaryDownloadableTableName); - $this->getConnection()->query($query); + $this->tableMaintainer->insertFromSelect($select, $temporaryDownloadableTableName, []); } /** @@ -259,14 +255,13 @@ private function fillFinalPrice( IndexTableStructure $temporaryPriceTable ) { $select = $this->baseFinalPrice->getQuery($dimensions, Type::TYPE_DOWNLOADABLE, iterator_to_array($entityIds)); - $query = $select->insertFromSelect($temporaryPriceTable->getTableName(), [], false); - $this->tableMaintainer->getConnection()->query($query); + $this->tableMaintainer->insertFromSelect($select, $temporaryPriceTable->getTableName(), []); } /** * Get connection * - * return \Magento\Framework\DB\Adapter\AdapterInterface + * @return \Magento\Framework\DB\Adapter\AdapterInterface * @throws \DomainException */ private function getConnection(): \Magento\Framework\DB\Adapter\AdapterInterface diff --git a/app/code/Magento/GroupedProduct/Model/ResourceModel/Product/Indexer/Price/Grouped.php b/app/code/Magento/GroupedProduct/Model/ResourceModel/Product/Indexer/Price/Grouped.php index e1599dc772c2c..c5f0316feb126 100644 --- a/app/code/Magento/GroupedProduct/Model/ResourceModel/Product/Indexer/Price/Grouped.php +++ b/app/code/Magento/GroupedProduct/Model/ResourceModel/Product/Indexer/Price/Grouped.php @@ -20,6 +20,7 @@ /** * Calculate minimal and maximal prices for Grouped products + * * Use calculated price for relation products * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -85,10 +86,7 @@ public function __construct( } /** - * {@inheritdoc} - * @param array $dimensions - * @param \Traversable $entityIds - * @throws \Exception + * @inheritDoc */ public function executeByDimensions(array $dimensions, \Traversable $entityIds) { @@ -105,9 +103,8 @@ public function executeByDimensions(array $dimensions, \Traversable $entityIds) 'maxPriceField' => 'max_price', 'tierPriceField' => 'tier_price', ]); - $query = $this->prepareGroupedProductPriceDataSelect($dimensions, iterator_to_array($entityIds)) - ->insertFromSelect($temporaryPriceTable->getTableName()); - $this->getConnection()->query($query); + $select = $this->prepareGroupedProductPriceDataSelect($dimensions, iterator_to_array($entityIds)); + $this->tableMaintainer->insertFromSelect($select, $temporaryPriceTable->getTableName(), []); } /** @@ -186,13 +183,13 @@ private function getMainTable($dimensions) if ($this->fullReindexAction) { return $this->tableMaintainer->getMainReplicaTable($dimensions); } - return $this->tableMaintainer->getMainTable($dimensions); + return $this->tableMaintainer->getMainTableByDimensions($dimensions); } /** * Get connection * - * return \Magento\Framework\DB\Adapter\AdapterInterface + * @return \Magento\Framework\DB\Adapter\AdapterInterface * @throws \DomainException */ private function getConnection(): \Magento\Framework\DB\Adapter\AdapterInterface diff --git a/app/code/Magento/Indexer/Model/ResourceModel/AbstractResource.php b/app/code/Magento/Indexer/Model/ResourceModel/AbstractResource.php index 7032c9258cae6..d86b15134283b 100644 --- a/app/code/Magento/Indexer/Model/ResourceModel/AbstractResource.php +++ b/app/code/Magento/Indexer/Model/ResourceModel/AbstractResource.php @@ -11,6 +11,7 @@ /** * Abstract resource model. Can be used as base for indexer resources * + * phpcs:disable Magento2.Classes.AbstractApi * @api * @since 100.0.2 */ @@ -120,8 +121,7 @@ public function insertFromTable($sourceTable, $destTable, $readToIndex = true) } /** - * Insert data from select statement of read adapter to - * destination table related with index adapter + * Insert data from select statement of read adapter to destination table related with index adapter * * @param Select $select * @param string $destTable @@ -141,6 +141,9 @@ public function insertFromSelect($select, $destTable, array $columns, $readToInd if ($from === $to) { $query = $select->insertFromSelect($destTable, $columns); + if ($to->getTransactionLevel() === 0) { + $to->query('SET TRANSACTION ISOLATION LEVEL READ COMMITTED;'); + } $to->query($query); } else { $stmt = $from->query($select); From d9eb1f471cedb0dd85e955208c632ac843a606d2 Mon Sep 17 00:00:00 2001 From: Vasya Tsviklinskyi Date: Mon, 1 Jun 2020 13:59:19 +0300 Subject: [PATCH 13/24] MC-34152: [Magento Cloud] Unable to cancel PayPal Payflow order / Declined: 10601-Authorization has expired. --- app/code/Magento/Paypal/Model/Payflowpro.php | 48 ++-- .../Paypal/Model/Payflow/TransparentTest.php | 6 +- .../Paypal/Model/PayflowproVoidTest.php | 266 ++++++++++++++++++ .../Magento/Paypal/Model/VoidTest.php | 102 ------- 4 files changed, 300 insertions(+), 122 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Paypal/Model/PayflowproVoidTest.php delete mode 100644 dev/tests/integration/testsuite/Magento/Paypal/Model/VoidTest.php diff --git a/app/code/Magento/Paypal/Model/Payflowpro.php b/app/code/Magento/Paypal/Model/Payflowpro.php index 778cd0c728de3..f8d17c5a5fdfe 100644 --- a/app/code/Magento/Paypal/Model/Payflowpro.php +++ b/app/code/Magento/Paypal/Model/Payflowpro.php @@ -8,6 +8,8 @@ use Magento\Framework\DataObject; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\State\InvalidTransitionException; +use Magento\Payment\Gateway\Command\CommandException; use Magento\Payment\Helper\Formatter; use Magento\Payment\Model\InfoInterface; use Magento\Payment\Model\Method\ConfigInterface; @@ -85,6 +87,8 @@ class Payflowpro extends \Magento\Payment\Model\Method\Cc implements GatewayInte const RESPONSE_CODE_VOID_ERROR = 108; + private const RESPONSE_CODE_AUTHORIZATION_EXPIRED = 10601; + const PNREF = 'pnref'; /**#@-*/ @@ -376,7 +380,7 @@ public function getConfigPaymentAction() * @param float $amount * @return $this * @throws \Magento\Framework\Exception\LocalizedException - * @throws \Magento\Framework\Exception\State\InvalidTransitionException + * @throws InvalidTransitionException */ public function authorize(\Magento\Payment\Model\InfoInterface $payment, $amount) { @@ -410,7 +414,7 @@ protected function _getCaptureAmount($amount) * @param float $amount * @return $this * @throws \Magento\Framework\Exception\LocalizedException - * @throws \Magento\Framework\Exception\State\InvalidTransitionException + * @throws InvalidTransitionException */ public function capture(\Magento\Payment\Model\InfoInterface $payment, $amount) { @@ -448,7 +452,7 @@ public function capture(\Magento\Payment\Model\InfoInterface $payment, $amount) * @param InfoInterface|Payment|Object $payment * @return $this * @throws \Magento\Framework\Exception\LocalizedException - * @throws \Magento\Framework\Exception\State\InvalidTransitionException + * @throws InvalidTransitionException */ public function void(\Magento\Payment\Model\InfoInterface $payment) { @@ -491,14 +495,23 @@ public function canVoid() * * @param InfoInterface|Object $payment * @return $this + * @throws CommandException */ public function cancel(\Magento\Payment\Model\InfoInterface $payment) { if (!$payment->getOrder()->getInvoiceCollection()->count()) { - return $this->void($payment); + try { + $this->void($payment); + } catch (CommandException $e) { + // Ignore error about expiration of authorization transaction. + if (strpos($e->getMessage(), (string)self::RESPONSE_CODE_AUTHORIZATION_EXPIRED) === false) { + throw $e; + } + } + } - return false; + return $this; } /** @@ -508,7 +521,7 @@ public function cancel(\Magento\Payment\Model\InfoInterface $payment) * @param float $amount * @return $this * @throws \Magento\Framework\Exception\LocalizedException - * @throws \Magento\Framework\Exception\State\InvalidTransitionException + * @throws InvalidTransitionException */ public function refund(\Magento\Payment\Model\InfoInterface $payment, $amount) { @@ -558,7 +571,7 @@ public function fetchTransactionInfo(InfoInterface $payment, $transactionId) * @return bool * phpcs:disable Magento2.Functions.StaticFunction */ - protected static function _isTransactionUnderReview($status) + protected function _isTransactionUnderReview($status) { if (in_array($status, [self::RESPONSE_CODE_APPROVED, self::RESPONSE_CODE_DECLINED_BY_MERCHANT])) { return false; @@ -650,21 +663,22 @@ public function buildBasicRequest() * * @param DataObject $response * @return void - * @throws \Magento\Payment\Gateway\Command\CommandException - * @throws \Magento\Framework\Exception\State\InvalidTransitionException + * @throws CommandException + * @throws InvalidTransitionException */ public function processErrors(DataObject $response) { - if ($response->getResultCode() == self::RESPONSE_CODE_VOID_ERROR) { - throw new \Magento\Framework\Exception\State\InvalidTransitionException( + $resultCode = (int)$response->getResultCode(); + if ($resultCode === self::RESPONSE_CODE_VOID_ERROR) { + throw new InvalidTransitionException( __("The verification transaction can't be voided. ") ); - } elseif ($response->getResultCode() != self::RESPONSE_CODE_APPROVED && - $response->getResultCode() != self::RESPONSE_CODE_FRAUDSERVICE_FILTER - ) { - throw new \Magento\Payment\Gateway\Command\CommandException(__($response->getRespmsg())); - } elseif ($response->getOrigresult() == self::RESPONSE_CODE_DECLINED_BY_FILTER) { - throw new \Magento\Payment\Gateway\Command\CommandException(__($response->getRespmsg())); + } + if (!in_array($resultCode, [self::RESPONSE_CODE_APPROVED, self::RESPONSE_CODE_FRAUDSERVICE_FILTER])) { + throw new CommandException(__($response->getRespmsg())); + } + if ((int)$response->getOrigresult() === self::RESPONSE_CODE_DECLINED_BY_FILTER) { + throw new CommandException(__($response->getRespmsg())); } } diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Payflow/TransparentTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Model/Payflow/TransparentTest.php index eb0976a696300..3f24aaaa8686e 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/Payflow/TransparentTest.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/Payflow/TransparentTest.php @@ -47,14 +47,14 @@ protected function setUp(): void } /** - * Checks a case when order should be placed in "Suspected Fraud" status based on after account verification. + * Checks a case when order should be placed in "Suspected Fraud" status based on account verification. * * @magentoDataFixture Magento/Checkout/_files/quote_with_shipping_method.php * @magentoConfigFixture current_store payment/payflowpro/active 1 * @magentoConfigFixture current_store payment/payflowpro/payment_action Authorization * @magentoConfigFixture current_store payment/payflowpro/fmf 1 */ - public function testPlaceOrderSuspectedFraud() + public function testPlaceOrderSuspectedFraud(): void { $quote = $this->getQuote('test_order_1'); $this->addFraudPayment($quote); @@ -114,7 +114,7 @@ private function getQuote(string $reservedOrderId): CartInterface * * @return void */ - private function addFraudPayment(CartInterface $quote) + private function addFraudPayment(CartInterface $quote): void { $payment = $quote->getPayment(); $payment->setMethod(Config::METHOD_PAYFLOWPRO); diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/PayflowproVoidTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Model/PayflowproVoidTest.php new file mode 100644 index 0000000000000..3c7ae6c79132b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/PayflowproVoidTest.php @@ -0,0 +1,266 @@ +objectManager = Bootstrap::getObjectManager(); + } + + /** + * Tests PayflowPro payment void operation. + * + * @magentoDataFixture Magento/Paypal/_files/order_payflowpro.php + * @magentoConfigFixture current_store payment/payflowpro/active 1 + */ + public function testPaymentVoid(): void + { + $response = new DataObject( + [ + 'result' => '0', + 'pnref' => 'V19A3D27B61E', + 'respmsg' => 'Approved', + 'authcode' => '510PNI', + 'hostcode' => 'A', + 'request_id' => 'f930d3dc6824c1f7230c5529dc37ae5e', + 'result_code' => '0', + ] + ); + + $order = $this->getOrder(); + $payment = $order->getPayment(); + $instance = $this->getPaymentMethodInstance($response); + $payment->setMethodInstance($instance); + + $this->assertTrue($order->canVoidPayment()); + + $payment->void(new DataObject()); + /** @var OrderRepositoryInterface $orderRepository */ + $orderRepository = $this->objectManager->get(OrderRepositoryInterface::class); + $orderRepository->save($order); + + $order = $this->getOrderByIncrementId('100000001'); + $this->assertFalse($order->canVoidPayment()); + } + + /** + * Tests canceling order with acceptable void transaction results. + * + * @param DataObject $response + * @magentoDataFixture Magento/Paypal/_files/order_payflowpro.php + * @magentoConfigFixture current_store payment/payflowpro/active 1 + * @dataProvider orderCancelSuccessDataProvider + */ + public function testOrderCancelSuccess(DataObject $response): void + { + $order = $this->getOrder(); + $payment = $order->getPayment(); + $instance = $this->getPaymentMethodInstance($response); + $payment->setMethodInstance($instance); + $order->cancel(); + + $this->assertEquals(Order::STATE_CANCELED, $order->getState()); + $this->assertEquals(Order::STATE_CANCELED, $order->getStatus()); + } + + /** + * @return array + */ + public function orderCancelSuccessDataProvider(): array + { + return [ + 'Authorization has expired' => [ + new DataObject( + [ + 'respmsg' => 'Declined: 10601-Authorization has expired.', + 'result_code' => '10601', + ] + ) + ], + 'Authorization voided successfully' => [ + new DataObject( + [ + 'respmsg' => 'Approved', + 'result_code' => '0', + ] + ) + ] + ]; + } + + /** + * Tests canceling the order when got an error during transaction voiding. + * + * @magentoDataFixture Magento/Paypal/_files/order_payflowpro.php + * @magentoConfigFixture current_store payment/payflowpro/active 1 + */ + public function testOrderCancelWithVoidError(): void + { + $response = new DataObject( + [ + 'respmsg' => 'Declined: for some reason other then expired authorization', + 'result_code' => '111', + ] + ); + $order = $this->getOrder(); + $payment = $order->getPayment(); + $instance = $this->getPaymentMethodInstance($response); + $payment->setMethodInstance($instance); + + $this->expectException(CommandException::class); + $order->cancel(); + } + + /** + * Returns prepared order. + * + * @return Order + * @throws \ReflectionException + */ + private function getOrder(): Order + { + /** @var $order Order */ + $order = $this->getOrderByIncrementId('100000001'); + $orderItem = $this->createMock(Item::class); + $orderItem->method('getQtyToInvoice') + ->willReturn(true); + $order->setItems([$orderItem]); + + $payment = $order->getPayment(); + $canVoidLookupProperty = new \ReflectionProperty(get_class($payment), '_canVoidLookup'); + $canVoidLookupProperty->setAccessible(true); + $canVoidLookupProperty->setValue($payment, true); + + return $order; + } + + /** + * Returns payment method instance. + * + * @param DataObject $response + * @return PaymentMethodInterface + * @throws \ReflectionException + */ + private function getPaymentMethodInstance(DataObject $response): PaymentMethodInterface + { + $gatewayMock = $this->createMock(Gateway::class); + $gatewayMock->expects($this->once()) + ->method('postRequest') + ->willReturn($response); + + $configMock = $this->createMock(PayflowConfig::class); + $configFactoryMock = $this->createPartialMock( + ConfigInterfaceFactory::class, + ['create'] + ); + + $configFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($configMock); + + $configMock->expects($this->any()) + ->method('getValue') + ->willReturnMap( + [ + ['use_proxy', false], + ['sandbox_flag', '1'], + ['transaction_url_test_mode', 'https://test_transaction_url'] + ] + ); + + /** @var Payflowpro|\PHPUnit_Framework_MockObject_MockObject $instance */ + $instance = $this->getMockBuilder(Payflowpro::class) + ->setMethods(['setStore', 'getInfoInstance']) + ->setConstructorArgs( + [ + $this->objectManager->get(Context::class), + $this->objectManager->get(Registry::class), + $this->objectManager->get(ExtensionAttributesFactory::class), + $this->objectManager->get(AttributeValueFactory::class), + $this->objectManager->get(Data::class), + $this->objectManager->get(ScopeConfigInterface::class), + $this->objectManager->get(Logger::class), + $this->objectManager->get(ModuleListInterface::class), + $this->objectManager->get(TimezoneInterface::class), + $this->objectManager->get(StoreManagerInterface::class), + $configFactoryMock, + $gatewayMock, + $this->objectManager->get(HandlerInterface::class), + null, + null, + [] + ] + ) + ->getMock(); + + $instance->expects($this->once()) + ->method('setStore') + ->willReturnSelf(); + $paymentInfoInstance = $this->createMock(InfoInterface::class); + $instance->method('getInfoInstance') + ->willReturn($paymentInfoInstance); + + return $instance; + } + + /** + * Get stored order. + * + * @param string $incrementId + * @return OrderInterface + */ + private function getOrderByIncrementId(string $incrementId): OrderInterface + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter(OrderInterface::INCREMENT_ID, $incrementId) + ->create(); + + $orderRepository = $this->objectManager->get(OrderRepositoryInterface::class); + $orders = $orderRepository->getList($searchCriteria) + ->getItems(); + + /** @var OrderInterface $order */ + return array_pop($orders); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/VoidTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Model/VoidTest.php deleted file mode 100644 index 6f295a62f48fb..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/VoidTest.php +++ /dev/null @@ -1,102 +0,0 @@ -create(\Magento\Sales\Model\Order::class); - $order->loadByIncrementId('100000001'); - $payment = $order->getPayment(); - - $gatewayMock = $this->createMock(\Magento\Paypal\Model\Payflow\Service\Gateway::class); - - $configMock = $this->createMock(\Magento\Paypal\Model\PayflowConfig::class); - $configFactoryMock = $this->createPartialMock( - \Magento\Payment\Model\Method\ConfigInterfaceFactory::class, - ['create'] - ); - - $configFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($configMock); - - $configMock->expects($this->any()) - ->method('getValue') - ->willReturnMap( - [ - ['use_proxy', false], - ['sandbox_flag', '1'], - ['transaction_url_test_mode', 'https://test_transaction_url'] - ] - ); - - /** @var \Magento\Paypal\Model\Payflowpro|\PHPUnit\Framework\MockObject\MockObject $instance */ - $instance = $this->getMockBuilder(\Magento\Paypal\Model\Payflowpro::class) - ->setMethods(['setStore']) - ->setConstructorArgs( - [ - $objectManager->get(\Magento\Framework\Model\Context::class), - $objectManager->get(\Magento\Framework\Registry::class), - $objectManager->get(\Magento\Framework\Api\ExtensionAttributesFactory::class), - $objectManager->get(\Magento\Framework\Api\AttributeValueFactory::class), - $objectManager->get(\Magento\Payment\Helper\Data::class), - $objectManager->get(\Magento\Framework\App\Config\ScopeConfigInterface::class), - $objectManager->get(\Magento\Payment\Model\Method\Logger::class), - $objectManager->get(\Magento\Framework\Module\ModuleListInterface::class), - $objectManager->get(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class), - $objectManager->get(\Magento\Store\Model\StoreManagerInterface::class), - $configFactoryMock, - $gatewayMock, - $objectManager->get(\Magento\Paypal\Model\Payflow\Service\Response\Handler\HandlerInterface::class), - null, - null, - [] - ] - ) - ->getMock(); - - $response = new \Magento\Framework\DataObject( - [ - 'result' => '0', - 'pnref' => 'V19A3D27B61E', - 'respmsg' => 'Approved', - 'authcode' => '510PNI', - 'hostcode' => 'A', - 'request_id' => 'f930d3dc6824c1f7230c5529dc37ae5e', - 'result_code' => '0', - ] - ); - - $gatewayMock->expects($this->once()) - ->method('postRequest') - ->willReturn($response); - $instance->expects($this->once()) - ->method('setStore') - ->willReturnSelf(); - - $payment->setMethodInstance($instance); - $payment->void(new \Magento\Framework\DataObject()); - $order->save(); - - $order = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Sales\Model\Order::class); - $order->loadByIncrementId('100000001'); - $this->assertFalse($order->canVoidPayment()); - } -} From a41a442164b25c4bc7af161b3764dde7ecdeac2b Mon Sep 17 00:00:00 2001 From: "rostyslav.hymon" Date: Mon, 1 Jun 2020 14:06:16 +0300 Subject: [PATCH 14/24] MC-25062: 1213 Deadlock found when trying to get lock --- .../Catalog/Model/Indexer/Product/Price/AbstractAction.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php index e24b0c46f5503..f010536f06ee5 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php @@ -401,6 +401,8 @@ protected function _reindexRows($changedIds = []) } /** + * Delete Index data + * * @param array $entityIds * @return void */ @@ -497,6 +499,8 @@ protected function getIndexTargetTable() } /** + * Get product Id field name + * * @return string */ protected function getProductIdFieldName() @@ -533,6 +537,7 @@ private function getProductsTypes(array $changedIds = []) /** * Get parent products types + * * Used for add composite products to reindex if we have only simple products in changed ids set * * @param array $productsIds From b045791fca9112dfd8a6c4535e7fc130de9dd7c9 Mon Sep 17 00:00:00 2001 From: DmytroPaidych Date: Tue, 2 Jun 2020 10:17:33 +0300 Subject: [PATCH 15/24] MC-33392: Customer configuration: Password options --- .../Block/Account/AuthenticationPopupTest.php | 59 ++++++ .../Block/Account/ResetPasswordTest.php | 20 ++ .../Magento/Customer/Block/Form/LoginTest.php | 20 ++ .../AccountManagement/AuthenticateTest.php | 71 +++++++ .../Customer/Model/AuthenticationTest.php | 60 ++++++ .../Model/Checkout/ConfigProviderTest.php | 57 +++++ .../Customer/Model/EmailNotificationTest.php | 193 +++++++++++++++++ .../customer_password_email_template.html | 10 + .../_files/expired_lock_for_customer.php | 29 +++ .../expired_lock_for_customer_rollback.php | 10 + .../Customer/_files/locked_customer.php | 29 +++ .../_files/locked_customer_rollback.php | 10 + .../Model/Plugin/AccountManagementTest.php | 196 ++++++++++++++++++ .../_files/customer_reset_password.php | 35 ++++ .../customer_reset_password_rollback.php | 25 +++ 15 files changed, 824 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Block/Account/AuthenticationPopupTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/AuthenticateTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Model/AuthenticationTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Model/Checkout/ConfigProviderTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Model/EmailNotificationTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/_files/customer_password_email_template.html create mode 100644 dev/tests/integration/testsuite/Magento/Customer/_files/expired_lock_for_customer.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/_files/expired_lock_for_customer_rollback.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/_files/locked_customer.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/_files/locked_customer_rollback.php create mode 100644 dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AccountManagementTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Security/_files/customer_reset_password.php create mode 100644 dev/tests/integration/testsuite/Magento/Security/_files/customer_reset_password_rollback.php diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Account/AuthenticationPopupTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Account/AuthenticationPopupTest.php new file mode 100644 index 0000000000000..5744e5e05f5fc --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Account/AuthenticationPopupTest.php @@ -0,0 +1,59 @@ +objectManager = Bootstrap::getObjectManager(); + $this->block = $this->objectManager->get(LayoutInterface::class)->createBlock(AuthenticationPopup::class); + } + + /** + * @magentoConfigFixture current_store customer/password/autocomplete_on_storefront 1 + * + * @return void + */ + public function testAutocompletePasswordEnabled(): void + { + $this->assertEquals('on', $this->block->getConfig()['autocomplete']); + } + + /** + * @magentoConfigFixture current_store customer/password/autocomplete_on_storefront 0 + * + * @return void + */ + public function testAutocompletePasswordDisabled(): void + { + $this->assertEquals('off', $this->block->getConfig()['autocomplete']); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Account/ResetPasswordTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Account/ResetPasswordTest.php index 80d77a3f90b1c..36de4386a7938 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Block/Account/ResetPasswordTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Account/ResetPasswordTest.php @@ -83,4 +83,24 @@ public function testResetPasswordForm(): void 'Set password button was not found on the page' ); } + + /** + * @magentoConfigFixture current_store customer/password/autocomplete_on_storefront 1 + * + * @return void + */ + public function testAutocompletePasswordEnabled(): void + { + $this->assertFalse($this->block->isAutocompleteDisabled()); + } + + /** + * @magentoConfigFixture current_store customer/password/autocomplete_on_storefront 0 + * + * @return void + */ + public function testAutocompletePasswordDisabled(): void + { + $this->assertTrue($this->block->isAutocompleteDisabled()); + } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Form/LoginTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Form/LoginTest.php index 6788d44d8e536..5c4a29f0a9d32 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Block/Form/LoginTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Form/LoginTest.php @@ -89,4 +89,24 @@ public function testLoginForm(): void 'Forgot password link does not exist on the page' ); } + + /** + * @magentoConfigFixture current_store customer/password/autocomplete_on_storefront 1 + * + * @return void + */ + public function testAutocompletePasswordEnabled(): void + { + $this->assertFalse($this->block->isAutocompleteDisabled()); + } + + /** + * @magentoConfigFixture current_store customer/password/autocomplete_on_storefront 0 + * + * @return void + */ + public function testAutocompletePasswordDisabled(): void + { + $this->assertTrue($this->block->isAutocompleteDisabled()); + } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/AuthenticateTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/AuthenticateTest.php new file mode 100644 index 0000000000000..345a001973e8c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/AuthenticateTest.php @@ -0,0 +1,71 @@ +objectManager = Bootstrap::getObjectManager(); + $this->accountManagement = $this->objectManager->get(AccountManagementInterface::class); + $this->customerRegistry = $this->objectManager->get(CustomerRegistry::class); + } + + /** + * @magentoDataFixture Magento/Customer/_files/locked_customer.php + * + * @return void + */ + public function testAuthenticateByLockedCustomer(): void + { + $this->expectExceptionObject(new UserLockedException(__('The account is locked.'))); + $this->accountManagement->authenticate('customer@example.com', 'password'); + } + + /** + * @magentoAppArea frontend + * @magentoDataFixture Magento/Customer/_files/expired_lock_for_customer.php + * + * @return void + */ + public function testAuthenticateByCustomerExpiredLock(): void + { + $email = 'customer@example.com'; + $customer = $this->accountManagement->authenticate($email, 'password'); + $customerSecure = $this->customerRegistry->retrieveSecureData($customer->getId()); + $this->assertEquals(0, $customerSecure->getFailuresNum()); + $this->assertNull($customerSecure->getFirstFailure()); + $this->assertNull($customerSecure->getLockExpires()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/AuthenticationTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/AuthenticationTest.php new file mode 100644 index 0000000000000..3de64701bdedb --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/AuthenticationTest.php @@ -0,0 +1,60 @@ +objectManager = Bootstrap::getObjectManager(); + $this->authentication = $this->objectManager->get(AuthenticationInterface::class); + } + + /** + * @magentoDataFixture Magento/Customer/_files/expired_lock_for_customer.php + * + * @return void + */ + public function testCustomerAuthenticate(): void + { + $this->assertTrue($this->authentication->authenticate(1, 'password')); + } + + /** + * @magentoDataFixture Magento/Customer/_files/expired_lock_for_customer.php + * + * @return void + */ + public function testCustomerAuthenticateWithWrongPassword(): void + { + $this->expectExceptionObject(new InvalidEmailOrPasswordException(__('Invalid login or password.'))); + $this->authentication->authenticate(1, 'password1'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/Checkout/ConfigProviderTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/Checkout/ConfigProviderTest.php new file mode 100644 index 0000000000000..f29b5f622cdf1 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/Checkout/ConfigProviderTest.php @@ -0,0 +1,57 @@ +objectManager = Bootstrap::getObjectManager(); + $this->configProvider = $this->objectManager->get(ConfigProvider::class); + } + + /** + * @magentoConfigFixture current_store customer/password/autocomplete_on_storefront 1 + * + * @return void + */ + public function testAutocompletePasswordEnabled(): void + { + $this->assertEquals('on', $this->configProvider->getConfig()['autocomplete']); + } + + /** + * @magentoConfigFixture current_store customer/password/autocomplete_on_storefront 0 + * + * @return void + */ + public function testAutocompletePasswordDisabled(): void + { + $this->assertEquals('off', $this->configProvider->getConfig()['autocomplete']); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/EmailNotificationTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/EmailNotificationTest.php new file mode 100644 index 0000000000000..e63c3d2761c49 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/EmailNotificationTest.php @@ -0,0 +1,193 @@ +objectManager = Bootstrap::getObjectManager(); + $this->moduleManager = $this->objectManager->get(Manager::class); + //This check is needed because Magento_Customer independent of Magento_Email + if (!$this->moduleManager->isEnabled('Magento_Email')) { + $this->markTestSkipped('Magento_Email module disabled.'); + } + $this->customerRepository = $this->objectManager->get(CustomerRepositoryInterface::class); + $this->emailNotification = $this->objectManager->get(EmailNotificationInterface::class); + $this->transportBuilder = $this->objectManager->get(TransportBuilderMock::class); + $this->templateResource = $this->objectManager->get(TemplateResource::class); + $this->templateFactory = $this->objectManager->get(TemplateFactory::class); + $this->mutableScopeConfig = $this->objectManager->get(MutableScopeConfigInterface::class); + $this->templateCollectionFactory = $this->objectManager->get(CollectionFactory::class); + } + + /** + * @inheritdoc + */ + protected function tearDown(): void + { + if ($this->moduleManager->isEnabled('Magento_Email')) { + $this->mutableScopeConfig->clean(); + $collection = $this->templateCollectionFactory->create(); + $template = $collection->addFieldToFilter('template_code', 'customer_password_email_template') + ->getFirstItem(); + if ($template->getId()) { + $this->templateResource->delete($template); + } + } + + parent::tearDown(); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * + * @return void + */ + public function testResetPasswordCustomTemplate(): void + { + $this->setEmailTemplateConfig(EmailNotification::XML_PATH_RESET_PASSWORD_TEMPLATE); + $customer = $this->customerRepository->get('customer@example.com'); + $this->emailNotification->credentialsChanged($customer, $customer->getEmail(), true); + $expectedSender = ['name' => 'CustomerSupport', 'email' => 'support@example.com']; + $this->assertMessage($expectedSender); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoConfigFixture current_store customer/password/forgot_email_identity custom1 + * + * @return void + */ + public function testForgotPasswordCustomTemplate(): void + { + $this->setEmailTemplateConfig(EmailNotification::XML_PATH_FORGOT_EMAIL_TEMPLATE); + $customer = $this->customerRepository->get('customer@example.com'); + $this->emailNotification->passwordResetConfirmation($customer); + $expectedSender = ['name' => 'Custom 1', 'email' => 'custom1@example.com']; + $this->assertMessage($expectedSender); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoConfigFixture current_store customer/password/forgot_email_identity custom2 + * + * @return void + */ + public function testRemindPasswordCustomTemplate(): void + { + $this->setEmailTemplateConfig(EmailNotification::XML_PATH_REMIND_EMAIL_TEMPLATE); + $customer = $this->customerRepository->get('customer@example.com'); + $this->emailNotification->passwordReminder($customer); + $expectedSender = ['name' => 'Custom 2', 'email' => 'custom2@example.com']; + $this->assertMessage($expectedSender); + } + + /** + * Assert message. + * + * @param array $expectedSender + * @return void + */ + private function assertMessage(array $expectedSender): void + { + $message = $this->transportBuilder->getSentMessage(); + $this->assertNotNull($message); + $this->assertMessageSender($message, $expectedSender); + $this->assertStringContainsString( + 'Text specially for check in test.', + $message->getBody()->getParts()[0]->getRawContent(), + 'Expected text wasn\'t found in message.' + ); + } + + /** + * Assert message sender. + * + * @param MessageInterface $message + * @param array $expectedSender + * @return void + */ + private function assertMessageSender(MessageInterface $message, array $expectedSender): void + { + $messageFrom = $message->getFrom(); + $this->assertNotNull($messageFrom); + $messageFrom = current($messageFrom); + $this->assertEquals($expectedSender['name'], $messageFrom->getName()); + $this->assertEquals($expectedSender['email'], $messageFrom->getEmail()); + } + + /** + * Set email template config. + * + * @param string $configPath + * @return void + */ + private function setEmailTemplateConfig(string $configPath): void + { + $template = $this->templateFactory->create(); + $template->setTemplateCode('customer_password_email_template') + ->setTemplateText(file_get_contents(__DIR__ . '/../_files/customer_password_email_template.html')) + ->setTemplateType(Template::TYPE_HTML); + $this->templateResource->save($template); + $this->mutableScopeConfig->setValue($configPath, $template->getId(), ScopeInterface::SCOPE_STORE, 'default'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_password_email_template.html b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_password_email_template.html new file mode 100644 index 0000000000000..482fa79247a23 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_password_email_template.html @@ -0,0 +1,10 @@ + + +{{template config_path="design/email/header_template"}} +

Text specially for check in test.

+{{template config_path="design/email/footer_template"}} diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/expired_lock_for_customer.php b/dev/tests/integration/testsuite/Magento/Customer/_files/expired_lock_for_customer.php new file mode 100644 index 0000000000000..e7c8e5074664e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/expired_lock_for_customer.php @@ -0,0 +1,29 @@ +requireDataFixture('Magento/Customer/_files/customer.php'); + +$objectManager = Bootstrap::getObjectManager(); +/** @var CustomerRegistry $customerRegistry */ +$customerRegistry = $objectManager->get(CustomerRegistry::class); +/** @var CustomerAuthUpdate $customerAuthUpdate */ +$customerAuthUpdate = $objectManager->get(CustomerAuthUpdate::class); +$customerId = 1; + +$customerSecure = $customerRegistry->retrieveSecureData($customerId); +$dateTime = new \DateTimeImmutable(); +$customerSecure->setFailuresNum(10) + ->setFirstFailure($dateTime->modify('-15 minutes')->format(DateTime::DATETIME_PHP_FORMAT)) + ->setLockExpires($dateTime->modify('-5 minutes')->format(DateTime::DATETIME_PHP_FORMAT)); +$customerAuthUpdate->saveAuth($customerId); +$customerRegistry->remove($customerId); diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/expired_lock_for_customer_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/expired_lock_for_customer_rollback.php new file mode 100644 index 0000000000000..abd24344319a6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/expired_lock_for_customer_rollback.php @@ -0,0 +1,10 @@ +requireDataFixture('Magento/Customer/_files/customer_rollback.php'); diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/locked_customer.php b/dev/tests/integration/testsuite/Magento/Customer/_files/locked_customer.php new file mode 100644 index 0000000000000..b88f025db4d76 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/locked_customer.php @@ -0,0 +1,29 @@ +requireDataFixture('Magento/Customer/_files/customer.php'); + +$objectManager = Bootstrap::getObjectManager(); +/** @var CustomerRegistry $customerRegistry */ +$customerRegistry = $objectManager->get(CustomerRegistry::class); +/** @var CustomerAuthUpdate $customerAuthUpdate */ +$customerAuthUpdate = $objectManager->get(CustomerAuthUpdate::class); +$customerId = 1; + +$customerSecure = $customerRegistry->retrieveSecureData($customerId); +$dateTime = new \DateTimeImmutable(); +$customerSecure->setFailuresNum(10) + ->setFirstFailure($dateTime->modify('-5 minutes')->format(DateTime::DATETIME_PHP_FORMAT)) + ->setLockExpires($dateTime->modify('+5 minutes')->format(DateTime::DATETIME_PHP_FORMAT)); +$customerAuthUpdate->saveAuth($customerId); +$customerRegistry->remove($customerId); diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/locked_customer_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/locked_customer_rollback.php new file mode 100644 index 0000000000000..abd24344319a6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/locked_customer_rollback.php @@ -0,0 +1,10 @@ +requireDataFixture('Magento/Customer/_files/customer_rollback.php'); diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AccountManagementTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AccountManagementTest.php new file mode 100644 index 0000000000000..9f878f6f51370 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AccountManagementTest.php @@ -0,0 +1,196 @@ +objectManager = Bootstrap::getObjectManager(); + $this->moduleManager = $this->objectManager->get(Manager::class); + //This check is needed because Magento_Security independent of Magento_Customer + if (!$this->moduleManager->isEnabled('Magento_Customer')) { + $this->markTestSkipped('Magento_Customer module disabled.'); + } + $this->accountManagement = $this->objectManager->get(AccountManagementInterface::class); + $this->request = $this->objectManager->get(RequestInterface::class); + $this->securityConfig = $this->objectManager->get(ConfigInterface::class); + $this->errorMessage = __( + 'We received too many requests for password resets. Please wait and try again later or contact %1.', + $this->securityConfig->getCustomerServiceEmail() + ); + } + + /** + * @return void + */ + public function testPluginIsRegistered(): void + { + $pluginInfo = $this->objectManager->get(PluginList::class)->get(CustomerAccountManagement::class); + $this->assertSame( + AccountManagement::class, + $pluginInfo['security_check_customer_password_reset_attempt']['instance'] + ); + } + + /** + * @magentoConfigFixture current_store customer/password/max_number_password_reset_requests 1 + * @magentoDataFixture Magento/Security/_files/customer_reset_password.php + * + * @return void + */ + public function testMaxNumberPasswordResetRequests(): void + { + $this->prepareServerParameters(); + $this->expectExceptionObject(new SecurityViolationException($this->errorMessage)); + $this->accountManagement->initiatePasswordReset( + 'customer@example.com', + CustomerAccountManagement::EMAIL_REMINDER + ); + } + + /** + * @magentoConfigFixture current_store customer/password/min_time_between_password_reset_requests 10 + * @magentoDataFixture Magento/Security/_files/customer_reset_password.php + * + * @return void + */ + public function testTimeBetweenPasswordResetRequests(): void + { + $this->prepareServerParameters(); + $this->expectExceptionObject(new SecurityViolationException($this->errorMessage)); + $this->accountManagement->initiatePasswordReset( + 'customer@example.com', + CustomerAccountManagement::EMAIL_REMINDER + ); + } + + /** + * @magentoConfigFixture current_store customer/password/password_reset_protection_type 0 + * @magentoConfigFixture current_store customer/password/max_number_password_reset_requests 1 + * @magentoDataFixture Magento/Security/_files/customer_reset_password.php + * + * @return void + */ + public function testPasswordResetProtectionTypeDisabled(): void + { + $this->prepareServerParameters(); + $result = $this->accountManagement->initiatePasswordReset( + 'customer@example.com', + CustomerAccountManagement::EMAIL_REMINDER + ); + $this->assertTrue($result); + } + + /** + * @magentoConfigFixture current_store customer/password/password_reset_protection_type 1 + * @magentoConfigFixture current_store customer/password/max_number_password_reset_requests 1 + * @magentoDataFixture Magento/Security/_files/customer_reset_password.php + * + * @return void + */ + public function testPasswordResetProtectionTypeByIpAndEmail(): void + { + $this->prepareServerParameters(); + $this->expectExceptionObject(new SecurityViolationException($this->errorMessage)); + $this->accountManagement->initiatePasswordReset( + 'customer@example.com', + CustomerAccountManagement::EMAIL_REMINDER + ); + } + + /** + * @magentoConfigFixture current_store customer/password/password_reset_protection_type 2 + * @magentoConfigFixture current_store customer/password/max_number_password_reset_requests 1 + * @magentoDataFixture Magento/Security/_files/customer_reset_password.php + * + * @return void + */ + public function testPasswordResetProtectionTypeByIp(): void + { + $this->markTestSkipped('Test blocked by issue MC-32988.'); + $this->prepareServerParameters(); + $this->expectExceptionObject(new SecurityViolationException($this->errorMessage)); + $this->accountManagement->initiatePasswordReset( + 'customer@example.com', + CustomerAccountManagement::EMAIL_REMINDER + ); + } + + /** + * @magentoConfigFixture current_store customer/password/password_reset_protection_type 3 + * @magentoConfigFixture current_store customer/password/max_number_password_reset_requests 1 + * @magentoDataFixture Magento/Security/_files/customer_reset_password.php + * + * @return void + */ + public function testPasswordResetProtectionTypeByEmail(): void + { + $this->prepareServerParameters(); + $this->expectExceptionObject(new SecurityViolationException($this->errorMessage)); + $this->accountManagement->initiatePasswordReset( + 'customer@example.com', + CustomerAccountManagement::EMAIL_REMINDER + ); + } + + /** + * Prepare server parameters. + * + * @return void + */ + private function prepareServerParameters(): void + { + $parameters = $this->objectManager->create(Parameters::class); + $parameters->set('REMOTE_ADDR', '127.0.0.1'); + $this->request->setServer($parameters); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Security/_files/customer_reset_password.php b/dev/tests/integration/testsuite/Magento/Security/_files/customer_reset_password.php new file mode 100644 index 0000000000000..3ea95460ed2d0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Security/_files/customer_reset_password.php @@ -0,0 +1,35 @@ +get(Manager::class); +//This check is needed because Magento_Security independent of Magento_Customer +if ($moduleManager->isEnabled('Magento_Customer')) { + Resolver::getInstance()->requireDataFixture('Magento/Customer/_files/customer.php'); + + /** @var PasswordResetRequestEventFactory $passwordResetRequestEventFactory */ + $passwordResetRequestEventFactory = $objectManager->get(PasswordResetRequestEventFactory::class); + /** @var PasswordResetRequestEventResource $passwordResetRequestEventResource */ + $passwordResetRequestEventResource = $objectManager->get(PasswordResetRequestEventResource::class); + + $dateTime = new DateTimeImmutable(); + $passwordResetRequestEvent = $passwordResetRequestEventFactory->create(); + $passwordResetRequestEvent->setRequestType(PasswordResetRequestEvent::CUSTOMER_PASSWORD_RESET_REQUEST) + ->setAccountReference('customer@example.com') + ->setIp(ip2long('127.0.0.1')) + ->setCreatedAt($dateTime->modify('-5 minutes')->format(DateTime::DATETIME_PHP_FORMAT))->save(); + $passwordResetRequestEventResource->save($passwordResetRequestEvent); +} diff --git a/dev/tests/integration/testsuite/Magento/Security/_files/customer_reset_password_rollback.php b/dev/tests/integration/testsuite/Magento/Security/_files/customer_reset_password_rollback.php new file mode 100644 index 0000000000000..d2a00c5eebd14 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Security/_files/customer_reset_password_rollback.php @@ -0,0 +1,25 @@ +get(Manager::class); +//This check is needed because Magento_Security independent of Magento_Customer +if ($moduleManager->isEnabled('Magento_Customer')) { + Resolver::getInstance()->requireDataFixture('Magento/Customer/_files/customer_rollback.php'); + + /** @var PasswordResetRequestEvent $passwordResetRequestEventResource */ + $passwordResetRequestEventResource = $objectManager->get(PasswordResetRequestEvent::class); + $dateTime = new DateTimeImmutable(); + $passwordResetRequestEventResource->deleteRecordsOlderThen($dateTime->format(DateTime::DATETIME_PHP_FORMAT)); +} From ce246cb4e20b6775e16b64df043cfe3d8adc55be Mon Sep 17 00:00:00 2001 From: DmytroPaidych Date: Tue, 2 Jun 2020 10:19:23 +0300 Subject: [PATCH 16/24] MC-33388: View/edit item in customer wish list grid in admin --- .../Adminhtml/Edit/Tab/View/WishlistTest.php | 78 +++++++++ .../Product/Composite/Wishlist/UpdateTest.php | 151 ++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/WishlistTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Wishlist/Product/Composite/Wishlist/UpdateTest.php diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/WishlistTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/WishlistTest.php new file mode 100644 index 0000000000000..11d51e1f2c814 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/WishlistTest.php @@ -0,0 +1,78 @@ +objectManager = Bootstrap::getObjectManager(); + $this->registry = $this->objectManager->get(Registry::class); + $this->block = $this->objectManager->get(LayoutInterface::class)->createBlock(Wishlist::class); + } + + /** + * @inheritdoc + */ + protected function tearDown(): void + { + $this->registry->unregister(RegistryConstants::CURRENT_CUSTOMER_ID); + + parent::tearDown(); + } + + /** + * @magentoDataFixture Magento/Wishlist/_files/wishlist.php + * + * @return void + */ + public function testWishListGrid(): void + { + $this->registerCustomerId(1); + $this->assertCount(1, $this->block->getPreparedCollection()); + } + + /** + * Add customer id to registry. + * + * @param int $customerId + * @return void + */ + private function registerCustomerId(int $customerId): void + { + $this->registry->unregister(RegistryConstants::CURRENT_CUSTOMER_ID); + $this->registry->register(RegistryConstants::CURRENT_CUSTOMER_ID, $customerId); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Wishlist/Product/Composite/Wishlist/UpdateTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Wishlist/Product/Composite/Wishlist/UpdateTest.php new file mode 100644 index 0000000000000..035db789b171e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Wishlist/Product/Composite/Wishlist/UpdateTest.php @@ -0,0 +1,151 @@ +getWishlistByCustomerId = $this->_objectManager->get(GetWishlistByCustomerId::class); + $this->json = $this->_objectManager->get(SerializerInterface::class); + $this->session = $this->_objectManager->get(Session::class); + } + + /** + * @magentoDataFixture Magento/Wishlist/_files/wishlist_with_simple_product.php + * @magentoDbIsolation disabled + * + * @return void + */ + public function testUpdateItem(): void + { + $item = $this->getWishlistByCustomerId->getItemBySku(1, 'simple-1'); + $this->assertNotNull($item); + $params = ['id' => $item->getId(), 'qty' => 5]; + $this->dispatchUpdateItemRequest($params); + $this->assertEquals($params['qty'], $this->getWishlistByCustomerId->getItemBySku(1, 'simple-1')->getQty()); + } + + /** + * @magentoDataFixture Magento/Wishlist/_files/wishlist_with_configurable_product.php + * @magentoDbIsolation disabled + * + * @return void + */ + public function testUpdateItemOption(): void + { + $item = $this->getWishlistByCustomerId->getItemBySku(1, 'Configurable product'); + $this->assertNotNull($item); + $params = [ + 'id' => $item->getId(), + 'super_attribute' => $this->performConfigurableOption($item->getProduct()), + 'qty' => 5, + ]; + $this->dispatchUpdateItemRequest($params); + $this->assertUpdatedItem( + $this->getWishlistByCustomerId->getItemBySku(1, 'Configurable product'), + $params + ); + } + + /** + * @return void + */ + public function testUpdateNotExistingItem(): void + { + $this->dispatchUpdateItemRequest(['id' => 989]); + $this->assertTrue($this->session->getCompositeProductResult()->getError()); + $this->assertEquals( + (string)__('Please load Wish List item.'), + $this->session->getCompositeProductResult()->getMessage() + ); + } + + /** + * @return void + */ + public function testUpdateWithoutParams(): void + { + $this->dispatchUpdateItemRequest([]); + $this->assertTrue($this->session->getCompositeProductResult()->getError()); + $this->assertEquals( + (string)__('Please define Wish List item ID.'), + $this->session->getCompositeProductResult()->getMessage() + ); + } + /** + * Assert updated item in wish list. + * + * @param Item $item + * @param array $expectedData + * @return void + */ + private function assertUpdatedItem(Item $item, array $expectedData): void + { + $this->assertEquals($expectedData['qty'], $item->getQty()); + $buyRequestOption = $this->json->unserialize($item->getOptionByCode('info_buyRequest')->getValue()); + foreach ($expectedData as $key => $value) { + $this->assertEquals($value, $buyRequestOption[$key]); + } + } + + /** + * Perform configurable option to select. + * + * @param ProductInterface $product + * @return array + */ + private function performConfigurableOption(ProductInterface $product): array + { + $configurableOptions = $product->getTypeInstance()->getConfigurableOptions($product); + $attributeId = key($configurableOptions); + $option = reset($configurableOptions[$attributeId]); + + return [$attributeId => $option['value_index']]; + } + + /** + * Dispatch update wish list item request. + * + * @param array $params + * @return void + */ + private function dispatchUpdateItemRequest(array $params): void + { + $this->getRequest()->setParams($params)->setMethod(HttpRequest::METHOD_POST); + $this->dispatch('backend/customer/wishlist_product_composite_wishlist/update'); + $this->assertRedirect($this->stringContains('backend/catalog/product/showUpdateResult/')); + } +} From a190a755cfea7ab11abd6d89e113b0753a5b1237 Mon Sep 17 00:00:00 2001 From: DmytroPaidych Date: Tue, 2 Jun 2020 10:33:33 +0300 Subject: [PATCH 17/24] MC-33387: Storefront: View and print order/invoice/shipment/refund details --- .../Item/Renderer/DefaultRendererTest.php | 235 ++++++++++++--- .../Block/Order/PrintOrder/CreditmemoTest.php | 267 ++++++++++++++++-- .../Block/Order/PrintOrder/InvoiceTest.php | 267 ++++++++++++++++-- .../Block/Order/PrintOrder/ShipmentTest.php | 237 ++++++++++++++++ .../Sales/Block/Order/PrintShipmentTest.php | 93 ++++++ .../Sales/_files/shipment_for_two_items.php | 22 ++ .../shipment_for_two_items_rollback.php | 10 + 7 files changed, 1054 insertions(+), 77 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintOrder/ShipmentTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintShipmentTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Sales/_files/shipment_for_two_items.php create mode 100644 dev/tests/integration/testsuite/Magento/Sales/_files/shipment_for_two_items_rollback.php diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Order/Item/Renderer/DefaultRendererTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Order/Item/Renderer/DefaultRendererTest.php index 2543313d6fdce..057f75874032d 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Block/Order/Item/Renderer/DefaultRendererTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Order/Item/Renderer/DefaultRendererTest.php @@ -8,7 +8,11 @@ namespace Magento\Sales\Block\Order\Item\Renderer; use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\Framework\View\Element\AbstractBlock; use Magento\Framework\View\LayoutInterface; +use Magento\Framework\View\Result\PageFactory; +use Magento\Sales\Api\Data\OrderInterface; use Magento\Sales\Api\Data\OrderInterfaceFactory; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\Helper\Xpath; @@ -19,6 +23,7 @@ * * @magentoAppArea frontend * @magentoDbIsolation enabled + * @magentoAppIsolation enabled */ class DefaultRendererTest extends TestCase { @@ -31,14 +36,43 @@ class DefaultRendererTest extends TestCase /** @var OrderInterfaceFactory */ private $orderFactory; + /** @var PageFactory */ + private $pageFactory; + + /** @var Registry */ + private $registry; + + /** + * @var array + */ + private $defaultFieldsToCheck = [ + 'name' => "//td[contains(@class, 'name')]/strong[contains(text(), '%s')]", + 'sku' => "//td[contains(@class, 'sku') and contains(text(), '%s')]", + 'qty' => "//td[contains(@class, 'qty') and contains(text(), '%d')]", + ]; + /** * @inheritdoc */ protected function setUp(): void { + parent::setUp(); + $this->objectManager = Bootstrap::getObjectManager(); $this->block = $this->objectManager->get(LayoutInterface::class)->createBlock(DefaultRenderer::class); $this->orderFactory = $this->objectManager->get(OrderInterfaceFactory::class); + $this->pageFactory = $this->objectManager->get(PageFactory::class); + $this->registry = $this->objectManager->get(Registry::class); + } + + /** + * @inheritdoc + */ + protected function tearDown(): void + { + $this->registry->unregister('current_order'); + + parent::tearDown(); } /** @@ -55,43 +89,13 @@ public function testDisplayingShipmentItem(): void $this->assertNotNull($item); $blockHtml = $this->block->setTemplate('Magento_Sales::order/shipment/items/renderer/default.phtml') ->setItem($item)->toHtml(); - $this->assertEquals( - 1, - Xpath::getElementsCountForXpath( - sprintf( - "//td[contains(@class, 'name')]/strong[contains(text(), '%s')]", - $item->getName() - ), - $blockHtml - ), - sprintf('Item with name %s wasn\'t found.', $item->getName()) - ); - $this->assertEquals( - 1, - Xpath::getElementsCountForXpath( - sprintf( - "//td[contains(@class, 'sku') and contains(text(), '%s')]", - $item->getSku() - ), - $blockHtml - ), - sprintf('Item with sku %s wasn\'t found.', $item->getSku()) - ); - $this->assertEquals( - 1, - Xpath::getElementsCountForXpath( - sprintf( - "//td[contains(@class, 'qty') and contains(text(), '%d')]", - $item->getQty() - ), - $blockHtml - ), - sprintf( - 'Qty for item %s wasn\'t found or not equals to %s.', - $item->getName(), - $item->getQty() - ) - ); + foreach ($this->defaultFieldsToCheck as $key => $xpath) { + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath(sprintf($xpath, $item->getData($key)), $blockHtml), + sprintf('Item %s wasn\'t found or not equals to %s.', $key, $item->getData($key)) + ); + } } /** @@ -108,4 +112,161 @@ public function testCreditmemoItemTotalAmount(): void $this->assertNotNull($item->getId()); $this->assertEquals(10.00, $this->block->getTotalAmount($item)); } + + /** + * @magentoDataFixture Magento/Sales/_files/customer_order_with_two_items.php + * + * @return void + */ + public function testPrintOrderItem(): void + { + $order = $this->orderFactory->create()->loadByIncrementId('100000555'); + $this->registerOrder($order); + $item = $order->getItemsCollection()->getFirstItem(); + $this->assertNotNull($item->getId()); + $block = $this->getBlock('sales_order_print', 'sales.order.print.renderers.default'); + $this->assertNotFalse($block); + $blockHtml = $block->setItem($item)->toHtml(); + $fieldsToCheck = [ + 'name' => "//td[contains(@class, 'name')]/strong[contains(text(), '%s')]", + 'sku' => "//td[contains(@class, 'sku') and contains(text(), '%s')]", + 'price' => "//td[contains(@class, 'price')]//span[contains(text(), '%01.2f')]", + 'qty_ordered' => "//td[contains(@class, 'qty')]//span[contains(text(), '" . __('Ordered') + . "')]/following-sibling::span[contains(text(), '%d')]", + 'row_total' => "//td[contains(@class, 'subtotal')]//span[contains(text(), '%01.2f')]", + ]; + foreach ($fieldsToCheck as $key => $xpath) { + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath(sprintf($xpath, $item->getData($key)), $blockHtml), + sprintf('Item %s wasn\'t found or not equals to %s.', $key, $item->getData($key)) + ); + } + } + + /** + * @magentoDataFixture Magento/Sales/_files/invoices_for_items.php + * + * @return void + */ + public function testPrintInvoiceItem(): void + { + $order = $this->orderFactory->create()->loadByIncrementId('100000555'); + $this->registerOrder($order); + $invoice = $order->getInvoiceCollection()->getFirstItem(); + $this->assertNotNull($invoice->getId()); + $item = $invoice->getItemsCollection()->getFirstItem(); + $this->assertNotNull($item->getId()); + $block = $this->getBlock('sales_order_printinvoice', 'sales.order.print.invoice.renderers.default'); + $this->assertNotFalse($block); + $blockHtml = $block->setItem($item)->toHtml(); + $additionalFields = [ + 'price' => "//td[contains(@class, 'price')]//span[contains(text(), '%01.2f')]", + 'qty' => "//td[contains(@class, 'qty')]/span[contains(text(), '%d')]", + 'row_total' => "//td[contains(@class, 'subtotal')]//span[contains(text(), '%01.2f')]", + ]; + $this->defaultFieldsToCheck = array_merge($this->defaultFieldsToCheck, $additionalFields); + foreach ($this->defaultFieldsToCheck as $key => $xpath) { + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath(sprintf($xpath, $item->getData($key)), $blockHtml), + sprintf('Item %s wasn\'t found or not equals to %s.', $key, $item->getData($key)) + ); + } + } + + /** + * @magentoDataFixture Magento/Sales/_files/shipment_for_order_with_customer.php + * + * @return void + */ + public function testPrintShipmentItem(): void + { + $order = $this->orderFactory->create()->loadByIncrementId('100000001'); + $this->registerOrder($order); + $shipment = $order->getShipmentsCollection()->getFirstItem(); + $this->assertNotNull($shipment->getId()); + $item = $shipment->getAllItems()[0] ?? null; + $this->assertNotNull($item); + $block = $this->getBlock('sales_order_printshipment', 'sales.order.print.shipment.renderers.default'); + $this->assertNotFalse($block); + $blockHtml = $block->setItem($item)->toHtml(); + foreach ($this->defaultFieldsToCheck as $key => $xpath) { + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath(sprintf($xpath, $item->getData($key)), $blockHtml), + sprintf('Item %s wasn\'t found or not equals to %s.', $key, $item->getData($key)) + ); + } + } + + /** + * @magentoDataFixture Magento/Sales/_files/refunds_for_items.php + * + * @return void + */ + public function testPrintCreditmemoItem(): void + { + $order = $this->orderFactory->create()->loadByIncrementId('100000555'); + $this->registerOrder($order); + $creditmemo = $order->getCreditmemosCollection()->getFirstItem(); + $this->assertNotNull($creditmemo->getId()); + $item = $creditmemo->getItemsCollection()->getFirstItem(); + $this->assertNotNull($item->getId()); + $block = $this->getBlock('sales_order_printcreditmemo', 'sales.order.print.creditmemo.renderers.default'); + $this->assertNotFalse($block); + $blockHtml = $block->setItem($item)->toHtml(); + $additionalFields = [ + 'price' => "//td[contains(@class, 'price')]//span[contains(text(), '%01.2f')]", + 'row_total' => "//td[contains(@class, 'subtotal')]//span[contains(text(), '%01.2f')]", + 'discount_amount' => "//td[contains(@class, 'discount')]/span[contains(text(), '%01.2f')]", + ]; + $this->defaultFieldsToCheck = array_merge($this->defaultFieldsToCheck, $additionalFields); + foreach ($this->defaultFieldsToCheck as $key => $xpath) { + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath(sprintf($xpath, $item->getData($key)), $blockHtml), + sprintf('Item %s wasn\'t found or not equals to %s.', $key, $item->getData($key)) + ); + } + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + sprintf( + "//td[contains(@class, 'total')]/span[contains(text(), '%01.2f')]", + $this->block->getTotalAmount($item) + ), + $blockHtml + ), + sprintf('Item total wasn\'t found or not equals to %s.', $this->block->getTotalAmount($item)) + ); + } + + /** + * Get block. + * + * @param string $handle + * @param string $blockName + * @return AbstractBlock + */ + private function getBlock(string $handle, string $blockName): AbstractBlock + { + $page = $this->pageFactory->create(); + $page->addHandle(['default', $handle]); + $page->getLayout()->generateXml(); + + return $page->getLayout()->getBlock($blockName); + } + + /** + * Register order in registry. + * + * @param OrderInterface $order + * @return void + */ + private function registerOrder(OrderInterface $order): void + { + $this->registry->unregister('current_order'); + $this->registry->register('current_order', $order); + } } diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintOrder/CreditmemoTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintOrder/CreditmemoTest.php index d5ca29cd0f0b7..c50c1b23c3f80 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintOrder/CreditmemoTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintOrder/CreditmemoTest.php @@ -3,41 +3,268 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Block\Order\PrintOrder; -class CreditmemoTest extends \PHPUnit\Framework\TestCase +use Magento\Directory\Model\CountryFactory; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\Framework\View\Element\Text; +use Magento\Framework\View\LayoutInterface; +use Magento\Framework\View\Result\PageFactory; +use Magento\Sales\Api\Data\CreditmemoInterfaceFactory; +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\OrderInterfaceFactory; +use Magento\Sales\Api\Data\OrderPaymentInterfaceFactory; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\Xpath; +use PHPUnit\Framework\TestCase; + +/** + * Tests for print creditmemo block. + * + * @magentoAppArea frontend + * @magentoDbIsolation enabled + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class CreditmemoTest extends TestCase { + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var Registry */ + private $registry; + + /** @var LayoutInterface */ + private $layout; + + /** @var OrderInterfaceFactory */ + private $orderFactory; + + /** @var CreditmemoInterfaceFactory */ + private $creditmemoFactory; + + /** @var PageFactory */ + private $pageFactory; + + /** @var CountryFactory */ + private $countryFactory; + + /** @var OrderPaymentInterfaceFactory */ + private $orderPaymentFactory; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->registry = $this->objectManager->get(Registry::class); + $this->layout = $this->objectManager->get(LayoutInterface::class); + $this->orderFactory = $this->objectManager->get(OrderInterfaceFactory::class); + $this->creditmemoFactory = $this->objectManager->get(CreditmemoInterfaceFactory::class); + $this->pageFactory = $this->objectManager->get(PageFactory::class); + $this->countryFactory = $this->objectManager->get(CountryFactory::class); + $this->orderPaymentFactory = $this->objectManager->create(OrderPaymentInterfaceFactory::class); + } + + /** + * @inheritdoc + */ + protected function tearDown(): void + { + $this->registry->unregister('current_order'); + $this->registry->unregister('current_creditmemo'); + + parent::tearDown(); + } + /** * @magentoAppIsolation enabled + * + * @return void */ - public function testGetTotalsHtml() + public function testGetTotalsHtml(): void { - $order = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Sales\Model\Order::class); - /** @var $objectManager \Magento\TestFramework\ObjectManager */ - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $objectManager->get(\Magento\Framework\Registry::class)->register('current_order', $order); - $payment = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Sales\Model\Order\Payment::class - ); + $order = $this->orderFactory->create(); + $this->registerOrder($order); + $payment = $this->orderPaymentFactory->create(); $payment->setMethod('checkmo'); $order->setPayment($payment); - - $layout = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - \Magento\Framework\View\LayoutInterface::class - ); - $block = $layout->createBlock(\Magento\Sales\Block\Order\PrintOrder\Creditmemo::class, 'block'); - $childBlock = $layout->addBlock(\Magento\Framework\View\Element\Text::class, 'creditmemo_totals', 'block'); - + $block = $this->layout->createBlock(Creditmemo::class, 'block'); + $childBlock = $this->layout->addBlock(Text::class, 'creditmemo_totals', 'block'); $expectedHtml = 'Any html'; - $creditmemo = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Sales\Model\Order\Creditmemo::class - ); + $creditmemo = $this->creditmemoFactory->create(); $this->assertEmpty($childBlock->getCreditmemo()); $this->assertNotEquals($expectedHtml, $block->getTotalsHtml($creditmemo)); - $childBlock->setText($expectedHtml); $actualHtml = $block->getTotalsHtml($creditmemo); $this->assertSame($creditmemo, $childBlock->getCreditmemo()); $this->assertEquals($expectedHtml, $actualHtml); } + + /** + * @magentoDataFixture Magento/Sales/_files/refunds_for_items.php + * + * @return void + */ + public function testPrintCreditmemo(): void + { + $order = $this->orderFactory->create()->loadByIncrementId('100000555'); + $creditmemo = $order->getCreditmemosCollection()->getFirstItem(); + $this->assertNotNull($creditmemo->getId()); + $this->registerOrder($order); + $this->registerCreditmemo($creditmemo); + $blockHtml = $this->renderPrintCreditmemoBlock(); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + sprintf( + "//div[contains(@class, 'order-title')]/strong[contains(text(), '%s')]", + __('Refund #%1', $creditmemo->getIncrementId()) + ), + $blockHtml + ), + sprintf('Title for %s was not found.', __('Refund #%1', $creditmemo->getIncrementId())) + ); + $this->assertOrderInformation($order, $blockHtml); + } + + /** + * @magentoDataFixture Magento/Sales/_files/refunds_for_items.php + * + * @return void + */ + public function testOrderInformation(): void + { + $order = $this->orderFactory->create()->loadByIncrementId('100000555'); + $this->registerOrder($order); + $block = $this->layout->createBlock(Creditmemo::class); + $orderDate = $block->formatDate($order->getCreatedAt(), \IntlDateFormatter::LONG); + $templates = [ + 'Order status' => [ + 'template' => 'Magento_Sales::order/order_status.phtml', + 'expected_data' => (string)__($order->getStatusLabel()), + ], + 'Order date' => [ + 'template' => 'Magento_Sales::order/order_date.phtml', + 'expected_data' => (string)__('Order Date: %1', $orderDate), + ], + ]; + foreach ($templates as $key => $data) { + $this->assertStringContainsString( + $data['expected_data'], + strip_tags($block->setTemplate($data['template'])->toHtml()), + sprintf('%s wasn\'t found.', $key) + ); + } + } + + /** + * Assert order information block. + * + * @param OrderInterface $order + * @param string $html + * @return void + */ + private function assertOrderInformation(OrderInterface $order, string $html): void + { + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + "//div[contains(@class, 'block-order-details-view')]" + . "//strong[contains(text(), '" . __('Order Information') . "')]", + $html + ), + __('Order Information') . ' title wasn\'t found.' + ); + foreach ([$order->getShippingAddress(), $order->getBillingAddress()] as $address) { + $addressBoxXpath = sprintf("//div[contains(@class, 'box-order-%s-address')]", $address->getAddressType()) + . "//address[contains(., '%s')]"; + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath(sprintf($addressBoxXpath, $address->getName()), $html), + sprintf('Customer name for %s address wasn\'t found.', $address->getAddressType()) + ); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + sprintf( + $addressBoxXpath, + $this->countryFactory->create()->loadByCode($address->getData('country_id'))->getName() + ), + $html + ), + sprintf('Country for %s address wasn\'t found.', $address->getAddressType()) + ); + $attributes = ['company', 'street', 'city', 'region', 'postcode', 'telephone']; + foreach ($attributes as $key) { + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath(sprintf($addressBoxXpath, $address->getData($key)), $html), + sprintf('%s for %s address wasn\'t found.', $key, $address->getAddressType()) + ); + } + } + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + sprintf( + "//div[contains(@class, 'box-order-shipping-method') and contains(.//strong, '%s')]" + . "//div[contains(text(), '%s')]", + __('Shipping Method'), + $order->getShippingDescription() + ), + $html + ), + 'Shipping method for order wasn\'t found.' + ); + } + + /** + * Register order in registry. + * + * @param OrderInterface $order + * @return void + */ + private function registerOrder(OrderInterface $order): void + { + $this->registry->unregister('current_order'); + $this->registry->register('current_order', $order); + } + + /** + * Register creditmemo in registry. + * + * @param CreditmemoInterface $creditmemo + * @return void + */ + private function registerCreditmemo(CreditmemoInterface $creditmemo): void + { + $this->registry->unregister('current_creditmemo'); + $this->registry->register('current_creditmemo', $creditmemo); + } + + /** + * Render print creditmemo block. + * + * @return string + */ + private function renderPrintCreditmemoBlock(): string + { + $page = $this->pageFactory->create(); + $page->addHandle([ + 'default', + 'sales_order_printcreditmemo', + ]); + $page->getLayout()->generateXml(); + $printCreditmemoBlock = $page->getLayout()->getBlock('sales.order.print.creditmemo'); + $this->assertNotFalse($printCreditmemoBlock); + + return $printCreditmemoBlock->toHtml(); + } } diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintOrder/InvoiceTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintOrder/InvoiceTest.php index 2ee19b1188075..5bdd9aa0f3d1c 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintOrder/InvoiceTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintOrder/InvoiceTest.php @@ -3,41 +3,268 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Block\Order\PrintOrder; -class InvoiceTest extends \PHPUnit\Framework\TestCase +use Magento\Directory\Model\CountryFactory; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\Framework\View\Element\Text; +use Magento\Framework\View\LayoutInterface; +use Magento\Framework\View\Result\PageFactory; +use Magento\Sales\Api\Data\InvoiceInterface; +use Magento\Sales\Api\Data\InvoiceInterfaceFactory; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\OrderInterfaceFactory; +use Magento\Sales\Api\Data\OrderPaymentInterfaceFactory; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\Xpath; +use PHPUnit\Framework\TestCase; + +/** + * Tests for print invoice block. + * + * @magentoAppArea frontend + * @magentoDbIsolation enabled + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class InvoiceTest extends TestCase { + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var Registry */ + private $registry; + + /** @var LayoutInterface */ + private $layout; + + /** @var OrderInterfaceFactory */ + private $orderFactory; + + /** @var InvoiceInterfaceFactory */ + private $invoiceFactory; + + /** @var PageFactory */ + private $pageFactory; + + /** @var CountryFactory */ + private $countryFactory; + + /** @var OrderPaymentInterfaceFactory */ + private $orderPaymentFactory; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->registry = $this->objectManager->get(Registry::class); + $this->layout = $this->objectManager->get(LayoutInterface::class); + $this->orderFactory = $this->objectManager->get(OrderInterfaceFactory::class); + $this->invoiceFactory = $this->objectManager->get(InvoiceInterfaceFactory::class); + $this->pageFactory = $this->objectManager->get(PageFactory::class); + $this->countryFactory = $this->objectManager->get(CountryFactory::class); + $this->orderPaymentFactory = $this->objectManager->create(OrderPaymentInterfaceFactory::class); + } + + /** + * @inheritdoc + */ + protected function tearDown(): void + { + $this->registry->unregister('current_order'); + $this->registry->unregister('current_invoice'); + + parent::tearDown(); + } + /** * @magentoAppIsolation enabled + * + * @return void */ - public function testGetInvoiceTotalsHtml() + public function testGetInvoiceTotalsHtml(): void { - $order = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Sales\Model\Order::class); - /** @var $objectManager \Magento\TestFramework\ObjectManager */ - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $objectManager->get(\Magento\Framework\Registry::class)->register('current_order', $order); - $payment = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Sales\Model\Order\Payment::class - ); + $order = $this->orderFactory->create(); + $this->registerOrder($order); + $payment = $this->orderPaymentFactory->create(); $payment->setMethod('checkmo'); $order->setPayment($payment); - - $layout = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - \Magento\Framework\View\LayoutInterface::class - ); - $block = $layout->createBlock(\Magento\Sales\Block\Order\PrintOrder\Invoice::class, 'block'); - $childBlock = $layout->addBlock(\Magento\Framework\View\Element\Text::class, 'invoice_totals', 'block'); - + $block = $this->layout->createBlock(Invoice::class, 'block'); + $childBlock = $this->layout->addBlock(Text::class, 'invoice_totals', 'block'); $expectedHtml = 'Any html'; - $invoice = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Sales\Model\Order\Invoice::class - ); + $invoice = $this->invoiceFactory->create(); $this->assertEmpty($childBlock->getInvoice()); $this->assertNotEquals($expectedHtml, $block->getInvoiceTotalsHtml($invoice)); - $childBlock->setText($expectedHtml); $actualHtml = $block->getInvoiceTotalsHtml($invoice); $this->assertSame($invoice, $childBlock->getInvoice()); $this->assertEquals($expectedHtml, $actualHtml); } + + /** + * @magentoDataFixture Magento/Sales/_files/invoices_for_items.php + * + * @return void + */ + public function testPrintInvoice(): void + { + $order = $this->orderFactory->create()->loadByIncrementId('100000555'); + $invoice = $order->getInvoiceCollection()->getFirstItem(); + $this->assertNotNull($invoice->getId()); + $this->registerOrder($order); + $this->registerInvoice($invoice); + $blockHtml = $this->renderPrintInvoiceBlock(); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + sprintf( + "//div[contains(@class, 'order-title')]/strong[contains(text(), '%s')]", + __('Invoice #%1', (int)$invoice->getIncrementId()) + ), + $blockHtml + ), + sprintf('Title for %s was not found.', __('Invoice #%1', (int)$invoice->getIncrementId())) + ); + $this->assertOrderInformation($order, $blockHtml); + } + + /** + * @magentoDataFixture Magento/Sales/_files/invoices_for_items.php + * + * @return void + */ + public function testOrderInformation(): void + { + $order = $this->orderFactory->create()->loadByIncrementId('100000555'); + $this->registerOrder($order); + $block = $this->layout->createBlock(Invoice::class); + $orderDate = $block->formatDate($order->getCreatedAt(), \IntlDateFormatter::LONG); + $templates = [ + 'Order status' => [ + 'template' => 'Magento_Sales::order/order_status.phtml', + 'expected_data' => (string)__($order->getStatusLabel()), + ], + 'Order date' => [ + 'template' => 'Magento_Sales::order/order_date.phtml', + 'expected_data' => (string)__('Order Date: %1', $orderDate), + ], + ]; + foreach ($templates as $key => $data) { + $this->assertStringContainsString( + $data['expected_data'], + strip_tags($block->setTemplate($data['template'])->toHtml()), + sprintf('%s wasn\'t found.', $key) + ); + } + } + + /** + * Assert order information block. + * + * @param OrderInterface $order + * @param string $html + * @return void + */ + private function assertOrderInformation(OrderInterface $order, string $html): void + { + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + "//div[contains(@class, 'block-order-details-view')]" + . "//strong[contains(text(), '" . __('Order Information') . "')]", + $html + ), + __('Order Information') . ' title wasn\'t found.' + ); + foreach ([$order->getShippingAddress(), $order->getBillingAddress()] as $address) { + $addressBoxXpath = sprintf("//div[contains(@class, 'box-order-%s-address')]", $address->getAddressType()) + . "//address[contains(., '%s')]"; + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath(sprintf($addressBoxXpath, $address->getName()), $html), + sprintf('Customer name for %s address wasn\'t found.', $address->getAddressType()) + ); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + sprintf( + $addressBoxXpath, + $this->countryFactory->create()->loadByCode($address->getData('country_id'))->getName() + ), + $html + ), + sprintf('Country for %s address wasn\'t found.', $address->getAddressType()) + ); + $attributes = ['company', 'street', 'city', 'region', 'postcode', 'telephone']; + foreach ($attributes as $key) { + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath(sprintf($addressBoxXpath, $address->getData($key)), $html), + sprintf('%s for %s address wasn\'t found.', $key, $address->getAddressType()) + ); + } + } + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + sprintf( + "//div[contains(@class, 'box-order-shipping-method') and contains(.//strong, '%s')]" + . "//div[contains(text(), '%s')]", + __('Shipping Method'), + $order->getShippingDescription() + ), + $html + ), + 'Shipping method for order wasn\'t found.' + ); + } + + /** + * Register order in registry. + * + * @param OrderInterface $order + * @return void + */ + private function registerOrder(OrderInterface $order): void + { + $this->registry->unregister('current_order'); + $this->registry->register('current_order', $order); + } + + /** + * Register invoice in registry. + * + * @param InvoiceInterface $invoice + * @return void + */ + private function registerInvoice(InvoiceInterface $invoice): void + { + $this->registry->unregister('current_invoice'); + $this->registry->register('current_invoice', $invoice); + } + + /** + * Render print invoice block. + * + * @return string + */ + private function renderPrintInvoiceBlock(): string + { + $page = $this->pageFactory->create(); + $page->addHandle([ + 'default', + 'sales_order_printinvoice', + ]); + $page->getLayout()->generateXml(); + $printInvoiceBlock = $page->getLayout()->getBlock('sales.order.print.invoice'); + $this->assertNotFalse($printInvoiceBlock); + + return $printInvoiceBlock->toHtml(); + } } diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintOrder/ShipmentTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintOrder/ShipmentTest.php new file mode 100644 index 0000000000000..434dacec5c6b8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintOrder/ShipmentTest.php @@ -0,0 +1,237 @@ +objectManager = Bootstrap::getObjectManager(); + $this->registry = $this->objectManager->get(Registry::class); + $this->layout = $this->objectManager->get(LayoutInterface::class); + $this->orderFactory = $this->objectManager->get(OrderInterfaceFactory::class); + $this->pageFactory = $this->objectManager->get(PageFactory::class); + $this->countryFactory = $this->objectManager->get(CountryFactory::class); + } + + /** + * @inheritdoc + */ + protected function tearDown(): void + { + $this->registry->unregister('current_order'); + $this->registry->unregister('current_shipment'); + + parent::tearDown(); + } + + /** + * @magentoDataFixture Magento/Sales/_files/shipment_for_two_items.php + * + * @return void + */ + public function testPrintShipment(): void + { + $order = $this->orderFactory->create()->loadByIncrementId('100000555'); + $this->registerOrder($order); + $shipment = $order->getShipmentsCollection()->getFirstItem(); + $this->assertNotNull($shipment->getId()); + $this->registerOrder($order); + $this->registerShipment($shipment); + $blockHtml = $this->renderPrintShipmentBlock(); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + sprintf( + "//div[contains(@class, 'order-title')]/strong[contains(text(), '%s')]", + __('Shipment #%1', $shipment->getIncrementId()) + ), + $blockHtml + ), + sprintf('Title for %s was not found.', __('Shipment #%1', $shipment->getIncrementId())) + ); + $this->assertOrderInformation($order, $blockHtml); + } + + /** + * @magentoDataFixture Magento/Sales/_files/shipment_for_order_with_customer.php + * + * @return void + */ + public function testOrderInformation(): void + { + $order = $this->orderFactory->create()->loadByIncrementId('100000001'); + $this->registerOrder($order); + $block = $this->layout->createBlock(Shipment::class); + $orderDate = $block->formatDate($order->getCreatedAt(), \IntlDateFormatter::LONG); + $templates = [ + 'Order status' => [ + 'template' => 'Magento_Sales::order/order_status.phtml', + 'expected_data' => (string)__($order->getStatusLabel()), + ], + 'Order date' => [ + 'template' => 'Magento_Sales::order/order_date.phtml', + 'expected_data' => (string)__('Order Date: %1', $orderDate), + ], + ]; + foreach ($templates as $key => $data) { + $this->assertStringContainsString( + $data['expected_data'], + strip_tags($block->setTemplate($data['template'])->toHtml()), + sprintf('%s wasn\'t found.', $key) + ); + } + } + + /** + * Assert order information block. + * + * @param OrderInterface $order + * @param string $html + * @return void + */ + private function assertOrderInformation(OrderInterface $order, string $html): void + { + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + "//div[contains(@class, 'block-order-details-view')]" + . "//strong[contains(text(), '" . __('Order Information') . "')]", + $html + ), + __('Order Information') . ' title wasn\'t found.' + ); + foreach ([$order->getShippingAddress(), $order->getBillingAddress()] as $address) { + $addressBoxXpath = ($address->getAddressType() == 'shipping') + ? "//div[contains(@class, 'box-order-shipping-address')]//address[contains(., '%s')]" + : "//div[contains(@class, 'box-order-billing-method')]//address[contains(., '%s')]"; + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath(sprintf($addressBoxXpath, $address->getName()), $html), + sprintf('Customer name for %s address wasn\'t found.', $address->getAddressType()) + ); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + sprintf( + $addressBoxXpath, + $this->countryFactory->create()->loadByCode($address->getData('country_id'))->getName() + ), + $html + ), + sprintf('Country for %s address wasn\'t found.', $address->getAddressType()) + ); + $attributes = ['company', 'street', 'city', 'region', 'postcode', 'telephone']; + foreach ($attributes as $key) { + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath(sprintf($addressBoxXpath, $address->getData($key)), $html), + sprintf('%s for %s address wasn\'t found.', $key, $address->getAddressType()) + ); + } + } + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + sprintf( + "//div[contains(@class, 'box-order-shipping-method') and contains(.//strong, '%s')]" + . "//div[contains(text(), '%s')]", + __('Shipping Method'), + $order->getShippingDescription() + ), + $html + ), + 'Shipping method for order wasn\'t found.' + ); + } + + /** + * Register order in registry. + * + * @param OrderInterface $order + * @return void + */ + private function registerOrder(OrderInterface $order): void + { + $this->registry->unregister('current_order'); + $this->registry->register('current_order', $order); + } + + /** + * Register shipment in registry. + * + * @param ShipmentInterface $shipment + * @return void + */ + private function registerShipment(ShipmentInterface $shipment): void + { + $this->registry->unregister('current_shipment'); + $this->registry->register('current_shipment', $shipment); + } + + /** + * Render print shipment block. + * + * @return string + */ + private function renderPrintShipmentBlock(): string + { + $page = $this->pageFactory->create(); + $page->addHandle([ + 'default', + 'sales_order_printshipment', + ]); + $page->getLayout()->generateXml(); + $printShipmentBlock = $page->getLayout()->getBlock('sales.order.print.shipment'); + $this->assertNotFalse($printShipmentBlock); + + return $printShipmentBlock->toHtml(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintShipmentTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintShipmentTest.php new file mode 100644 index 0000000000000..5a6dc50bac0c2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintShipmentTest.php @@ -0,0 +1,93 @@ +objectManager = Bootstrap::getObjectManager(); + $this->registry = $this->objectManager->get(Registry::class); + $this->layout = $this->objectManager->get(LayoutInterface::class); + $this->orderFactory = $this->objectManager->get(OrderInterfaceFactory::class); + } + + /** + * @magentoDataFixture Magento/Sales/_files/shipment_for_order_with_customer.php + * + * @return void + */ + public function testOrderInformation(): void + { + $order = $this->orderFactory->create()->loadByIncrementId('100000001'); + $this->registerOrder($order); + $block = $this->layout->createBlock(PrintShipment::class); + $orderDate = $block->formatDate($order->getCreatedAt(), \IntlDateFormatter::LONG); + $templates = [ + 'Order status' => [ + 'template' => 'Magento_Sales::order/order_status.phtml', + 'expected_data' => (string)__($order->getStatusLabel()), + ], + 'Order date' => [ + 'template' => 'Magento_Sales::order/order_date.phtml', + 'expected_data' => (string)__('Order Date: %1', $orderDate), + ], + ]; + foreach ($templates as $key => $data) { + $this->assertStringContainsString( + $data['expected_data'], + strip_tags($block->setTemplate($data['template'])->toHtml()), + sprintf('%s wasn\'t found.', $key) + ); + } + } + + /** + * Register order in registry. + * + * @param OrderInterface $order + * @return void + */ + private function registerOrder(OrderInterface $order): void + { + $this->registry->unregister('current_order'); + $this->registry->register('current_order', $order); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/shipment_for_two_items.php b/dev/tests/integration/testsuite/Magento/Sales/_files/shipment_for_two_items.php new file mode 100644 index 0000000000000..e77134b1e5d4b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/shipment_for_two_items.php @@ -0,0 +1,22 @@ +requireDataFixture('Magento/Sales/_files/customer_order_with_two_items.php'); + +$objectManager = Bootstrap::getObjectManager(); +/** @var Order $order */ +$order = $objectManager->get(OrderInterfaceFactory::class)->create()->loadByIncrementId('100000555'); +/** @var ShipOrderInterface $invoiceOrder */ +$shipOrder = $objectManager->get(ShipOrderInterface::class); + +$shipOrder->execute($order->getId()); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/shipment_for_two_items_rollback.php b/dev/tests/integration/testsuite/Magento/Sales/_files/shipment_for_two_items_rollback.php new file mode 100644 index 0000000000000..438b097dd141b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/shipment_for_two_items_rollback.php @@ -0,0 +1,10 @@ +requireDataFixture('Magento/Sales/_files/customer_order_with_two_items_rollback.php'); From e33a69d2bd21d37936f879d3466912e32c25a8c7 Mon Sep 17 00:00:00 2001 From: DmytroPaidych Date: Tue, 2 Jun 2020 10:36:56 +0300 Subject: [PATCH 18/24] MC-34610: Delete simple, configurable, bundle products --- .../Bundle/Product/Edit/MassDeleteTest.php | 32 ++++ .../Adminhtml/Product/MassDeleteTest.php | 101 ++++++++++ .../Catalog/Model/ProductRepositoryTest.php | 180 +++++++++++------- .../Adminhtml/Product/MassDeleteTest.php | 31 +++ .../Product/Plugin/RemoveQuoteItemsTest.php | 75 ++++++++ .../Model/ResourceModel/ProductTest.php | 71 +++++++ 6 files changed, 417 insertions(+), 73 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/MassDeleteTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/MassDeleteTest.php create mode 100644 dev/tests/integration/testsuite/Magento/ConfigurableProduct/Controller/Adminhtml/Product/MassDeleteTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Quote/Model/Product/Plugin/RemoveQuoteItemsTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/Plugin/Model/ResourceModel/ProductTest.php diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/MassDeleteTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/MassDeleteTest.php new file mode 100644 index 0000000000000..9343e6201f7ff --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/MassDeleteTest.php @@ -0,0 +1,32 @@ +productRepository->get('bundle-product-checkbox-required-option'); + $this->dispatchMassDeleteAction([$product->getId()]); + $this->assertSuccessfulDeleteProducts(1); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/MassDeleteTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/MassDeleteTest.php new file mode 100644 index 0000000000000..6384883c56c58 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/MassDeleteTest.php @@ -0,0 +1,101 @@ +productRepository = $this->_objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/multiple_products.php + * + * @return void + */ + public function testDeleteSimpleProductViaMassAction(): void + { + $productIds = [10, 11, 12]; + $this->dispatchMassDeleteAction($productIds); + $this->assertSuccessfulDeleteProducts(count($productIds)); + } + + /** + * @return void + */ + public function testDeleteNotExistingProductViaMassAction(): void + { + $this->dispatchMassDeleteAction([989]); + $this->assertSessionMessages($this->isEmpty(), MessageInterface::TYPE_ERROR); + $this->assertRedirect($this->stringContains('backend/catalog/product/index')); + } + + /** + * @return void + */ + public function testMassDeleteWithoutProductIds(): void + { + $this->markTestSkipped('Test is blocked by issue MC-34495'); + $this->dispatchMassDeleteAction(); + $this->assertSessionMessages( + $this->equalTo('An item needs to be selected. Select and try again.'), + MessageInterface::TYPE_ERROR + ); + $this->assertRedirect($this->stringContains('backend/catalog/product/index')); + } + + /** + * Assert successful delete products. + * + * @param int $productCount + * @return void + */ + protected function assertSuccessfulDeleteProducts(int $productCount): void + { + $this->assertSessionMessages( + $this->equalTo([(string)__('A total of %1 record(s) have been deleted.', $productCount)]), + MessageInterface::TYPE_SUCCESS + ); + $this->assertRedirect($this->stringContains('backend/catalog/product/index')); + } + + /** + * Dispatch mass delete action. + * + * @param array $productIds + * @return void + */ + protected function dispatchMassDeleteAction(array $productIds = []): void + { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); + $this->getRequest()->setParams(['selected' => $productIds, 'namespace' => 'product_listing']); + $this->dispatch('backend/catalog/product/massDelete/'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php index 0fe3ef55455d2..af7a027367fff 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php @@ -7,15 +7,22 @@ namespace Magento\Catalog\Model; -use Magento\Backend\Model\Auth; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Media\ConfigInterface; use Magento\Catalog\Model\ResourceModel\Product as ProductResource; use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Exception\CouldNotSaveException; +use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\StateException; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\WriteInterface; +use Magento\Framework\ObjectManagerInterface; use Magento\TestFramework\Catalog\Model\ProductLayoutUpdateManager; use Magento\TestFramework\Helper\Bootstrap; -use Magento\TestFramework\Bootstrap as TestBootstrap; -use Magento\Framework\Acl\Builder; +use PHPUnit\Framework\TestCase; /** * Provide tests for ProductRepository model. @@ -24,8 +31,13 @@ * @magentoAppIsolation enabled * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class ProductRepositoryTest extends \PHPUnit\Framework\TestCase +class ProductRepositoryTest extends TestCase { + /** + * @var ObjectManagerInterface + */ + private $objectManager; + /** * Test subject. * @@ -54,40 +66,68 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase private $layoutManager; /** - * Sets up common objects + * @var ConfigInterface + */ + private $mediaConfig; + + /** + * @var WriteInterface + */ + private $mediaDirectory; + + /** + * @var array + */ + private $productSkusToDelete = []; + + /** + * @inheritdoc */ protected function setUp(): void { - $this->productRepository = Bootstrap::getObjectManager()->create(ProductRepositoryInterface::class); - $this->searchCriteriaBuilder = Bootstrap::getObjectManager()->get(SearchCriteriaBuilder::class); - $this->productFactory = Bootstrap::getObjectManager()->get(ProductFactory::class); - $this->productResource = Bootstrap::getObjectManager()->get(ProductResource::class); - $this->layoutManager = Bootstrap::getObjectManager()->get(ProductLayoutUpdateManager::class); + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $this->productFactory = $this->objectManager->get(ProductFactory::class); + $this->productResource = $this->objectManager->get(ProductResource::class); + $this->layoutManager = $this->objectManager->get(ProductLayoutUpdateManager::class); + $this->mediaConfig = $this->objectManager->get(ConfigInterface::class); + $this->mediaDirectory = $this->objectManager->get(Filesystem::class) + ->getDirectoryWrite(DirectoryList::MEDIA); } /** - * Create new subject instance. - * - * @return ProductRepositoryInterface + * @inheritdoc */ - private function createRepo(): ProductRepositoryInterface + protected function tearDown(): void { - return Bootstrap::getObjectManager()->create(ProductRepositoryInterface::class); + foreach ($this->productSkusToDelete as $productSku) { + try { + $this->productRepository->deleteById($productSku); + } catch (NoSuchEntityException $e) { + //Product already removed + } + } + + parent::tearDown(); } /** * Checks filtering by store_id * * @magentoDataFixture Magento/Catalog/Model/ResourceModel/_files/product_simple.php + * @return void */ - public function testFilterByStoreId() + public function testFilterByStoreId(): void { $searchCriteria = $this->searchCriteriaBuilder ->addFilter('store_id', '1', 'eq') ->create(); $list = $this->productRepository->getList($searchCriteria); $count = $list->getTotalCount(); - $this->assertGreaterThanOrEqual(1, $count); } @@ -99,13 +139,11 @@ public function testFilterByStoreId() * @magentoDataFixture Magento/Catalog/_files/product_simple.php * @dataProvider skuDataProvider */ - public function testGetProduct(string $sku) : void + public function testGetProduct(string $sku): void { $expectedSku = 'simple'; $product = $this->productRepository->get($sku); - - self::assertNotEmpty($product); - self::assertEquals($expectedSku, $product->getSku()); + $this->assertEquals($expectedSku, $product->getSku()); } /** @@ -127,45 +165,29 @@ public function skuDataProvider(): array * * @magentoDataFixture Magento/Catalog/_files/product_simple_with_image.php * - * @throws \Magento\Framework\Exception\CouldNotSaveException - * @throws \Magento\Framework\Exception\InputException - * @throws \Magento\Framework\Exception\StateException + * @return void + * @throws CouldNotSaveException + * @throws InputException + * @throws StateException */ public function testSaveProductWithGalleryImage(): void { - /** @var $mediaConfig \Magento\Catalog\Model\Product\Media\Config */ - $mediaConfig = Bootstrap::getObjectManager() - ->get(\Magento\Catalog\Model\Product\Media\Config::class); - - /** @var $mediaDirectory \Magento\Framework\Filesystem\Directory\WriteInterface */ - $mediaDirectory = Bootstrap::getObjectManager() - ->get(\Magento\Framework\Filesystem::class) - ->getDirectoryWrite(\Magento\Framework\App\Filesystem\DirectoryList::MEDIA); - - $product = Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); - $product->load(1); - - $path = $mediaConfig->getBaseMediaPath() . '/magento_image.jpg'; - $absolutePath = $mediaDirectory->getAbsolutePath() . $path; + $product = $this->productRepository->get('simple'); + $path = $this->mediaConfig->getBaseMediaPath() . '/magento_image.jpg'; + $absolutePath = $this->mediaDirectory->getAbsolutePath() . $path; $product->addImageToMediaGallery( $absolutePath, [ - 'image', - 'small_image', + 'image', + 'small_image', ], false, false ); - - /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ - $productRepository = Bootstrap::getObjectManager() - ->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); - $productRepository->save($product); - + $this->productRepository->save($product); $gallery = $product->getData('media_gallery'); $this->assertArrayHasKey('images', $gallery); $images = array_values($gallery['images']); - $this->assertNotEmpty($gallery); $this->assertTrue(isset($images[0]['file'])); $this->assertStringStartsWith('/m/a/magento_image', $images[0]['file']); @@ -179,58 +201,70 @@ public function testSaveProductWithGalleryImage(): void * Test Product Repository can change(update) "sku" for given product. * * @magentoDataFixture Magento/Catalog/_files/product_simple.php - * @magentoDbIsolation enabled * @magentoAppArea adminhtml + * @return void */ - public function testUpdateProductSku() + public function testUpdateProductSku(): void { $newSku = 'simple-edited'; $productId = $this->productResource->getIdBySku('simple'); $initialProduct = $this->productFactory->create(); $this->productResource->load($initialProduct, $productId); - $initialProduct->setSku($newSku); $this->productRepository->save($initialProduct); - + $this->productSkusToDelete[] = $newSku; $updatedProduct = $this->productFactory->create(); $this->productResource->load($updatedProduct, $productId); - self::assertSame($newSku, $updatedProduct->getSku()); - - //clean up. - $this->productRepository->delete($updatedProduct); + $this->assertSame($newSku, $updatedProduct->getSku()); } /** * Test that custom layout file attribute is saved. * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php * @return void * @throws \Throwable - * @magentoDataFixture Magento/Catalog/_files/product_simple.php - * @magentoDbIsolation enabled - * @magentoAppIsolation enabled */ public function testCustomLayout(): void { - //New valid value - $repo = $this->createRepo(); - $product = $repo->get('simple'); + $product = $this->productRepository->get('simple'); $newFile = 'test'; $this->layoutManager->setFakeFiles((int)$product->getId(), [$newFile]); $product->setCustomAttribute('custom_layout_update_file', $newFile); - $repo->save($product); - $repo = $this->createRepo(); - $product = $repo->get('simple'); + $this->productRepository->save($product); + $product = $this->productRepository->get('simple'); $this->assertEquals($newFile, $product->getCustomAttribute('custom_layout_update_file')->getValue()); - - //Setting non-existent value $newFile = 'does not exist'; $product->setCustomAttribute('custom_layout_update_file', $newFile); - $caughtException = false; - try { - $repo->save($product); - } catch (LocalizedException $exception) { - $caughtException = true; - } - $this->assertTrue($caughtException); + $this->expectException(LocalizedException::class); + $this->productRepository->save($product); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/product_simple_duplicated.php + * @magentoAppArea adminhtml + * + * @return void + */ + public function testDeleteByIdSimpleProduct(): void + { + $productSku = 'simple-1'; + $result = $this->productRepository->deleteById($productSku); + $this->assertTrue($result); + $this->assertProductNotExist($productSku); + } + + /** + * Assert that product does not exist. + * + * @param string $sku + * @return void + */ + private function assertProductNotExist(string $sku): void + { + $this->expectExceptionObject(new NoSuchEntityException( + __("The product that was requested doesn't exist. Verify the product and try again.") + )); + $this->productRepository->get($sku); } } diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Controller/Adminhtml/Product/MassDeleteTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Controller/Adminhtml/Product/MassDeleteTest.php new file mode 100644 index 0000000000000..4f003e26db43f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Controller/Adminhtml/Product/MassDeleteTest.php @@ -0,0 +1,31 @@ +productRepository->get('configurable'); + $this->dispatchMassDeleteAction([$product->getId()]); + $this->assertSuccessfulDeleteProducts(1); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/Product/Plugin/RemoveQuoteItemsTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/Product/Plugin/RemoveQuoteItemsTest.php new file mode 100644 index 0000000000000..ccc146c459b07 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/Product/Plugin/RemoveQuoteItemsTest.php @@ -0,0 +1,75 @@ +objectManager = Bootstrap::getObjectManager(); + $this->productResoure = $this->objectManager->get(ProductResourceModel::class); + $this->getQuoteByReservedOrderId = $this->objectManager->get(GetQuoteByReservedOrderId::class); + } + + /** + * @return void + */ + public function testPluginIsRegistered(): void + { + $pluginInfo = $this->objectManager->get(PluginList::class)->get(ProductResourceModel::class); + $this->assertSame( + RemoveQuoteItems::class, + $pluginInfo['clean_quote_items_after_product_delete']['instance'] + ); + } + + /** + * @magentoDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * + * @return void + */ + public function testDeleteProduct(): void + { + $quote = $this->getQuoteByReservedOrderId->execute('test_order_with_simple_product_without_address'); + $this->assertNotNull($quote); + $quoteItems = $quote->getItems(); + $quoteItem = current($quoteItems); + $this->assertNotNull($quoteItem); + $this->productResoure->delete($quoteItem->getProduct()); + $quote = $this->getQuoteByReservedOrderId->execute('test_order_with_simple_product_without_address'); + $this->assertNotNull($quote); + $this->assertEmpty($quote->getItems()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Plugin/Model/ResourceModel/ProductTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Plugin/Model/ResourceModel/ProductTest.php new file mode 100644 index 0000000000000..ecf932ce13b74 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Plugin/Model/ResourceModel/ProductTest.php @@ -0,0 +1,71 @@ +objectManager = Bootstrap::getObjectManager(); + $this->productResoure = $this->objectManager->get(ProductResourceModel::class); + $this->getWishlistByCustomerId = $this->objectManager->get(GetWishlistByCustomerId::class); + } + + /** + * @return void + */ + public function testPluginIsRegistered(): void + { + $pluginInfo = $this->objectManager->get(PluginList::class)->get(ProductResourceModel::class); + $this->assertSame( + Product::class, + $pluginInfo['cleanups_wishlist_item_after_product_delete']['instance'] + ); + } + + /** + * @magentoDataFixture Magento/Wishlist/_files/wishlist.php + * + * @return void + */ + public function testDeleteProduct(): void + { + $item = $this->getWishlistByCustomerId->getItemBySku(1, 'simple'); + $this->assertNotNull($item); + $this->productResoure->delete($item->getProduct()); + $this->assertNull($this->getWishlistByCustomerId->getItemBySku(1, 'simple')); + } +} From 3eb9a2d3c062685791fd3dcbad8fad7c614b05e6 Mon Sep 17 00:00:00 2001 From: Yurii Sapiha Date: Tue, 2 Jun 2020 11:05:22 +0300 Subject: [PATCH 19/24] MC-33396: Customer reward points --- .../Customer/Model/DeleteCustomer.php | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 dev/tests/integration/framework/Magento/TestFramework/Customer/Model/DeleteCustomer.php diff --git a/dev/tests/integration/framework/Magento/TestFramework/Customer/Model/DeleteCustomer.php b/dev/tests/integration/framework/Magento/TestFramework/Customer/Model/DeleteCustomer.php new file mode 100644 index 0000000000000..5158191efef22 --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/Customer/Model/DeleteCustomer.php @@ -0,0 +1,58 @@ +customerRepository = $customerRepository; + $this->registry = $registry; + } + + /** + * Delete customer by id or email + * + * @param int|string $id + * @return void + */ + public function execute($id): void + { + $isSecure = $this->registry->registry('isSecureArea'); + + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', true); + + try { + $customer = is_numeric($id) ? $this->customerRepository->getById($id) : $this->customerRepository->get($id); + $this->customerRepository->delete($customer); + } catch (NoSuchEntityException $e) { + //customer already deleted + } + + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', $isSecure); + } +} From 817f944b1dec169f9519a6c4b0790a6631e4acda Mon Sep 17 00:00:00 2001 From: Roman Zhupanyn Date: Tue, 2 Jun 2020 11:44:30 +0300 Subject: [PATCH 20/24] MC-34611: Admin: View and edit customer shopping cart --- .../Catalog/Helper/Product/CompositeTest.php | 128 +++++-- ...uote_with_items_simple_product_options.php | 103 ++++++ ..._items_simple_product_options_rollback.php | 34 ++ ..._quote_with_items_configurable_product.php | 69 ++++ ...th_items_configurable_product_rollback.php | 34 ++ .../Adminhtml/Edit/Tab/AbstractCartTest.php | 148 ++++++++ .../Edit/Tab/Cart/CartBundleTest.php | 67 ++++ .../Edit/Tab/Cart/CartConfigurableTest.php | 44 +++ .../Block/Adminhtml/Edit/Tab/CartTest.php | 102 ++---- .../View/Grid/Renderer/AbstractItemTest.php | 120 +++++++ .../Renderer/Item/ItemConfigurableTest.php | 43 +++ .../Edit/Tab/View/Grid/Renderer/ItemTest.php | 34 ++ .../Grid/Renderer/AbstractMultiactionTest.php | 110 ++++++ .../Multiaction/MultiactionBundleTest.php | 43 +++ .../MultiactionConfigurableTest.php | 43 +++ .../Grid/Renderer/MultiactionTest.php | 78 +++++ .../Product/Composite/Cart/ConfigureTest.php | 140 ++++++++ .../Product/Composite/Cart/UpdateTest.php | 325 ++++++++++++++++++ .../Cart/Product/Composite/CartTest.php | 105 ------ .../Controller/Adminhtml/Index/CartTest.php | 99 ++++++ .../Controller/Adminhtml/IndexTest.php | 11 - 21 files changed, 1655 insertions(+), 225 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Checkout/_files/customer_quote_with_items_simple_product_options.php create mode 100644 dev/tests/integration/testsuite/Magento/Checkout/_files/customer_quote_with_items_simple_product_options_rollback.php create mode 100644 dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/customer_quote_with_items_configurable_product.php create mode 100644 dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/customer_quote_with_items_configurable_product_rollback.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/AbstractCartTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart/CartBundleTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart/CartConfigurableTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Grid/Renderer/AbstractItemTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Grid/Renderer/Item/ItemConfigurableTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Grid/Renderer/ItemTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Grid/Renderer/AbstractMultiactionTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Grid/Renderer/Multiaction/MultiactionBundleTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Grid/Renderer/Multiaction/MultiactionConfigurableTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Grid/Renderer/MultiactionTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Cart/Product/Composite/Cart/ConfigureTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Cart/Product/Composite/Cart/UpdateTest.php delete mode 100644 dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Cart/Product/Composite/CartTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/CartTest.php diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Helper/Product/CompositeTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Helper/Product/CompositeTest.php index a558a99bd2f17..bc310f8bd65b5 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Helper/Product/CompositeTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Helper/Product/CompositeTest.php @@ -3,34 +3,51 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Catalog\Helper\Product; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product; use Magento\Customer\Controller\RegistryConstants; +use Magento\Framework\DataObject; +use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Registry; use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; /** * Test Composite */ -class CompositeTest extends \PHPUnit\Framework\TestCase +class CompositeTest extends TestCase { - /** - * @var Composite - */ - protected $helper; + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var Composite */ + private $helper; + + /** @var Registry */ + private $registry; + + /** @var ProductRepositoryInterface */ + private $productRepository; /** - * @var Registry + * @inheritdoc */ - protected $registry; - protected function setUp(): void { - $this->helper = Bootstrap::getObjectManager()->get(\Magento\Catalog\Helper\Product\Composite::class); - $this->registry = Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + $this->objectManager = Bootstrap::getObjectManager(); + $this->helper = $this->objectManager->get(Composite::class); + $this->registry = $this->objectManager->get(Registry::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); } + /** + * @inheritdoc + */ protected function tearDown(): void { $this->registry->unregister('composite_configure_result_error_message'); @@ -42,40 +59,85 @@ protected function tearDown(): void /** * @magentoDataFixture Magento/Catalog/_files/product_simple.php * @magentoDataFixture Magento/Customer/_files/customer.php + * @return void */ - public function testRenderConfigureResult() + public function testRenderConfigureResult(): void { - /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ - $productRepository = Bootstrap::getObjectManager()->create( - \Magento\Catalog\Api\ProductRepositoryInterface::class - ); - /** @var $product \Magento\Catalog\Model\Product */ - $product = $productRepository->get('simple'); - - $configureResult = new \Magento\Framework\DataObject(); + $product = $this->productRepository->get('simple'); + /** @var DataObject $buyRequest */ + $buyRequest = $this->objectManager->create(DataObject::class); + $buyRequest->setData(['qty' => 1]); + /** @var DataObject $configureResult */ + $configureResult = $this->objectManager->create(DataObject::class); $configureResult->setOk(true) ->setProductId($product->getId()) + ->setBuyRequest($buyRequest) ->setCurrentCustomerId(1); - $this->helper->renderConfigureResult($configureResult); + $resultLayout = $this->helper->renderConfigureResult($configureResult); - $customerId = $this->registry->registry(RegistryConstants::CURRENT_CUSTOMER_ID); - $this->assertEquals(1, $customerId); - $errorMessage = $this->registry->registry('composite_configure_result_error_message'); - $this->assertNull($errorMessage); + /** @var Product $preparedProduct */ + $preparedProduct = $this->registry->registry('product'); + $preparedCurrentProduct = $this->registry->registry('current_product'); + $this->assertTrue($preparedProduct && $preparedCurrentProduct); + $this->assertEquals(1, $this->registry->registry(RegistryConstants::CURRENT_CUSTOMER_ID)); + $this->assertNotNull($preparedProduct->getPreconfiguredValues()); + $this->assertContains( + 'CATALOG_PRODUCT_COMPOSITE_CONFIGURE', + $resultLayout->getLayout()->getUpdate()->getHandles() + ); + $this->assertContains( + 'catalog_product_view_type_' . $product->getTypeId(), + $resultLayout->getLayout()->getUpdate()->getHandles() + ); } - public function testRenderConfigureResultNotOK() + /** + * @dataProvider renderConfigureResultExceptionProvider + * @param array $data + * @param string $expectedErrorMessage + * @return void + */ + public function testRenderConfigureResultException(array $data, string $expectedErrorMessage): void { - $configureResult = new \Magento\Framework\DataObject(); - $configureResult->setError(true) - ->setMessage('Test Message'); + /** @var DataObject $configureResult */ + $configureResult = $this->objectManager->create(DataObject::class); + $configureResult->setData($data); - $this->helper->renderConfigureResult($configureResult); + $resultLayout = $this->helper->renderConfigureResult($configureResult); + + $this->assertEquals( + $expectedErrorMessage, + $this->registry->registry('composite_configure_result_error_message') + ); + $this->assertContains( + 'CATALOG_PRODUCT_COMPOSITE_CONFIGURE_ERROR', + $resultLayout->getLayout()->getUpdate()->getHandles() + ); + } - $customerId = $this->registry->registry(RegistryConstants::CURRENT_CUSTOMER_ID); - $this->assertNull($customerId); - $errorMessage = $this->registry->registry('composite_configure_result_error_message'); - $this->assertEquals('Test Message', $errorMessage); + /** + * Create render configure result exception provider + * + * @return array + */ + public function renderConfigureResultExceptionProvider(): array + { + return [ + 'error_true' => [ + 'data' => [ + 'error' => true, + 'message' => 'Test Message' + ], + 'expected_error_message' => 'Test Message', + ], + 'without_product' => [ + 'data' => [ + 'ok' => true, + ], + 'expected_error_message' => 'The product that was requested doesn\'t exist.' + . ' Verify the product and try again.', + ], + ]; } } diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/customer_quote_with_items_simple_product_options.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/customer_quote_with_items_simple_product_options.php new file mode 100644 index 0000000000000..66d984301d14f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/customer_quote_with_items_simple_product_options.php @@ -0,0 +1,103 @@ +requireDataFixture('Magento/Catalog/_files/product_with_options.php'); +Resolver::getInstance()->requireDataFixture('Magento/Customer/_files/customer_with_uk_address.php'); + +/** @var ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +/** @var StoreManagerInterface $storeManager */ +$storeManager = $objectManager->get(StoreManagerInterface::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$productRepository->cleanCache(); +/** @var CustomerRepositoryInterface $customerRepository */ +$customerRepository = $objectManager->get(CustomerRepositoryInterface::class); +/** @var Quote $quote */ +$quote = $objectManager->get(QuoteFactory::class)->create(); +/** @var CartRepositoryInterface $quoteRepository */ +$quoteRepository = $objectManager->get(CartRepositoryInterface::class); + +$customer = $customerRepository->get('customer_uk_address@test.com'); +$product = $productRepository->get('simple'); +$options = []; +$dropDownValues = []; +$iDate = 1; +/** @var Option $option */ +foreach ($product->getOptions() as $option) { + switch ($option->getGroupByType()) { + case ProductCustomOptionInterface::OPTION_GROUP_SELECT: + if ($option->getType() == ProductCustomOptionInterface::OPTION_TYPE_DROP_DOWN) { + $dropDownValues = $option->getValues(); + $value = null; + } elseif ($option->getType() == ProductCustomOptionInterface::OPTION_TYPE_CHECKBOX) { + $value = array_keys($option->getValues()); + } else { + $value = (string)key($option->getValues()); + } + break; + case ProductCustomOptionInterface::OPTION_GROUP_DATE: + $value = [ + 'year' => 2013 + $iDate, + 'month' => 1 + $iDate, + 'day' => 1 + $iDate, + 'hour' => 10 + $iDate, + 'minute' => 30 + $iDate, + ]; + $iDate++; + break; + case ProductCustomOptionInterface::OPTION_GROUP_FILE: + $value = 'test.jpg'; + break; + default: + $value = 'test'; + break; + } + $options[$option->getId()] = $value; +} + +$itemsOptions = []; +/** @var Value $dropDownValue */ +foreach ($dropDownValues as $dropDownId => $dropDownValue) { + $options[$dropDownValue->getOption()->getId()] = $dropDownId; + $itemsOptions[$dropDownValue->getTitle()] = $options; +} + +$validatorFileMock = (new ValidatorFileMock())->getInstance(); +$objectManager->addSharedInstance($validatorFileMock, ValidatorFile::class); + +$quote->setStoreId($storeManager->getStore()->getId()) + ->assignCustomer($customer) + ->setReservedOrderId('customer_quote_product_custom_options'); + +/** @var DataObject $request */ +$requestInfo = $objectManager->create(DataObject::class); + +foreach ($itemsOptions as $itemOptions) { + $requestInfo->setData(['qty' => 1, 'options' => $itemOptions]); + $product = clone $product; + $quote->addProduct($product, $requestInfo); +} + +$quoteRepository->save($quote); +$objectManager->removeSharedInstance(ValidatorFile::class); diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/customer_quote_with_items_simple_product_options_rollback.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/customer_quote_with_items_simple_product_options_rollback.php new file mode 100644 index 0000000000000..5877e9a5ef975 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/customer_quote_with_items_simple_product_options_rollback.php @@ -0,0 +1,34 @@ +get(Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +$quote = $objectManager->get(GetQuoteByReservedOrderId::class)->execute('customer_quote_product_custom_options'); +if ($quote !== null) { + /** @var CartRepositoryInterface $quoteRepository */ + $quoteRepository = $objectManager->get(CartRepositoryInterface::class); + $quoteRepository->delete($quote); +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +Resolver::getInstance()->requireDataFixture('Magento/Customer/_files/customer_with_uk_address_rollback.php'); +Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/product_with_options_rollback.php'); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/customer_quote_with_items_configurable_product.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/customer_quote_with_items_configurable_product.php new file mode 100644 index 0000000000000..30a9a47f910d8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/customer_quote_with_items_configurable_product.php @@ -0,0 +1,69 @@ +requireDataFixture('Magento/ConfigurableProduct/_files/configurable_products.php'); +Resolver::getInstance()->requireDataFixture('Magento/Customer/_files/customer_with_uk_address.php'); + +/** @var ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +/** @var StoreManagerInterface $storeManager */ +$storeManager = $objectManager->get(StoreManagerInterface::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$productRepository->cleanCache(); +/** @var CustomerRepositoryInterface $customerRepository */ +$customerRepository = $objectManager->get(CustomerRepositoryInterface::class); +/** @var Quote $quote */ +$quote = $objectManager->get(QuoteFactory::class)->create(); +/** @var CartRepositoryInterface $quoteRepository */ +$quoteRepository = $objectManager->get(CartRepositoryInterface::class); +/** @var Config $eavConfig */ +$eavConfig = $objectManager->get(Config::class); +/** @var ProductAttribute $attribute */ +$attribute = $eavConfig->getAttribute(ProductAttributeInterface::ENTITY_TYPE_CODE, 'test_configurable'); + +$customer = $customerRepository->get('customer_uk_address@test.com'); +$quote->setStoreId($storeManager->getStore()->getId()) + ->setIsActive(true) + ->setIsMultiShipping(false) + ->setReservedOrderId('customer_quote_configurable_products') + ->assignCustomer($customer); + +$attributeOptions = $attribute->getOptions(); +unset($attributeOptions[0]); +$productConfigurable = $productRepository->get('configurable'); +/** @var DataObject $request */ +$request = $objectManager->create(DataObject::class); + +foreach ($attributeOptions as $attributeOption) { + $productConfigurable = clone $productConfigurable; + $request->setData( + [ + 'product_id' => $productConfigurable->getId(), + 'super_attribute' => [ + $attribute->getAttributeId() => $attributeOption->getValue() + ], + 'qty' => 1 + ] + ); + $quote->addProduct($productConfigurable, $request); +} +$quoteRepository->save($quote); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/customer_quote_with_items_configurable_product_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/customer_quote_with_items_configurable_product_rollback.php new file mode 100644 index 0000000000000..e5cd7637c6e55 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/customer_quote_with_items_configurable_product_rollback.php @@ -0,0 +1,34 @@ +get(Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +$quote = $objectManager->get(GetQuoteByReservedOrderId::class)->execute('customer_quote_configurable_products'); +if ($quote !== null) { + /** @var CartRepositoryInterface $quoteRepository */ + $quoteRepository = $objectManager->get(CartRepositoryInterface::class); + $quoteRepository->delete($quote); +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +Resolver::getInstance()->requireDataFixture('Magento/Customer/_files/customer_with_uk_address_rollback.php'); +Resolver::getInstance()->requireDataFixture('Magento/ConfigurableProduct/_files/configurable_products_rollback.php'); diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/AbstractCartTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/AbstractCartTest.php new file mode 100644 index 0000000000000..3681da9a10396 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/AbstractCartTest.php @@ -0,0 +1,148 @@ +objectManager = Bootstrap::getObjectManager(); + $this->registry = $this->objectManager->get(Registry::class); + $this->registerCustomerId(self::CUSTOMER_ID_VALUE); + $this->block = $this->objectManager->get(LayoutInterface::class)->createBlock(Cart::class); + $this->quoteRepository = $this->objectManager->get(CartRepositoryInterface::class); + $this->customerRepository = $this->objectManager->get(CustomerRepositoryInterface::class); + $this->quoteFactory = $this->objectManager->get(QuoteFactory::class); + } + + /** + * @inheritdoc + */ + public function tearDown(): void + { + $this->registry->unregister(RegistryConstants::CURRENT_CUSTOMER_ID); + } + + /** + * Check that the expected items of the shopping cart are in the block + * + * @param string $customerEmail + * @return void + */ + protected function processCheckQuoteItems(string $customerEmail): void + { + $customer = $this->customerRepository->get($customerEmail); + $this->registerCustomerId((int)$customer->getId()); + $this->block->toHtml(); + + $quoteItemIds = $this->getQuoteItemIds((int)$customer->getId()); + $this->assertCount( + count($quoteItemIds), + $this->block->getPreparedCollection(), + "Item's count in the customer cart grid block doesn't match expected count." + ); + $this->assertEmpty( + array_diff( + $this->block->getPreparedCollection()->getAllIds(), + $quoteItemIds + ), + "Items in the customer cart grid block doesn't match expected items." + ); + } + + /** + * Checks that customer's shopping cart block is empty + * + * @param string $customerEmail + * @return void + */ + protected function processCheckWithoutQuoteItems(string $customerEmail): void + { + $customer = $this->customerRepository->get($customerEmail); + $this->registerCustomerId((int)$customer->getId()); + $this->block->toHtml(); + + $this->assertCount( + 0, + $this->block->getPreparedCollection(), + "Item's count in the customer cart grid block doesn't match expected count." + ); + } + + /** + * Add customer id to registry. + * + * @param int $customerId + * @return void + */ + private function registerCustomerId(int $customerId): void + { + $this->registry->unregister(RegistryConstants::CURRENT_CUSTOMER_ID); + $this->registry->register(RegistryConstants::CURRENT_CUSTOMER_ID, $customerId); + } + + /** + * Get shopping cart quote item identifiers by customer id. + * + * @param int $customerId + * @return array + */ + private function getQuoteItemIds(int $customerId): array + { + $ids = []; + /** @var Quote $quote */ + $quote = $this->quoteRepository->getForCustomer($customerId); + /** @var Item $item */ + foreach ($quote->getItems() as $item) { + $ids[] = $item->getId(); + } + + return $ids; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart/CartBundleTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart/CartBundleTest.php new file mode 100644 index 0000000000000..22cb852c73f63 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart/CartBundleTest.php @@ -0,0 +1,67 @@ +quoteCollectionFactory = $this->objectManager->get(CollectionFactory::class); + } + + /** + * @inheritdoc + */ + public static function setUpBeforeClass(): void + { + $objectManager = Bootstrap::getObjectManager(); + /** @var Manager $moduleManager */ + $moduleManager = $objectManager->get(Manager::class); + //This check is needed because Customer independent of Magento_Bundle + if (!$moduleManager->isEnabled('Magento_Bundle')) { + self::markTestSkipped('Magento_Bundle module disabled.'); + } + } + + /** + * @magentoDataFixture Magento/Bundle/_files/quote_with_bundle_and_options.php + * @magentoDataFixture Magento/Customer/_files/customer.php + * @return void + */ + public function testBundleProductView(): void + { + $quoteCollection = $this->quoteCollectionFactory->create(); + $quoteCollection->addFieldToFilter('reserved_order_id', 'test_cart_with_bundle_and_options'); + /** @var Quote $quote */ + $quote = $quoteCollection->getFirstItem(); + $this->assertNotEmpty($quote->getId()); + $quote->setCustomerId(1); + $this->quoteRepository->save($quote); + $this->processCheckQuoteItems('customer@example.com'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart/CartConfigurableTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart/CartConfigurableTest.php new file mode 100644 index 0000000000000..613edc5aec4f9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart/CartConfigurableTest.php @@ -0,0 +1,44 @@ +get(Manager::class); + //This check is needed because Customer independent of Magento_ConfigurableProduct + if (!$moduleManager->isEnabled('Magento_ConfigurableProduct')) { + self::markTestSkipped('Magento_ConfigurableProduct module disabled.'); + } + } + + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/customer_quote_with_items_configurable_product.php + * @return void + */ + public function testConfigurableProductView(): void + { + $this->processCheckQuoteItems('customer_uk_address@test.com'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/CartTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/CartTest.php index b5abf1de5732b..df799a0878b59 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/CartTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/CartTest.php @@ -3,82 +3,39 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Customer\Block\Adminhtml\Edit\Tab; -use Magento\Backend\Block\Template\Context; use Magento\Backend\Model\Session\Quote as SessionQuote; -use Magento\Customer\Controller\RegistryConstants; -use Magento\Framework\ObjectManagerInterface; -use Magento\Framework\Registry; use Magento\Quote\Model\Quote; -use Magento\Store\Model\StoreManagerInterface; /** - * Magento\Customer\Block\Adminhtml\Edit\Tab\Cart + * Class checks customer's shopping cart block with simple product and simple product with options. * + * @see \Magento\Customer\Block\Adminhtml\Edit\Tab\Cart * @magentoAppArea adminhtml - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class CartTest extends \PHPUnit\Framework\TestCase +class CartTest extends AbstractCartTest { - const CUSTOMER_ID_VALUE = 1234; - - /** - * @var Context - */ - private $_context; - - /** - * @var Registry - */ - private $_coreRegistry; - - /** - * @var StoreManagerInterface - */ - private $_storeManager; - - /** - * @var Cart - */ - private $_block; - - /** - * @var ObjectManagerInterface - */ - private $_objectManager; - /** - * @inheritdoc + * @magentoDataFixture Magento/Checkout/_files/customer_quote_with_items_simple_product_options.php + * + * @return void */ - protected function setUp(): void + public function testProductOptionsView(): void { - $this->_objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - - $this->_storeManager = $this->_objectManager->get(\Magento\Store\Model\StoreManager::class); - $this->_context = $this->_objectManager->get( - \Magento\Backend\Block\Template\Context::class, - ['storeManager' => $this->_storeManager] - ); - - $this->_coreRegistry = $this->_objectManager->get(\Magento\Framework\Registry::class); - $this->_coreRegistry->register(RegistryConstants::CURRENT_CUSTOMER_ID, self::CUSTOMER_ID_VALUE); - - $this->_block = $this->_objectManager->get( - \Magento\Framework\View\LayoutInterface::class - )->createBlock( - \Magento\Customer\Block\Adminhtml\Edit\Tab\Cart::class, - '', - ['context' => $this->_context, 'registry' => $this->_coreRegistry] - ); + $this->processCheckQuoteItems('customer_uk_address@test.com'); } /** - * @inheritdoc + * @magentoDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @magentoDataFixture Magento/Customer/_files/two_customers.php + * @return void */ - protected function tearDown(): void + public function testCustomerWithoutQuoteView(): void { - $this->_coreRegistry->unregister(RegistryConstants::CURRENT_CUSTOMER_ID); + $this->processCheckWithoutQuoteItems('customer_two@example.com'); } /** @@ -95,23 +52,23 @@ protected function tearDown(): void */ public function testVerifyCollectionWithQuote(int $customerId, bool $guest, bool $contains): void { - $session = $this->_objectManager->create(SessionQuote::class); + $session = $this->objectManager->create(SessionQuote::class); $session->setCustomerId($customerId); - $quoteFixture = $this->_objectManager->create(Quote::class); + $quoteFixture = $this->objectManager->create(Quote::class); $quoteFixture->load('test01', 'reserved_order_id'); $quoteFixture->setCustomerIsGuest($guest) ->setCustomerId($customerId) ->save(); - $this->_block->toHtml(); + $this->block->toHtml(); if ($contains) { $this->assertStringContainsString( "We couldn't find any records", - $this->_block->getGridParentHtml() + $this->block->getGridParentHtml() ); } else { $this->assertStringNotContainsString( "We couldn't find any records", - $this->_block->getGridParentHtml() + $this->block->getGridParentHtml() ); } } @@ -144,7 +101,7 @@ public function getQuoteDataProvider(): array */ public function testGetCustomerId(): void { - $this->assertEquals(self::CUSTOMER_ID_VALUE, $this->_block->getCustomerId()); + $this->assertEquals(self::CUSTOMER_ID_VALUE, $this->block->getCustomerId()); } /** @@ -154,7 +111,7 @@ public function testGetCustomerId(): void */ public function testGetGridUrl(): void { - $this->assertStringContainsString('/backend/customer/index/cart', $this->_block->getGridUrl()); + $this->assertStringContainsString('/backend/customer/index/cart', $this->block->getGridUrl()); } /** @@ -164,20 +121,13 @@ public function testGetGridUrl(): void */ public function testGetGridParentHtml(): void { - $this->_block = $this->_objectManager->get( - \Magento\Framework\View\LayoutInterface::class - )->createBlock( - \Magento\Customer\Block\Adminhtml\Edit\Tab\Cart::class, - '', - [] - ); $mockCollection = $this->getMockBuilder(\Magento\Framework\Data\Collection::class) ->disableOriginalConstructor() ->getMock(); - $this->_block->setCollection($mockCollection); + $this->block->setCollection($mockCollection); $this->assertStringContainsString( "
_block->getGridParentHtml() + $this->block->getGridParentHtml() ); } @@ -190,7 +140,7 @@ public function testGetRowUrl(): void { $row = new \Magento\Framework\DataObject(); $row->setProductId(1); - $this->assertStringContainsString('/backend/catalog/product/edit/id/1', $this->_block->getRowUrl($row)); + $this->assertStringContainsString('/backend/catalog/product/edit/id/1', $this->block->getRowUrl($row)); } /** @@ -200,7 +150,7 @@ public function testGetRowUrl(): void */ public function testGetHtml(): void { - $html = $this->_block->toHtml(); + $html = $this->block->toHtml(); $this->assertStringContainsString("
assertStringContainsString("
assertStringContainsString("customer_cart_gridJsObject = new varienGrid(\"customer_cart_grid\",", $html); diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Grid/Renderer/AbstractItemTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Grid/Renderer/AbstractItemTest.php new file mode 100644 index 0000000000000..82a1f3647b786 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Grid/Renderer/AbstractItemTest.php @@ -0,0 +1,120 @@ +objectManager = Bootstrap::getObjectManager(); + $this->blockRendererItem = $this->objectManager->get(LayoutInterface::class)->createBlock(RendererItem::class); + $this->quoteItemCollectionFactory = $this->objectManager->get(CollectionFactory::class); + $this->productConfiguration = $this->objectManager->get(Configuration::class); + } + + /** + * Check item block rendering + * + * @return void + */ + protected function processRender(): void + { + $itemsCollection = $this->quoteItemCollectionFactory->create(); + /** @var Item $quoteItem */ + $quoteItem = $itemsCollection->getFirstItem(); + $this->assertNotEmpty($quoteItem->getId()); + $this->blockRendererItem->setProductHelpers([]); + $html = $this->blockRendererItem->render($quoteItem); + + $this->assertRendererItemValue($quoteItem, $html); + } + + /** + * Check that the product name and options are in the block. + * + * @param Item $quoteItem + * @param string $html + * @return void + */ + private function assertRendererItemValue(Item $quoteItem, string $html): void + { + $optionsXPath = $this->getOptionsValueXPath($quoteItem); + $productName = $quoteItem->getProduct()->getName(); + + $productNameXPath = count($optionsXPath) === 0 ? "/descendant::*[contains(text(), '$productName')]" + : "//div[contains(@class, 'product-title') and contains(text(), '$productName')]"; + + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath($productNameXPath, $html), + 'The block\'s rendered value does not contain expected product name.' + ); + foreach ($optionsXPath as $option) { + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath($option['xpath'], $html), + sprintf('The block\'s rendered value does not contain expected option. Option: %s', $option['label']) + ); + } + } + + /** + * Get item options and their xpath expression + * + * @param Item $quoteItem + * @return array + */ + private function getOptionsValueXPath(Item $quoteItem): array + { + $options = $this->productConfiguration->getOptions($quoteItem); + foreach ($options as $key => $option) { + $options[$key]['xpath'] = "//dl[contains(@class, 'item-options')]" + . "/dt[contains(text(), '{$option['label']}')]" + . "/following-sibling::dd[1]"; + + if (isset($option['option_type']) + && $option['option_type'] == ProductCustomOptionInterface::OPTION_GROUP_FILE) { + $value = explode(" ", $option['print_value']); + $options[$key]['xpath'] .= "/a[contains(text(), '{$value[0]}')]"; + } else { + $options[$key]['xpath'] .= "[contains(text(), '{$option['value']}')]"; + } + } + + return $options; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Grid/Renderer/Item/ItemConfigurableTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Grid/Renderer/Item/ItemConfigurableTest.php new file mode 100644 index 0000000000000..adcae7c5b434d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Grid/Renderer/Item/ItemConfigurableTest.php @@ -0,0 +1,43 @@ +get(Manager::class); + //This check is needed because Customer independent of Magento_ConfigurableProduct + if (!$moduleManager->isEnabled('Magento_ConfigurableProduct')) { + self::markTestSkipped('Magento_ConfigurableProduct module disabled.'); + } + } + + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/customer_quote_with_items_configurable_product.php + * @return void + */ + public function testRenderConfigurableProduct(): void + { + $this->processRender(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Grid/Renderer/ItemTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Grid/Renderer/ItemTest.php new file mode 100644 index 0000000000000..1a26d22b4cc5b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Grid/Renderer/ItemTest.php @@ -0,0 +1,34 @@ +processRender(); + } + + /** + * @magentoDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @return void + */ + public function testRenderSimpleProduct(): void + { + $this->processRender(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Grid/Renderer/AbstractMultiactionTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Grid/Renderer/AbstractMultiactionTest.php new file mode 100644 index 0000000000000..f8ede749872f4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Grid/Renderer/AbstractMultiactionTest.php @@ -0,0 +1,110 @@ +objectManager = Bootstrap::getObjectManager(); + $this->layout = $this->objectManager->get(LayoutInterface::class); + $this->blockColumn = $this->layout->createBlock(Extended::class); + $this->blockColumn->setData([ + 'header' => 'Action', + 'index' => 'item_id', + 'renderer' => Multiaction::class, + 'filter' => false, + 'sortable' => false, + ]); + $this->blockMultiaction = $this->layout->createBlock(Multiaction::class); + $this->quoteItemCollectionFactory = $this->objectManager->get(CollectionFactory::class); + } + + /** + * Check multiaction block rendering + * + * @return void + */ + protected function processRender(): void + { + $itemsCollection = $this->quoteItemCollectionFactory->create(); + /** @var Item $quoteItem */ + $quoteItem = $itemsCollection->getFirstItem(); + $this->assertNotEmpty($quoteItem->getId()); + $actions = [ + [ + 'caption' => 'configure', + 'url' => 'url_configureItem', + 'process' => 'configurable', + 'control_object' => 'cartControl', + ], + [ + 'caption' => 'delete', + 'url' => 'url_removeItem', + 'onclick' => 'return cartControl.removeItem($item_id);' + ], + ]; + $this->blockColumn->addData(['actions' => $actions]); + $this->blockMultiaction->setColumn($this->blockColumn); + $html = $this->blockMultiaction->render($quoteItem); + + foreach ($actions as $action) { + $this->assertUrl((int)$quoteItem->getId(), $action, $html); + } + } + + /** + * Check that the link in the block is correct + * + * @param int $quoteItemId + * @param array $action + * @param string $html + * @return void + */ + private function assertUrl(int $quoteItemId, array $action, string $html): void + { + $jsFunction = str_replace('url_', '', $action['url']); + $configureXPath = "//a[contains(@onclick, 'return cartControl.$jsFunction($quoteItemId)')" + . " and text()='{$action['caption']}' and @href='{$action['url']}']"; + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath($configureXPath, $html), + sprintf('Expected %s link is incorrect or missing', $action['caption']) + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Grid/Renderer/Multiaction/MultiactionBundleTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Grid/Renderer/Multiaction/MultiactionBundleTest.php new file mode 100644 index 0000000000000..2e2385c5088f2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Grid/Renderer/Multiaction/MultiactionBundleTest.php @@ -0,0 +1,43 @@ +get(Manager::class); + //This check is needed because Customer independent of Magento_Bundle + if (!$moduleManager->isEnabled('Magento_Bundle')) { + self::markTestSkipped('Magento_Bundle module disabled.'); + } + } + + /** + * @magentoDataFixture Magento/Bundle/_files/quote_with_bundle_and_options.php + * @return void + */ + public function testRenderConfigurableProduct(): void + { + $this->processRender(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Grid/Renderer/Multiaction/MultiactionConfigurableTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Grid/Renderer/Multiaction/MultiactionConfigurableTest.php new file mode 100644 index 0000000000000..ae279552c122a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Grid/Renderer/Multiaction/MultiactionConfigurableTest.php @@ -0,0 +1,43 @@ +get(Manager::class); + //This check is needed because Customer independent of Magento_ConfigurableProduct + if (!$moduleManager->isEnabled('Magento_ConfigurableProduct')) { + self::markTestSkipped('Magento_ConfigurableProduct module disabled.'); + } + } + + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/customer_quote_with_items_configurable_product.php + * @return void + */ + public function testRenderConfigurableProduct(): void + { + $this->processRender(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Grid/Renderer/MultiactionTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Grid/Renderer/MultiactionTest.php new file mode 100644 index 0000000000000..430fba8458c29 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Grid/Renderer/MultiactionTest.php @@ -0,0 +1,78 @@ +objectManager->create(DataObject::class); + $this->blockColumn->addData($columnData); + $this->blockMultiaction->setColumn($this->blockColumn); + $this->assertEquals( + ' ', + $this->blockMultiaction->render($row) + ); + } + + /** + * Data provider for testRenderEmpty + * + * @return array + */ + public function renderEmptyProvider(): array + { + return [ + 'empty_actions' => [ + 'column_data' => ['actions' => []], + ], + 'not_array_actions' => [ + 'column_data' => ['actions' => 'actions'], + ], + 'empty_actions_element' => [ + 'column_data' => [ + 'actions' => [ + 'action_1' => 'actions', + ], + ], + ], + ]; + } + + /** + * @magentoDataFixture Magento/Checkout/_files/customer_quote_with_items_simple_product_options.php + * @return void + */ + public function testRenderProductOptions(): void + { + $this->processRender(); + } + + /** + * @magentoDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @return void + */ + public function testRenderSimpleProduct(): void + { + $this->markTestSkipped('Test is blocked by issue MC-34612'); + $this->processRender(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Cart/Product/Composite/Cart/ConfigureTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Cart/Product/Composite/Cart/ConfigureTest.php new file mode 100644 index 0000000000000..87a4eb15f1913 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Cart/Product/Composite/Cart/ConfigureTest.php @@ -0,0 +1,140 @@ +quoteItemCollectionFactory = $this->_objectManager->get(CollectionFactory::class); + $this->baseWebsiteId = (int)$this->_objectManager->get(StoreManagerInterface::class) + ->getWebsite('base') + ->getId(); + $this->json = $this->_objectManager->get(SerializerInterface::class); + } + + /** + * @return void + */ + public function testConfigureActionNoCustomerId(): void + { + $this->dispatchCompositeCartConfigure(); + $this->assertEquals( + [ + 'error' => true, + 'message' => "The customer ID isn't defined.", + ], + $this->json->unserialize($this->getResponse()->getBody()) + ); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * @return void + */ + public function testConfigureNoQuoteId(): void + { + $this->dispatchCompositeCartConfigure([ + 'customer_id' => 1, + 'website_id' => $this->baseWebsiteId, + ]); + $this->assertEquals( + [ + 'error' => true, + 'message' => "The quote items are incorrect. Verify the quote items and try again.", + ], + $this->json->unserialize($this->getResponse()->getBody()) + ); + } + + /** + * @dataProvider configureWithQuoteProvider + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Customer/_files/quote.php + * @param bool $hasQuoteItem + * @param string $expectedResponseBody + * @return void + */ + public function testConfigureWithQuote(bool $hasQuoteItem, string $expectedResponseBody): void + { + $itemsCollection = $this->quoteItemCollectionFactory->create(); + $itemId = $itemsCollection->getFirstItem()->getId(); + $this->assertNotEmpty($itemId); + if (!$hasQuoteItem) { + $itemId++; + } + $this->dispatchCompositeCartConfigure([ + 'customer_id' => 1, + 'website_id' => $this->baseWebsiteId, + 'id' => $itemId, + ]); + $this->assertStringContainsString( + $expectedResponseBody, + $this->getResponse()->getBody() + ); + } + + /** + * Create configure with quote provider + * + * @return array + */ + public function configureWithQuoteProvider(): array + { + return [ + 'with_quote_item_id' => [ + 'has_quote_item' => true, + 'expected_response_body' => '', + ], + 'without_quote_item_id' => [ + 'has_quote_item' => false, + 'expected_response_body' => '{"error":true,"message":"The quote items are incorrect.' + . ' Verify the quote items and try again."}', + ], + ]; + } + + /** + * Dispatch configure quote item in customer shopping cart + * using backend/customer/cart_product_composite_cart/configure action. + * + * @param array $params + * @param array $postValue + * @return void + */ + private function dispatchCompositeCartConfigure(array $params = [], array $postValue = []): void + { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); + $this->getRequest()->setParams($params); + $this->getRequest()->setPostValue($postValue); + $this->dispatch('backend/customer/cart_product_composite_cart/configure'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Cart/Product/Composite/Cart/UpdateTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Cart/Product/Composite/Cart/UpdateTest.php new file mode 100644 index 0000000000000..fd0e7a8d95833 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Cart/Product/Composite/Cart/UpdateTest.php @@ -0,0 +1,325 @@ +quoteItemCollectionFactory = $this->_objectManager->get(CollectionFactory::class); + $this->session = $this->_objectManager->get(Session::class); + $this->json = $this->_objectManager->get(SerializerInterface::class); + $this->quoteRepository = $this->_objectManager->get(CartRepositoryInterface::class); + $this->customerRepository = $this->_objectManager->get(CustomerRepositoryInterface::class); + $this->baseWebsiteId = (int)$this->_objectManager->get(StoreManagerInterface::class) + ->getWebsite('base') + ->getId(); + } + + /** + * @return void + */ + public function testUpdateNoCustomerId(): void + { + $expectedUpdateResult = [ + 'error' => true, + 'message' => (string)__("The customer ID isn't defined."), + 'js_var_name' => null, + ]; + $this->dispatchCompositeCartUpdate(); + /** @var DataObject $updateResult */ + $updateResult = $this->session->getCompositeProductResult(); + $this->assertEquals($expectedUpdateResult, $updateResult->getData()); + $this->assertRedirect($this->stringContains('catalog/product/showUpdateResult')); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * @return void + */ + public function testUpdateNoQuoteId(): void + { + $expectedUpdateResult = [ + 'error' => true, + 'message' => (string)__('The quote items are incorrect. Verify the quote items and try again.'), + 'js_var_name' => 'iFrameResponse', + ]; + $this->dispatchCompositeCartUpdate([ + 'customer_id' => 1, + 'website_id' => $this->baseWebsiteId, + 'as_js_varname' => 'iFrameResponse', + ]); + /** @var DataObject $updateResult */ + $updateResult = $this->session->getCompositeProductResult(); + $this->assertEquals($expectedUpdateResult, $updateResult->getData()); + $this->assertRedirect($this->stringContains('catalog/product/showUpdateResult')); + } + + /** + * @dataProvider updateWithQuoteProvider + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Customer/_files/quote.php + * @param bool $hasQuoteItem + * @param array $expectedUpdateResult + * @return void + */ + public function testUpdateWithQuote(bool $hasQuoteItem, array $expectedUpdateResult): void + { + $itemsCollection = $this->quoteItemCollectionFactory->create(); + $itemId = $itemsCollection->getFirstItem()->getId(); + $this->assertNotEmpty($itemId); + if (!$hasQuoteItem) { + $itemId++; + } + $this->dispatchCompositeCartUpdate( + [ + 'customer_id' => 1, + 'website_id' => $this->baseWebsiteId, + ], + [ + 'id' => $itemId, + 'as_js_varname' => 'iFrameResponse', + 'qty' => 20, + ] + ); + /** @var DataObject $updateResult */ + $updateResult = $this->session->getCompositeProductResult(); + $this->assertEquals($expectedUpdateResult, $updateResult->getData()); + $this->assertRedirect($this->stringContains('catalog/product/showUpdateResult')); + } + + /** + * Create update with quote provider + * + * @return array + */ + public function updateWithQuoteProvider(): array + { + return [ + 'with_quote_item_id' => [ + 'has_quote_item' => true, + 'expected_update_result' => [ + 'ok' => true, + 'js_var_name' => 'iFrameResponse', + ], + ], + 'without_quote_item_id' => [ + 'has_quote_item' => false, + 'expected_update_result' => [ + 'error' => true, + 'message' => (string)__('The quote items are incorrect. Verify the quote items and try again.'), + 'js_var_name' => 'iFrameResponse', + ], + ], + ]; + } + + /** + * @magentoDataFixture Magento/Checkout/_files/customer_quote_with_items_simple_product_options.php + * @return void + */ + public function testUpdateSimpleProductOption(): void + { + $customer = $this->customerRepository->get('customer_uk_address@test.com'); + /** @var Quote $quote */ + $quote = $this->quoteRepository->getForCustomer($customer->getId()); + /** @var QuoteItem $quoteItem */ + $quoteItem = $quote->getItemsCollection()->getFirstItem(); + $this->assertNotEmpty($quoteItem->getId()); + $expectedData = $this->prepareExpectedData($quoteItem); + $expectedUpdateResult = [ + 'ok' => true, + 'js_var_name' => 'iFrameResponse', + ]; + $expectedParams = [ + 'id' => $quoteItem->getId(), + 'as_js_varname' => 'iFrameResponse', + 'options' => $expectedData['options'], + 'qty' => 5, + ]; + $this->dispatchCompositeCartUpdate( + [ + 'customer_id' => $customer->getId(), + 'website_id' => $customer->getWebsiteId(), + ], + $expectedParams + ); + /** @var DataObject $updateResult */ + $updateResult = $this->session->getCompositeProductResult(); + $this->assertEquals($expectedUpdateResult, $updateResult->getData()); + + $quoteItem = $this->getQuoteItemBySku($quote, $expectedData['sku']); + $this->assertNotNull($quoteItem, 'Missing expected shopping cart item after update.'); + $this->assertQuoteItemOptions($quoteItem, $expectedParams); + $this->assertRedirect($this->stringContains('catalog/product/showUpdateResult')); + } + + /** + * Prepare quote item options and sku for update. + * + * @param QuoteItem $quoteItem + * @return array + */ + private function prepareExpectedData(QuoteItem $quoteItem): array + { + $buyRequest = $this->json->unserialize($quoteItem->getOptionByCode('info_buyRequest')->getValue()); + $productOptions = $quoteItem->getProduct()->getOptions(); + $options = []; + $sku = $quoteItem->getSku(); + /** @var ProductOption $productOption */ + foreach ($productOptions as $productOption) { + $itemOptionValue = $buyRequest['options'][$productOption->getId()]; + switch ($productOption->getType()) { + case ProductCustomOptionInterface::OPTION_TYPE_RADIO: + $productValues = $productOption->getValues(); + $currentRadioSku = $productValues[$itemOptionValue]->getSku(); + unset($productValues[$itemOptionValue]); + $value = (string)key($productValues); + $newRadioSku = $productValues[$value]->getSku(); + $sku = str_replace($currentRadioSku, $newRadioSku, $sku); + break; + case ProductCustomOptionInterface::OPTION_TYPE_DATE: + $value = ['year' => 2019, 'month' => 8, 'day' => 9, 'hour' => 13, 'minute' => 35]; + break; + case ProductCustomOptionInterface::OPTION_TYPE_FILE: + $itemOptionValue['title'] = 'testcart.jpg'; + $value = $itemOptionValue; + $validatorInfoMock = $this->prepareValidatorInfoMock(); + $this->_objectManager->addSharedInstance($validatorInfoMock, ValidatorInfo::class); + break; + case ProductCustomOptionInterface::OPTION_TYPE_AREA: + $value = 'testcart'; + break; + default: + $value = $itemOptionValue; + break; + } + $options[$productOption->getId()] = $value; + } + + return [ + 'options' => $options, + 'sku' => $sku, + ]; + } + + /** + * Prepare mock for updating file type options. + * + * @return MockObject + */ + private function prepareValidatorInfoMock(): MockObject + { + $validatorInfoMock = $this->createMock(ValidatorInfo::class); + $validatorInfoMock->method('setUseQuotePath')->willReturnSelf(); + $validatorInfoMock->expects($this->any()) + ->method('validate') + ->willReturn(true); + + return $validatorInfoMock; + } + + /** + * Get quote item by sku. + * + * @param Quote $quote + * @param string $sku + * @return QuoteItem|null + */ + private function getQuoteItemBySku(Quote $quote, string $sku): ?QuoteItem + { + $itemsCollection = $quote->getItemsCollection(false); + $itemsCollection->addFieldToFilter('sku', $sku); + /** @var QuoteItem $quoteItem */ + $quoteItem = $itemsCollection->getFirstItem(); + + return empty($quoteItem->getId()) ? null : $quoteItem; + } + + /** + * Verify that the quote item options are saved successfully. + * + * @param QuoteItem $quoteItem + * @param array $expectedParams + * @return void + */ + private function assertQuoteItemOptions(QuoteItem $quoteItem, array $expectedParams): void + { + $buyRequest = $this->json->unserialize($quoteItem->getOptionByCode('info_buyRequest')->getValue()); + foreach ($expectedParams as $key => $value) { + if ($key == 'options') { + foreach ($value as $optionId => $optionValue) { + $buyRequestValue = is_array($optionValue) + ? array_intersect_assoc($optionValue, $buyRequest[$key][$optionId]) + : $buyRequest[$key][$optionId]; + $this->assertEquals($optionValue, $buyRequestValue); + } + } else { + $this->assertEquals($value, $buyRequest[$key]); + } + } + } + + /** + * Dispatch update quote item in customer shopping cart + * using backend/customer/cart_product_composite_cart/update action. + * + * @param array $params + * @param array $postValue + * @return void + */ + private function dispatchCompositeCartUpdate(array $params = [], array $postValue = []): void + { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); + $this->getRequest()->setParams($params); + $this->getRequest()->setPostValue($postValue); + $this->dispatch('backend/customer/cart_product_composite_cart/update'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Cart/Product/Composite/CartTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Cart/Product/Composite/CartTest.php deleted file mode 100644 index ea21b2df663d9..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Cart/Product/Composite/CartTest.php +++ /dev/null @@ -1,105 +0,0 @@ -quoteItemCollectionFactory = $this->_objectManager->get( - \Magento\Quote\Model\ResourceModel\Quote\Item\CollectionFactory::class - ); - } - - public function testConfigureActionNoCustomerId() - { - $this->dispatch('backend/customer/cart_product_composite_cart/configure'); - $this->assertEquals( - '{"error":true,"message":"The customer ID isn\'t defined."}', - $this->getResponse()->getBody() - ); - } - - /** - * @magentoDataFixture Magento/Customer/_files/customer.php - */ - public function testConfigureActionNoQuoteId() - { - $this->getRequest()->setParam('customer_id', 1); - $this->getRequest()->setParam('website_id', 1); - $this->dispatch('backend/customer/cart_product_composite_cart/configure'); - $this->assertEquals( - '{"error":true,"message":"The quote items are incorrect. Verify the quote items and try again."}', - $this->getResponse()->getBody() - ); - } - - /** - * @magentoDbIsolation enabled - * @magentoAppIsolation enabled - * @magentoDataFixture Magento/Customer/_files/customer.php - * @magentoDataFixture Magento/Customer/_files/quote.php - */ - public function testConfigureAction() - { - $items = $this->quoteItemCollectionFactory->create(); - $itemId = $items->getAllIds()[0]; - $this->getRequest()->setParam('customer_id', 1); - $this->getRequest()->setParam('website_id', 1); - $this->getRequest()->setParam('id', $itemId); - $this->dispatch('backend/customer/cart_product_composite_cart/configure'); - $this->assertStringContainsString( - '', - $this->getResponse()->getBody() - ); - } - - public function testUpdateActionNoCustomerId() - { - $this->dispatch('backend/customer/cart_product_composite_cart/update'); - $this->assertRedirect($this->stringContains('catalog/product/showUpdateResult')); - } - - /** - * @magentoDataFixture Magento/Customer/_files/customer.php - */ - public function testUpdateActionNoQuoteId() - { - $this->getRequest()->setParam('customer_id', 1); - $this->getRequest()->setParam('website_id', 1); - $this->dispatch('backend/customer/cart_product_composite_cart/update'); - $this->assertRedirect($this->stringContains('catalog/product/showUpdateResult')); - } - - /** - * @magentoDbIsolation enabled - * @magentoAppIsolation enabled - * @magentoDataFixture Magento/Customer/_files/customer.php - * @magentoDataFixture Magento/Customer/_files/quote.php - */ - public function testUpdateAction() - { - $items = $this->quoteItemCollectionFactory->create(); - $itemId = $items->getAllIds()[0]; - $this->getRequest()->setParam('customer_id', 1); - $this->getRequest()->setParam('website_id', 1); - $this->getRequest()->setParam('id', $itemId); - - $this->dispatch('backend/customer/cart_product_composite_cart/update'); - $this->assertRedirect($this->stringContains('catalog/product/showUpdateResult')); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/CartTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/CartTest.php new file mode 100644 index 0000000000000..3e2652f0a0709 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/CartTest.php @@ -0,0 +1,99 @@ +customerRepository = $this->_objectManager->get(CustomerRepositoryInterface::class); + $this->quoteRepository = $this->_objectManager->get(CartRepositoryInterface::class); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer_sample.php + * @return void + */ + public function testCartAction(): void + { + $this->dispatchShoppingCart( + [ + 'id' => 1, + 'website_id' => 1, + ], + ['delete' => 1] + ); + $body = $this->getResponse()->getBody(); + $this->assertStringContainsString('
customerRepository->get('customer_uk_address@test.com'); + /** @var Quote $quote */ + $quote = $this->quoteRepository->getForCustomer($customer->getId()); + $quoteItemId = $quote->getItemsCollection()->getFirstItem()->getItemId(); + $this->assertNotEmpty($quoteItemId); + $this->dispatchShoppingCart( + [ + 'id' => $customer->getId(), + 'website_id' => $customer->getWebsiteId(), + ], + ['delete' => $quoteItemId] + ); + $quote->getItemsCollection(false); + $this->assertFalse( + $quote->getItemById($quoteItemId), + sprintf('Customer\'s shopping cart item with ID = %s has not been deleted', $quoteItemId) + ); + } + + /** + * Dispatch admin shopping cart using backend/customer/index/cart action. + * + * @param array $params + * @param array $postValue + * @return void + */ + private function dispatchShoppingCart(array $params = [], array $postValue = []): void + { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); + $this->getRequest()->setParams($params); + $this->getRequest()->setPostValue($postValue); + $this->dispatch('backend/customer/index/cart'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php index 019c1c277e55f..40c84d8b5db58 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php @@ -153,17 +153,6 @@ public function te1stNewActionWithCustomerData() $this->testNewAction(); } - /** - * @magentoDataFixture Magento/Customer/_files/customer_sample.php - */ - public function testCartAction() - { - $this->getRequest()->setParam('id', 1)->setParam('website_id', 1)->setPostValue('delete', 1); - $this->dispatch('backend/customer/index/cart'); - $body = $this->getResponse()->getBody(); - $this->assertStringContainsString('
Date: Tue, 2 Jun 2020 11:54:31 +0300 Subject: [PATCH 21/24] MC-34201: WebAPI Test Extensibility --- .../Model/FixtureCallStorage.php | 79 ++++++ .../TestModuleOverrideConfig/composer.json | 21 ++ .../etc/adminhtml/system.xml | 18 ++ .../TestModuleOverrideConfig/etc/config.xml | 27 ++ .../TestModuleOverrideConfig/etc/module.xml | 10 + .../TestModuleOverrideConfig/registration.php | 13 + .../Test/Api/_files/overrides.xml | 150 +++++++++++ .../TestModuleOverrideConfig2/composer.json | 21 ++ .../TestModuleOverrideConfig2/etc/module.xml | 14 + .../registration.php | 13 + .../Test/Api/_files/overrides.xml | 46 ++++ .../TestModuleOverrideConfig3/composer.json | 21 ++ .../TestModuleOverrideConfig3/etc/module.xml | 14 + .../registration.php | 13 + .../Annotation/ApiConfigFixture.php | 216 ++++++++------- .../Annotation/ApiDataFixture.php | 149 ++-------- .../Magento/TestFramework/ApiSuiteLoader.php | 16 ++ .../App/ApiMutableScopeConfig.php | 83 +++--- .../Bootstrap/WebapiDocBlock.php | 2 +- .../WebapiWorkaround/Override/Config.php | 47 ++++ .../Override/Config/Converter.php | 58 ++++ .../Override/Config/FileCollector.php | 60 +++++ .../Override/Config/SchemaLocator.php | 32 +++ .../Override/Fixture/Resolver.php | 52 ++++ .../WebapiWorkaround/etc/overrides.xsd | 82 ++++++ .../api-functional/framework/bootstrap.php | 12 +- .../api-functional/phpunit_graphql.xml.dist | 8 +- .../api-functional/phpunit_rest.xml.dist | 8 +- .../api-functional/phpunit_soap.xml.dist | 6 + .../AbstractOverridesTest.php | 38 +++ .../AddFixtureTest.php | 109 ++++++++ .../RemoveFixtureTest.php | 178 ++++++++++++ .../ReplaceFixtureTest.php | 178 ++++++++++++ .../MagentoApiDataFixture/AddFixtureTest.php | 97 +++++++ .../RemoveFixtureTest.php | 84 ++++++ .../ReplaceFixtureTest.php | 136 ++++++++++ .../SortFixturesTest.php | 78 ++++++ .../Skip/SkipClassTest.php | 28 ++ .../Skip/SkipDataSetTest.php | 43 +++ .../Skip/SkipMethodTest.php | 28 ++ .../testsuite/Magento/WebApiTest.php | 71 +++++ .../Model/FixtureCallStorage.php | 8 +- .../TestModuleOverrideConfig/composer.json | 2 +- .../Test/Integration/_files/overrides.xml | 5 +- .../TestModuleOverrideConfig2/composer.json | 2 +- .../Test/Integration/_files/overrides.xml | 3 + .../TestModuleOverrideConfig3/composer.json | 2 +- .../Annotation/AbstractDataFixture.php | 1 - .../Annotation/ConfigFixture.php | 86 ++++-- .../Config/Model/ConfigStorage.php | 123 +++++++++ .../Workaround/Override/Config.php | 254 +++++++++++------- .../Workaround/Override/Config/Converter.php | 14 +- .../Workaround/Override/ConfigInterface.php | 71 +++++ .../Fixture/Applier/ConfigFixture.php | 20 +- .../Override/Fixture/Applier/DataFixture.php | 27 +- .../Workaround/Override/Fixture/Resolver.php | 116 ++++---- .../Override/Fixture/ResolverInterface.php | 69 +++++ dev/tests/integration/framework/bootstrap.php | 11 +- .../RemoveFixtureTest.php | 2 +- 59 files changed, 2684 insertions(+), 491 deletions(-) create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/Model/FixtureCallStorage.php create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/composer.json create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/etc/adminhtml/system.xml create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/etc/config.xml create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/etc/module.xml create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/registration.php create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig2/Test/Api/_files/overrides.xml create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig2/composer.json create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig2/etc/module.xml create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig2/registration.php create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig3/Test/Api/_files/overrides.xml create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig3/composer.json create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig3/etc/module.xml create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig3/registration.php create mode 100644 dev/tests/api-functional/framework/Magento/TestFramework/ApiSuiteLoader.php create mode 100644 dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/Override/Config.php create mode 100644 dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/Override/Config/Converter.php create mode 100644 dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/Override/Config/FileCollector.php create mode 100644 dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/Override/Config/SchemaLocator.php create mode 100644 dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/Override/Fixture/Resolver.php create mode 100644 dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/etc/overrides.xsd create mode 100644 dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/AbstractOverridesTest.php create mode 100644 dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiConfigFixture/AddFixtureTest.php create mode 100644 dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiConfigFixture/RemoveFixtureTest.php create mode 100644 dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiConfigFixture/ReplaceFixtureTest.php create mode 100644 dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiDataFixture/AddFixtureTest.php create mode 100644 dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiDataFixture/RemoveFixtureTest.php create mode 100644 dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiDataFixture/ReplaceFixtureTest.php create mode 100644 dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiDataFixture/SortFixturesTest.php create mode 100644 dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/Skip/SkipClassTest.php create mode 100644 dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/Skip/SkipDataSetTest.php create mode 100644 dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/Skip/SkipMethodTest.php create mode 100644 dev/tests/api-functional/testsuite/Magento/WebApiTest.php create mode 100644 dev/tests/integration/framework/Magento/TestFramework/Config/Model/ConfigStorage.php create mode 100644 dev/tests/integration/framework/Magento/TestFramework/Workaround/Override/ConfigInterface.php create mode 100644 dev/tests/integration/framework/Magento/TestFramework/Workaround/Override/Fixture/ResolverInterface.php diff --git a/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/Model/FixtureCallStorage.php b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/Model/FixtureCallStorage.php new file mode 100644 index 0000000000000..57a607feedb0c --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/Model/FixtureCallStorage.php @@ -0,0 +1,79 @@ +storage[] = $fixture; + } + + /** + * Get fixture position in storage + * + * @param string $fixture + * @return null|int + */ + public function getFixturePosition(string $fixture): ?int + { + return array_search($fixture, $this->storage) ?: null; + } + + /** + * Get storage + * + * @return array + */ + public function getStorage(): array + { + return $this->storage; + } + + /** + * Get fixtures count in storage + * + * @param string $fixture + * @return int + */ + public function getFixturesCount(string $fixture = ''): int + { + $count = count($this->storage); + if ($fixture) { + $result = array_filter($this->storage, function ($storedFixture) use ($fixture) { + return $storedFixture === $fixture; + }); + $count = count($result); + } + + return $count; + } + + /** + * Clear storage + * + * @return void + */ + public function clearStorage(): void + { + $this->storage = []; + } +} diff --git a/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/composer.json b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/composer.json new file mode 100644 index 0000000000000..47ac2d4ac4a3b --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/composer.json @@ -0,0 +1,21 @@ +{ + "name": "magento/module-override-config-test", + "description": "module for override config check", + "config": { + "sort-packages": true + }, + "require": { + "php": "~7.3.0||~7.4.0", + "magento/framework": "*", + "magento/module-integration": "*" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/TestModuleOverrideConfig" + ] + ] + } +} diff --git a/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/etc/adminhtml/system.xml b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/etc/adminhtml/system.xml new file mode 100644 index 0000000000000..8c0badac4b1d1 --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/etc/adminhtml/system.xml @@ -0,0 +1,18 @@ + + + + +
+ + + + + +
+
+
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/etc/config.xml b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/etc/config.xml new file mode 100644 index 0000000000000..3b2f2a1ddde1e --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/etc/config.xml @@ -0,0 +1,27 @@ + + + + + + + 1st field default value + 2nd field default value + 3rd field default value + + + + + + + + 3rd field website scope default value + + + + + diff --git a/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/etc/module.xml b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/etc/module.xml new file mode 100644 index 0000000000000..f9d63847959df --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/etc/module.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/registration.php b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/registration.php new file mode 100644 index 0000000000000..16ffc73cef00f --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig/registration.php @@ -0,0 +1,13 @@ +getPath(ComponentRegistrar::MODULE, 'Magento_TestModuleOverrideConfig') === null) { + ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_TestModuleOverrideConfig', __DIR__); +} diff --git a/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig2/Test/Api/_files/overrides.xml b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig2/Test/Api/_files/overrides.xml new file mode 100644 index 0000000000000..bda41e51aa5c8 --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig2/Test/Api/_files/overrides.xml @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig2/composer.json b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig2/composer.json new file mode 100644 index 0000000000000..43b7bec56945d --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig2/composer.json @@ -0,0 +1,21 @@ +{ + "name": "magento/module-override-config2-test", + "description": "module for override config check", + "config": { + "sort-packages": true + }, + "require": { + "php": "~7.3.0||~7.4.0", + "magento/framework": "*", + "magento/module-integration": "*" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/TestModuleOverrideConfig2" + ] + ] + } +} diff --git a/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig2/etc/module.xml b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig2/etc/module.xml new file mode 100644 index 0000000000000..6432681d22e1d --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig2/etc/module.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig2/registration.php b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig2/registration.php new file mode 100644 index 0000000000000..ac3f1763eb93b --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig2/registration.php @@ -0,0 +1,13 @@ +getPath(ComponentRegistrar::MODULE, 'Magento_TestModuleOverrideConfig2') === null) { + ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_TestModuleOverrideConfig2', __DIR__); +} diff --git a/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig3/Test/Api/_files/overrides.xml b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig3/Test/Api/_files/overrides.xml new file mode 100644 index 0000000000000..f4af91e02b2a1 --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig3/Test/Api/_files/overrides.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig3/composer.json b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig3/composer.json new file mode 100644 index 0000000000000..432b2ef703a57 --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig3/composer.json @@ -0,0 +1,21 @@ +{ + "name": "magento/module-override-config3-test", + "description": "module for override config check", + "config": { + "sort-packages": true + }, + "require": { + "php": "~7.3.0||~7.4.0", + "magento/framework": "*", + "magento/module-integration": "*" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/TestModuleOverrideConfig3" + ] + ] + } +} diff --git a/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig3/etc/module.xml b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig3/etc/module.xml new file mode 100644 index 0000000000000..ef3693fd036f3 --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig3/etc/module.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig3/registration.php b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig3/registration.php new file mode 100644 index 0000000000000..e3217d8f97e8a --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleOverrideConfig3/registration.php @@ -0,0 +1,13 @@ +getPath(ComponentRegistrar::MODULE, 'Magento_TestModuleOverrideConfig3') === null) { + ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_TestModuleOverrideConfig3', __DIR__); +} diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/Annotation/ApiConfigFixture.php b/dev/tests/api-functional/framework/Magento/TestFramework/Annotation/ApiConfigFixture.php index 8061cb138660d..3ef6e6618c6c5 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/Annotation/ApiConfigFixture.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/Annotation/ApiConfigFixture.php @@ -7,13 +7,14 @@ namespace Magento\TestFramework\Annotation; -use Magento\Config\Model\Config; use Magento\Config\Model\ResourceModel\Config as ConfigResource; -use Magento\Config\Model\ResourceModel\Config\Data\CollectionFactory; +use Magento\Framework\App\Config\MutableScopeConfigInterface; use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\TestFramework\Helper\Bootstrap; +use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\StoreManagerInterface; -use PHPUnit\Framework\TestCase; +use Magento\TestFramework\App\ApiMutableScopeConfig; +use Magento\TestFramework\Config\Model\ConfigStorage; +use Magento\TestFramework\Helper\Bootstrap; /** * @inheritDoc @@ -21,167 +22,162 @@ class ApiConfigFixture extends ConfigFixture { /** - * Original values for global configuration options that need to be restored + * Values need to be deleted form the database * * @var array */ - private $_globalConfigValues = []; + private $valuesToDeleteFromDatabase = []; /** - * Original values for store-scoped configuration options that need to be restored - * - * @var array + * @inheritdoc + * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ - private $_storeConfigValues = []; + protected function setStoreConfigValue(array $matches, $configPathAndValue): void + { + $storeCode = $matches[0]; + [$configScope, $configPath, $requiredValue] = preg_split('/\s+/', $configPathAndValue, 3); + /** @var ConfigStorage $configStorage */ + $configStorage = Bootstrap::getObjectManager()->get(ConfigStorage::class); + if (!$configStorage->checkIsRecordExist($configPath, ScopeInterface::SCOPE_STORES, $storeCode)) { + $this->valuesToDeleteFromDatabase[$storeCode][$configPath ?? ''] = $requiredValue ?? ''; + } + + parent::setStoreConfigValue($matches, $configPathAndValue); + } /** - * Values need to be deleted form the database - * - * @var array + * @inheritdoc */ - private $_valuesToDeleteFromDatabase = []; + protected function setGlobalConfigValue($configPathAndValue): void + { + [$configPath, $requiredValue] = preg_split('/\s+/', $configPathAndValue, 2); + /** @var ConfigStorage $configStorage */ + $configStorage = Bootstrap::getObjectManager()->get(ConfigStorage::class); + if (!$configStorage->checkIsRecordExist($configPath)) { + $this->valuesToDeleteFromDatabase['global'][$configPath] = $requiredValue; + } + + $originalValue = $this->getScopeConfigValue($configPath, ScopeConfigInterface::SCOPE_TYPE_DEFAULT); + $this->globalConfigValues[$configPath] = $originalValue; + $this->_setConfigValue($configPath, $requiredValue); + } /** - * Assign required config values and save original ones - * - * @param TestCase $test + * @inheritdoc * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ - protected function _assignConfigData(TestCase $test) + protected function setWebsiteConfigValue(array $matches, $configPathAndValue): void { - $annotations = $test->getAnnotations(); - if (!isset($annotations['method'][$this->annotation])) { - return; + $websiteCode = $matches[0]; + [$configScope, $configPath, $requiredValue] = preg_split('/\s+/', $configPathAndValue, 3); + /** @var ConfigStorage $configStorage */ + $configStorage = Bootstrap::getObjectManager()->get(ConfigStorage::class); + if (!$configStorage->checkIsRecordExist($configPath, ScopeInterface::SCOPE_WEBSITES, $websiteCode)) { + $this->valuesToDeleteFromDatabase[$websiteCode][$configPath ?? ''] = $requiredValue ?? ''; } - foreach ($annotations['method'][$this->annotation] as $configPathAndValue) { - if (preg_match('/^.+?(?=_store\s)/', $configPathAndValue, $matches)) { - /* Store-scoped config value */ - $storeCode = $matches[0]; - $parts = preg_split('/\s+/', $configPathAndValue, 3); - list($configScope, $configPath, $requiredValue) = $parts + ['', '', '']; - $originalValue = $this->_getConfigValue($configPath, $storeCode); - $this->_storeConfigValues[$storeCode][$configPath] = $originalValue; - if ($this->checkIfValueExist($configPath, $storeCode)) { - $this->_valuesToDeleteFromDatabase[$storeCode][$configPath] = $requiredValue; - } - $this->_setConfigValue($configPath, $requiredValue, $storeCode); - } else { - /* Global config value */ - list($configPath, $requiredValue) = preg_split('/\s+/', $configPathAndValue, 2); - - $originalValue = $this->_getConfigValue($configPath); - $this->_globalConfigValues[$configPath] = $originalValue; - if ($this->checkIfValueExist($configPath)) { - $this->_valuesToDeleteFromDatabase['global'][$configPath] = $requiredValue; - } - $this->_setConfigValue($configPath, $requiredValue); - } - } + parent::setWebsiteConfigValue($matches, $configPathAndValue); } /** - * Restore original values for changed config options + * @inheritDoc + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function _restoreConfigData() { + /** @var ConfigResource $configResource */ $configResource = Bootstrap::getObjectManager()->get(ConfigResource::class); - /* Restore global values */ - foreach ($this->_globalConfigValues as $configPath => $originalValue) { - if (isset($this->_valuesToDeleteFromDatabase['global'][$configPath])) { + foreach ($this->globalConfigValues as $configPath => $originalValue) { + if (isset($this->valuesToDeleteFromDatabase['global'][$configPath])) { $configResource->deleteConfig($configPath); } else { $this->_setConfigValue($configPath, $originalValue); } } - $this->_globalConfigValues = []; - + $this->globalConfigValues = []; /* Restore store-scoped values */ - foreach ($this->_storeConfigValues as $storeCode => $originalData) { + foreach ($this->storeConfigValues as $storeCode => $originalData) { foreach ($originalData as $configPath => $originalValue) { - if (empty($storeCode)) { - $storeCode = null; + $storeCode = $storeCode ?: null; + if (isset($this->valuesToDeleteFromDatabase[$storeCode][$configPath])) { + $scopeId = $this->getIdByScopeType(ScopeInterface::SCOPE_STORES, $storeCode); + $configResource->deleteConfig($configPath, ScopeInterface::SCOPE_STORES, $scopeId); + } else { + $this->setScopeConfigValue( + $configPath, + $originalValue, + ScopeInterface::SCOPE_STORES, + $storeCode + ); } - if (isset($this->_valuesToDeleteFromDatabase[$storeCode][$configPath])) { - $scopeId = $this->getStoreIdByCode($storeCode); - $configResource->deleteConfig($configPath, 'stores', $scopeId); + } + } + $this->storeConfigValues = []; + /* Restore website-scoped values */ + foreach ($this->websiteConfigValues as $websiteCode => $originalData) { + foreach ($originalData as $configPath => $originalValue) { + $websiteCode = $websiteCode ?: null; + if (isset($this->valuesToDeleteFromDatabase[$websiteCode][$configPath])) { + $scopeId = $this->getIdByScopeType(ScopeInterface::SCOPE_WEBSITES, $websiteCode); + $configResource->deleteConfig($configPath, ScopeInterface::SCOPE_WEBSITES, $scopeId); } else { - $this->_setConfigValue($configPath, $originalValue, $storeCode); + $this->setScopeConfigValue( + $configPath, + $originalValue, + ScopeInterface::SCOPE_WEBSITES, + $websiteCode + ); } } } - $this->_storeConfigValues = []; + $this->websiteConfigValues = []; } /** - * Load configs by path and scope - * - * @param string $configPath - * @param string $storeCode - * @return Config[] + * @inheritdoc */ - private function loadConfigs(string $configPath, string $storeCode = null): array + protected function getMutableScopeConfig(): MutableScopeConfigInterface { - $configCollectionFactory = Bootstrap::getObjectManager()->get(CollectionFactory::class); - $collection = $configCollectionFactory->create(); - $scope = $storeCode ? 'stores' : 'default'; - $scopeId = $storeCode ? $this->getStoreIdByCode($storeCode) : 0; - - $collection->addScopeFilter($scope, $scopeId, $configPath); - return $collection->getItems(); + return Bootstrap::getObjectManager() + ->get(ApiMutableScopeConfig::class); } /** - * Check if config exist in the database - * - * @param string $configPath - * @param string|null $storeCode + * @inheritdoc */ - private function checkIfValueExist(string $configPath, string $storeCode = null): bool + protected function getScopeConfigValue(string $configPath, string $scopeType, string $scopeCode = null): ?string { - $configs = $this->loadConfigs($configPath, $storeCode); + /** @var ConfigStorage $configStorage */ + $configStorage = Bootstrap::getObjectManager()->get(ConfigStorage::class); + $result = $configStorage->getValueFromDb($configPath, $scopeType, $scopeCode); - return !(bool)$configs; + return $result ?: null; } /** - * Returns the store ID by the store code + * Get id by code * - * @param string $storeCode + * @param string $scopeType + * @param string|null $scopeId * @return int */ - private function getStoreIdByCode(string $storeCode): int + private function getIdByScopeType(string $scopeType, ?string $scopeId): int { + $id = 0; + /** @var StoreManagerInterface $storeManager */ $storeManager = Bootstrap::getObjectManager()->get(StoreManagerInterface::class); - $store = $storeManager->getStore($storeCode); - return (int)$store->getId(); - } - - /** - * @inheritDoc - */ - protected function _setConfigValue($configPath, $value, $storeCode = false) - { - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - if ($storeCode === false) { - $objectManager->get( - \Magento\TestFramework\App\ApiMutableScopeConfig::class - )->setValue( - $configPath, - $value, - ScopeConfigInterface::SCOPE_TYPE_DEFAULT - ); - - return; + switch ($scopeType) { + case ScopeInterface::SCOPE_WEBSITES: + $id = (int)$storeManager->getWebsite($scopeId)->getId(); + break; + case ScopeInterface::SCOPE_STORES: + $id = (int)$storeManager->getStore($scopeId)->getId(); + break; + default: + break; } - \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - \Magento\TestFramework\App\ApiMutableScopeConfig::class - )->setValue( - $configPath, - $value, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - $storeCode - ); + + return $id; } } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/Annotation/ApiDataFixture.php b/dev/tests/api-functional/framework/Magento/TestFramework/Annotation/ApiDataFixture.php index 88ac682f6b282..d99b056ca359e 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/Annotation/ApiDataFixture.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/Annotation/ApiDataFixture.php @@ -12,46 +12,27 @@ namespace Magento\TestFramework\Annotation; -class ApiDataFixture -{ - /** - * @var string - */ - protected $_fixtureBaseDir; +use Magento\Customer\Model\Metadata\AttributeMetadataCache; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; - /** - * Fixtures that have been applied - * - * @var array - */ - private $_appliedFixtures = []; - - /** - * Constructor - * - * @param string $fixtureBaseDir - * @throws \Magento\Framework\Exception\LocalizedException - */ - public function __construct($fixtureBaseDir) - { - if (!is_dir($fixtureBaseDir)) { - throw new \Magento\Framework\Exception\LocalizedException( - __("Fixture base directory '%1' does not exist.", $fixtureBaseDir) - ); - } - $this->_fixtureBaseDir = realpath($fixtureBaseDir); - } +/** + * Implementation of the @magentoApiDataFixture DocBlock annotation. + */ +class ApiDataFixture extends DataFixture +{ + public const ANNOTATION = 'magentoApiDataFixture'; /** * Handler for 'startTest' event * - * @param \PHPUnit\Framework\TestCase $test + * @param TestCase $test */ - public function startTest(\PHPUnit\Framework\TestCase $test) + public function startTest(TestCase $test) { - \Magento\TestFramework\Helper\Bootstrap::getInstance()->reinitialize(); + Bootstrap::getInstance()->reinitialize(); /** Apply method level fixtures if thy are available, apply class level fixtures otherwise */ - $this->_applyFixtures($this->_getFixtures('method', $test) ?: $this->_getFixtures('class', $test)); + $this->_applyFixtures($this->_getFixtures($test, 'method') ?: $this->_getFixtures($test, 'class')); } /** @@ -60,109 +41,15 @@ public function startTest(\PHPUnit\Framework\TestCase $test) public function endTest() { $this->_revertFixtures(); - /** @var $objectManager \Magento\TestFramework\ObjectManager */ - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $objectManager->get(\Magento\Customer\Model\Metadata\AttributeMetadataCache::class)->clean(); - } - - /** - * Retrieve fixtures from annotation - * - * @param string $scope 'class' or 'method' - * @param \PHPUnit\Framework\TestCase $test - * @return array - * @throws \Magento\Framework\Exception\LocalizedException - */ - protected function _getFixtures($scope, \PHPUnit\Framework\TestCase $test) - { - $annotations = $test->getAnnotations(); - $result = []; - if (!empty($annotations[$scope]['magentoApiDataFixture'])) { - foreach ($annotations[$scope]['magentoApiDataFixture'] as $fixture) { - if (strpos($fixture, '\\') !== false) { - // usage of a single directory separator symbol streamlines search across the source code - throw new \Magento\Framework\Exception\LocalizedException( - __('Directory separator "\\" is prohibited in fixture declaration.') - ); - } - $fixtureMethod = [get_class($test), $fixture]; - if (is_callable($fixtureMethod)) { - $result[] = $fixtureMethod; - } else { - $result[] = $this->_fixtureBaseDir . '/' . $fixture; - } - } - } - return $result; - } - - /** - * Execute single fixture script - * - * @param string|array $fixture - * @throws \Throwable - */ - protected function _applyOneFixture($fixture) - { - try { - if (is_callable($fixture)) { - call_user_func($fixture); - } else { - require $fixture; - } - } catch (\Exception $e) { - throw new \Exception( - sprintf( - "Exception occurred when running the %s fixture: \n%s", - (\is_array($fixture) || is_scalar($fixture) ? json_encode($fixture) : 'callback'), - $e->getMessage() - ) - ); - } - $this->_appliedFixtures[] = $fixture; - } - - /** - * Execute fixture scripts if any - * - * @param array $fixtures - * @throws \Magento\Framework\Exception\LocalizedException - */ - protected function _applyFixtures(array $fixtures) - { - /* Execute fixture scripts */ - foreach ($fixtures as $oneFixture) { - /* Skip already applied fixtures */ - if (!in_array($oneFixture, $this->_appliedFixtures, true)) { - $this->_applyOneFixture($oneFixture); - } - } + $objectManager = Bootstrap::getObjectManager(); + $objectManager->get(AttributeMetadataCache::class)->clean(); } /** - * Revert changes done by fixtures + * @inheritdoc */ - protected function _revertFixtures() + protected function getAnnotation(): string { - $appliedFixtures = array_reverse($this->_appliedFixtures); - foreach ($appliedFixtures as $fixture) { - if (is_callable($fixture)) { - $fixture[1] .= 'Rollback'; - if (is_callable($fixture)) { - $this->_applyOneFixture($fixture); - } - } else { - $fileInfo = pathinfo($fixture); - $extension = ''; - if (isset($fileInfo['extension'])) { - $extension = '.' . $fileInfo['extension']; - } - $rollbackScript = $fileInfo['dirname'] . '/' . $fileInfo['filename'] . '_rollback' . $extension; - if (file_exists($rollbackScript)) { - $this->_applyOneFixture($rollbackScript); - } - } - } - $this->_appliedFixtures = []; + return self::ANNOTATION; } } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/ApiSuiteLoader.php b/dev/tests/api-functional/framework/Magento/TestFramework/ApiSuiteLoader.php new file mode 100644 index 0000000000000..ea8ee3a358410 --- /dev/null +++ b/dev/tests/api-functional/framework/Magento/TestFramework/ApiSuiteLoader.php @@ -0,0 +1,16 @@ +testAppConfig = $config; + $this->storeRepository = $storeRepository; + $this->websiteRepository = $websiteRepository; + $this->configFactory = $configFactory; + } /** * @inheritdoc */ public function isSetFlag($path, $scopeType = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, $scopeCode = null) { - return $this->getTestAppConfig()->isSetFlag($path, $scopeType, $scopeCode); + return $this->testAppConfig->isSetFlag($path, $scopeType, $scopeCode); } /** @@ -37,7 +64,7 @@ public function isSetFlag($path, $scopeType = ScopeConfigInterface::SCOPE_TYPE_D */ public function getValue($path, $scopeType = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, $scopeCode = null) { - return $this->getTestAppConfig()->getValue($path, $scopeType, $scopeCode); + return $this->testAppConfig->getValue($path, $scopeType, $scopeCode); } /** @@ -46,11 +73,11 @@ public function getValue($path, $scopeType = ScopeConfigInterface::SCOPE_TYPE_DE public function setValue( $path, $value, - $scopeType = \Magento\Framework\App\Config\ScopeConfigInterface::SCOPE_TYPE_DEFAULT, + $scopeType = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, $scopeCode = null ) { $this->persistConfig($path, $value, $scopeType, $scopeCode); - return $this->getTestAppConfig()->setValue($path, $value, $scopeType, $scopeCode); + return $this->testAppConfig->setValue($path, $value, $scopeType, $scopeCode); } /** @@ -60,21 +87,7 @@ public function setValue( */ public function clean() { - $this->getTestAppConfig()->clean(); - } - - /** - * Retrieve test app config instance - * - * @return \Magento\TestFramework\App\Config - */ - private function getTestAppConfig() - { - if (!$this->testAppConfig) { - $this->testAppConfig = ObjectManager::getInstance()->get(ScopeConfigInterface::class); - } - - return $this->testAppConfig; + $this->testAppConfig->clean(); } /** @@ -84,18 +97,12 @@ private function getTestAppConfig() * @param string $value * @param string $scopeType * @param string|null $scopeCode + * @return void */ - private function persistConfig($path, $value, $scopeType, $scopeCode): void + private function persistConfig(string $path, string $value, string $scopeType, ?string $scopeCode): void { $pathParts = explode('/', $path); $store = 0; - if ($scopeType === \Magento\Store\Model\ScopeInterface::SCOPE_STORE - && $scopeCode !== null) { - $store = ObjectManager::getInstance() - ->get(\Magento\Store\Api\StoreRepositoryInterface::class) - ->get($scopeCode) - ->getId(); - } $configData = [ 'section' => $pathParts[0], 'website' => '', @@ -110,9 +117,15 @@ private function persistConfig($path, $value, $scopeType, $scopeCode): void ] ] ]; - ObjectManager::getInstance() - ->get(\Magento\Config\Model\Config\Factory::class) - ->create(['data' => $configData]) - ->save(); + if ($scopeType === ScopeInterface::SCOPE_STORE && $scopeCode !== null) { + $store = $this->storeRepository->get($scopeCode)->getId(); + $configData['store'] = $store; + } elseif ($scopeType === ScopeInterface::SCOPE_WEBSITES && $scopeCode !== null) { + $website = $this->websiteRepository->get($scopeCode)->getId(); + $configData['store'] = ''; + $configData['website'] = $website; + } + + $this->configFactory->create(['data' => $configData])->save(); } } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/Bootstrap/WebapiDocBlock.php b/dev/tests/api-functional/framework/Magento/TestFramework/Bootstrap/WebapiDocBlock.php index a3a013ae812ad..7b7047d1aceba 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/Bootstrap/WebapiDocBlock.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/Bootstrap/WebapiDocBlock.php @@ -32,7 +32,7 @@ protected function _getSubscribers(\Magento\TestFramework\Application $applicati unset($subscribers[$key]); } } - $subscribers[] = new \Magento\TestFramework\Annotation\ApiDataFixture($this->_fixturesBaseDir); + $subscribers[] = new \Magento\TestFramework\Annotation\ApiDataFixture(); $subscribers[] = new ApiConfigFixture(); return $subscribers; diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/Override/Config.php b/dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/Override/Config.php new file mode 100644 index 0000000000000..9fa5d2868fd11 --- /dev/null +++ b/dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/Override/Config.php @@ -0,0 +1,47 @@ +create(Converter::class); + } + + /** + * @inheritdoc + */ + protected function getSchemaLocator(): SchemaLocatorInterface + { + return ObjectManager::getInstance()->create(SchemaLocator::class); + } + + /** + * @inheritdoc + */ + protected function getFileCollector(): CollectorInterface + { + return ObjectManager::getInstance()->create(FileCollector::class); + } +} diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/Override/Config/Converter.php b/dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/Override/Config/Converter.php new file mode 100644 index 0000000000000..ce83a611020a8 --- /dev/null +++ b/dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/Override/Config/Converter.php @@ -0,0 +1,58 @@ +nodeName) { + case DataFixtureBeforeTransaction::ANNOTATION: + case DataFixture::ANNOTATION: + case ApiDataFixture::ANNOTATION: + $result = $this->fillDataFixtureAttributes($fixture); + break; + case ConfigFixture::ANNOTATION: + $result = $this->fillConfigFixtureAttributes($fixture); + break; + case AdminConfigFixture::ANNOTATION: + $result = $this->fillAdminConfigFixtureAttributes($fixture); + break; + default: + break; + } + + return $result; + } +} diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/Override/Config/FileCollector.php b/dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/Override/Config/FileCollector.php new file mode 100644 index 0000000000000..29ff24a0c0478 --- /dev/null +++ b/dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/Override/Config/FileCollector.php @@ -0,0 +1,60 @@ +componentDirSearch = $dirSearch; + $this->fileFactory = $fileFactory; + } + + /** + * Retrieve files + * + * @param \Magento\Framework\View\Design\ThemeInterface $theme + * @param string $filePath + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @return \Magento\Framework\View\File[] + */ + public function getFiles(ThemeInterface $theme, $filePath) + { + $result = []; + $configFiles = $this->componentDirSearch->collectFilesWithContext( + ComponentRegistrar::MODULE, + 'Test/Api/_files/' . $filePath + ); + foreach ($configFiles as $file) { + $result[] = $this->fileFactory->create($file->getFullPath(), $file->getComponentName(), null, true); + } + return $result; + } +} diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/Override/Config/SchemaLocator.php b/dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/Override/Config/SchemaLocator.php new file mode 100644 index 0000000000000..6c671e25d2812 --- /dev/null +++ b/dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/Override/Config/SchemaLocator.php @@ -0,0 +1,32 @@ +objectManager->get(DataFixtureApplier::class); + break; + case ApiConfigFixture::ANNOTATION: + $applier = $this->objectManager->get(ConfigFixtureApplier::class); + break; + case AdminConfigFixture::ANNOTATION: + $applier = $this->objectManager->get(AdminConfigFixtureApplier::class); + break; + default: + throw new \InvalidArgumentException(sprintf('Unsupported fixture type %s provided', $fixtureType)); + } + + return $applier; + } +} diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/etc/overrides.xsd b/dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/etc/overrides.xsd new file mode 100644 index 0000000000000..c0409afa9ea65 --- /dev/null +++ b/dev/tests/api-functional/framework/Magento/TestFramework/WebapiWorkaround/etc/overrides.xsd @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/api-functional/framework/bootstrap.php b/dev/tests/api-functional/framework/bootstrap.php index 01580bc268d05..d3a9a6add5776 100644 --- a/dev/tests/api-functional/framework/bootstrap.php +++ b/dev/tests/api-functional/framework/bootstrap.php @@ -94,9 +94,19 @@ $themePackageList ) ); - unset($bootstrap, $application, $settings, $shell); + $overrideConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + Magento\TestFramework\WebapiWorkaround\Override\Config::class + ); + $overrideConfig->init(); + Magento\TestFramework\Workaround\Override\Fixture\Resolver::setInstance( + new \Magento\TestFramework\WebapiWorkaround\Override\Fixture\Resolver($overrideConfig) + ); + \Magento\TestFramework\Workaround\Override\Config::setInstance($overrideConfig); + unset($bootstrap, $application, $settings, $shell, $overrideConfig); } catch (\Exception $e) { + // phpcs:ignore Magento2.Security.LanguageConstruct.DirectOutput echo $e . PHP_EOL; + // phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage exit(1); } diff --git a/dev/tests/api-functional/phpunit_graphql.xml.dist b/dev/tests/api-functional/phpunit_graphql.xml.dist index aa1899d88f48e..2f6ad1f9a37d4 100644 --- a/dev/tests/api-functional/phpunit_graphql.xml.dist +++ b/dev/tests/api-functional/phpunit_graphql.xml.dist @@ -13,10 +13,15 @@ columns="max" beStrictAboutTestsThatDoNotTestAnything="false" bootstrap="./framework/bootstrap.php" + testSuiteLoaderClass="Magento\TestFramework\ApiSuiteLoader" + testSuiteLoaderFile="framework/Magento/TestFramework/ApiSuiteLoader.php" > - + + testsuite/Magento/WebApiTest.php + + testsuite/Magento/GraphQl @@ -47,6 +52,7 @@ + diff --git a/dev/tests/api-functional/phpunit_rest.xml.dist b/dev/tests/api-functional/phpunit_rest.xml.dist index c5173e5dd432e..065c2bd11c48c 100644 --- a/dev/tests/api-functional/phpunit_rest.xml.dist +++ b/dev/tests/api-functional/phpunit_rest.xml.dist @@ -13,13 +13,18 @@ columns="max" beStrictAboutTestsThatDoNotTestAnything="false" bootstrap="./framework/bootstrap.php" + testSuiteLoaderClass="Magento\TestFramework\ApiSuiteLoader" + testSuiteLoaderFile="framework/Magento/TestFramework/ApiSuiteLoader.php" > + testsuite/Magento/WebApiTest.php + + testsuite - testsuite/Magento/GraphQl ../../../app/code/*/*/Test/Api + testsuite/Magento/GraphQl @@ -53,6 +58,7 @@ + diff --git a/dev/tests/api-functional/phpunit_soap.xml.dist b/dev/tests/api-functional/phpunit_soap.xml.dist index 935f5113b67a7..5e90b8965d34c 100644 --- a/dev/tests/api-functional/phpunit_soap.xml.dist +++ b/dev/tests/api-functional/phpunit_soap.xml.dist @@ -13,10 +13,15 @@ columns="max" beStrictAboutTestsThatDoNotTestAnything="false" bootstrap="./framework/bootstrap.php" + testSuiteLoaderClass="Magento\TestFramework\ApiSuiteLoader" + testSuiteLoaderFile="framework/Magento/TestFramework/ApiSuiteLoader.php" > + testsuite/Magento/WebApiTest.php + + testsuite ../../../app/code/*/*/Test/Api @@ -52,6 +57,7 @@ + diff --git a/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/AbstractOverridesTest.php b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/AbstractOverridesTest.php new file mode 100644 index 0000000000000..f0dccff848f04 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/AbstractOverridesTest.php @@ -0,0 +1,38 @@ +markTestSkipped('Override config is disabled.'); + } + + $this->objectManager = Bootstrap::getObjectManager(); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiConfigFixture/AddFixtureTest.php b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiConfigFixture/AddFixtureTest.php new file mode 100644 index 0000000000000..bc9933a886f50 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiConfigFixture/AddFixtureTest.php @@ -0,0 +1,109 @@ +config = $this->objectManager->get(ScopeConfigInterface::class); + $this->configStorage = $this->objectManager->get(ConfigStorage::class); + } + + /** + * Checks that fixture added in test class node successfully applied + * + * @return void + */ + public function testAddFixtureToClass(): void + { + $value = $this->config->getValue('test_section/test_group/field_1', ScopeInterface::SCOPE_STORES, 'default'); + $this->assertEquals('overridden value for full class', $value); + $this->assertEquals( + 'overridden value for full class', + $this->configStorage->getValueFromDb( + 'test_section/test_group/field_1', + ScopeInterface::SCOPE_STORES, + 'default' + ) + ); + } + + /** + * Checks that fixtures added in method and data set nodes successfully applied + * + * @dataProvider testDataProvider + * + * @param string $expectedConfigValue + * @return void + */ + public function testAddFixtureToMethod(string $expectedConfigValue): void + { + $value = $this->config->getValue('test_section/test_group/field_1', ScopeInterface::SCOPE_STORES, 'default'); + $this->assertEquals($expectedConfigValue, $value); + $this->assertEquals( + $expectedConfigValue, + $this->configStorage->getValueFromDb( + 'test_section/test_group/field_1', + ScopeInterface::SCOPE_STORES, + 'default' + ) + ); + } + + /** + * @return array + */ + public function testDataProvider(): array + { + return [ + 'first_data_set' => ['expected_config_value' => 'overridden value for method'], + 'second_data_set' => ['expected_config_value' => 'overridden value for data set'] + ]; + } + + /** + * Checks that fixtures can be added on website scope + * + * @return void + */ + public function testAddFixtureOnWebsiteScope(): void + { + $value = $this->config->getValue('test_section/test_group/field_1', ScopeInterface::SCOPE_WEBSITES, 'base'); + $this->assertEquals('overridden value for method on website scope', $value); + $this->assertEquals( + 'overridden value for method on website scope', + $this->configStorage->getValueFromDb( + 'test_section/test_group/field_1', + ScopeInterface::SCOPE_WEBSITES, + 'base' + ) + ); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiConfigFixture/RemoveFixtureTest.php b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiConfigFixture/RemoveFixtureTest.php new file mode 100644 index 0000000000000..148f18b4cc811 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiConfigFixture/RemoveFixtureTest.php @@ -0,0 +1,178 @@ +config = $this->objectManager->get(ScopeConfigInterface::class); + $this->configStorage = $this->objectManager->get(ConfigStorage::class); + } + + /** + * Checks that fixture can be removed in test class node + * + * @magentoConfigFixture default_store test_section/test_group/field_1 new_value + * + * @return void + */ + public function testRemoveFixtureForClass(): void + { + $value = $this->config->getValue( + 'test_section/test_group/field_1', + ScopeInterface::SCOPE_STORES, + 'default' + ); + $this->assertEquals('1st field default value', $value); + $this->assertFalse( + $this->configStorage->checkIsRecordExist( + 'test_section/test_group/field_1', + ScopeInterface::SCOPE_STORES, + 'default' + ) + ); + } + + /** + * Checks that fixtures can be removed in method and data set nodes + * + * @magentoConfigFixture default_store test_section/test_group/field_2 new_value + * @magentoConfigFixture default_store test_section/test_group/field_3 new_value + * + * @dataProvider testDataProvider + * + * @param string $expectedFirstValue + * @param string $expectedSecondValue + * @param bool $firstvalueExist + * @param bool $secondvalueExist + * @return void + */ + public function testRemoveFixtureForMethod( + string $expectedFirstValue, + string $expectedSecondValue, + bool $firstvalueExist, + bool $secondvalueExist + ): void { + $fistValue = $this->config->getValue( + 'test_section/test_group/field_2', + ScopeInterface::SCOPE_STORES, + 'default' + ); + $secondValue = $this->config->getValue( + 'test_section/test_group/field_3', + ScopeInterface::SCOPE_STORES, + 'default' + ); + $this->assertEquals($expectedFirstValue, $fistValue); + if ($firstvalueExist) { + $this->assertEquals( + $expectedFirstValue, + $this->configStorage->getValueFromDb( + 'test_section/test_group/field_2', + ScopeInterface::SCOPE_STORES, + 'default' + ) + ); + } + + $this->assertEquals( + $firstvalueExist, + $this->configStorage->checkIsRecordExist( + 'test_section/test_group/field_2', + ScopeInterface::SCOPE_STORES, + 'default' + ) + ); + $this->assertEquals($expectedSecondValue, $secondValue); + if ($secondvalueExist) { + $this->assertEquals( + $expectedSecondValue, + $this->configStorage->getValueFromDb( + 'test_section/test_group/field_3', + ScopeInterface::SCOPE_STORES, + 'default' + ) + ); + } + $this->assertEquals( + $secondvalueExist, + $this->configStorage->checkIsRecordExist( + 'test_section/test_group/field_3', + ScopeInterface::SCOPE_STORES, + 'default' + ) + ); + } + + /** + * @return array + */ + public function testDataProvider(): array + { + return [ + 'first_data_set' => [ + 'expected_first_config_value' => '2nd field default value', + 'expected_second_config_value' => 'new_value', + 'first_value_exist' => false, + 'second_value_exist' => true, + ], + 'second_data_set' => [ + 'expected_first_config_value' => '2nd field default value', + 'expected_second_config_value' => '3rd field website scope default value', + 'first_value_exist' => false, + 'second_value_exist' => false, + ], + ]; + } + + /** + * Checks that website scope fixture can be removed + * + * @magentoConfigFixture base_website test_section/test_group/field_3 new_value + * + * @return void + */ + public function testRemoveWebsiteScopeFixture(): void + { + $value = $this->config->getValue( + 'test_section/test_group/field_3', + ScopeInterface::SCOPE_WEBSITES, + 'base' + ); + $this->assertEquals('3rd field website scope default value', $value); + $this->assertFalse( + $this->configStorage->checkIsRecordExist( + 'test_section/test_group/field_3', + ScopeInterface::SCOPE_WEBSITES, + 'base' + ) + ); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiConfigFixture/ReplaceFixtureTest.php b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiConfigFixture/ReplaceFixtureTest.php new file mode 100644 index 0000000000000..d8342fe3394ce --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiConfigFixture/ReplaceFixtureTest.php @@ -0,0 +1,178 @@ +config = $this->objectManager->get(ScopeConfigInterface::class); + $this->configStorage = $this->objectManager->get(ConfigStorage::class); + } + + /** + * Checks that fixture can be replaced in test class node + * + * @magentoConfigFixture default_store test_section/test_group/field_1 new_value + * + * @return void + */ + public function testReplaceFixtureForClass(): void + { + $expectedValue = 'Overridden fixture for class'; + $value = $this->config->getValue('test_section/test_group/field_1', ScopeInterface::SCOPE_STORES, 'default'); + $this->assertEquals($expectedValue, $value); + $this->assertEquals( + $expectedValue, + $this->configStorage->getValueFromDb( + 'test_section/test_group/field_1', + ScopeInterface::SCOPE_STORES, + 'default' + ) + ); + } + + /** + * Checks that fixture can be replaced in method and data set nodes + * + * @magentoConfigFixture default_store test_section/test_group/field_1 new_value + * + * @dataProvider testDataProvider + * + * @param string $expectedConfigValue + * @return void + */ + public function testReplaceFixtureForMethod(string $expectedConfigValue): void + { + $value = $this->config->getValue('test_section/test_group/field_1', ScopeInterface::SCOPE_STORES, 'default'); + $this->assertEquals($expectedConfigValue, $value); + $this->assertEquals( + $expectedConfigValue, + $this->configStorage->getValueFromDb( + 'test_section/test_group/field_1', + ScopeInterface::SCOPE_STORES, + 'default' + ) + ); + } + + /** + * @return array + */ + public function testDataProvider(): array + { + return [ + 'first_data_set' => [ + 'expected_config_value' => 'Overridden fixture for method', + ], + 'second_data_set' => [ + 'expected_config_value' => 'Overridden fixture for data set', + ], + ]; + } + + /** + * Checks that website scope fixture can be replaced + * + * @magentoConfigFixture base_website test_section/test_group/field_1 new_value + * + * @return void + */ + public function testReplaceWebsiteScopedFixture(): void + { + $expectedConfigValue = 'Overridden value for website scope'; + $value = $this->config->getValue('test_section/test_group/field_1', ScopeInterface::SCOPE_WEBSITES, 'base'); + $this->assertEquals($expectedConfigValue, $value); + $this->assertEquals( + $expectedConfigValue, + $this->configStorage->getValueFromDb( + 'test_section/test_group/field_1', + ScopeInterface::SCOPE_WEBSITE, + 'base' + ) + ); + } + + /** + * Checks that replace config from last loaded file will be applied + * + * @magentoConfigFixture default_store test_section/test_group/field_1 new_value + * + * @dataProvider configValuesProvider + * + * @param string $expectedConfigValue + * @return void + */ + public function testReplaceFixtureViaThirdModule(string $expectedConfigValue): void + { + $value = $this->config->getValue('test_section/test_group/field_1', ScopeInterface::SCOPE_STORES, 'default'); + $this->assertEquals($expectedConfigValue, $value); + $this->assertEquals( + $expectedConfigValue, + $this->configStorage->getValueFromDb( + 'test_section/test_group/field_1', + ScopeInterface::SCOPE_STORES, + 'default' + ) + ); + } + + /** + * @return array + */ + public function configValuesProvider(): array + { + return [ + 'first_data_set' => [ + 'expected_config_value' => 'Overridden fixture for method from third module', + ], + 'second_data_set' => [ + 'expected_config_value' => 'Overridden fixture for data set from third module', + ], + ]; + } + + /** + * Checks that fixture for global scope can be replaced + * + * @magentoConfigFixture test_section/test_group/field_1 new_value + * + * @return void + */ + public function testReplaceDefaultConfig(): void + { + $expectedConfigValue = 'Overridden value for default scope'; + $value = $this->config->getValue('test_section/test_group/field_1'); + $this->assertEquals('Overridden value for default scope', $value); + $this->assertEquals( + $expectedConfigValue, + $this->configStorage->getValueFromDb('test_section/test_group/field_1') + ); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiDataFixture/AddFixtureTest.php b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiDataFixture/AddFixtureTest.php new file mode 100644 index 0000000000000..2021bbf10672b --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiDataFixture/AddFixtureTest.php @@ -0,0 +1,97 @@ +fixtureCallStorage = $this->objectManager->get(FixtureCallStorage::class); + } + + /** + * Checks that fixtures added in all nodes successfully applied + * + * @dataProvider addedFixturesProvider + * + * @param array $fixtures + * @return void + */ + public function testAddFixtures(array $fixtures): void + { + foreach ($fixtures as $scope => $fixture) { + $this->assertEquals( + 1, + $this->fixtureCallStorage->getFixturesCount($fixture), + sprintf('Fixture added in %s scope was not called', $scope) + ); + } + } + + /** + * @return array + */ + public function addedFixturesProvider(): array + { + return [ + 'first_data_set' => [ + [ + 'class' => 'fixture1_second_module.php', + 'method' => 'fixture2_second_module.php', + 'data_set' => 'fixture3_second_module.php', + ], + ], + 'second_data_set' => [ + [ + 'class' => 'fixture1_second_module.php', + 'method' => 'fixture2_second_module.php', + ], + ], + ]; + } + + /** + * Checks that same fixture can be added via override config from few files + * + * @return void + */ + public function testAddSameFixtures(): void + { + $this->assertEquals( + 3, + $this->fixtureCallStorage->getFixturesCount('fixture2_second_module.php') + ); + } + + /** + * Checks that fixture which require another fixture can be added using override + * + * @return void + */ + public function testAddFixtureWithRequiredFixture(): void + { + $this->assertEquals(1, $this->fixtureCallStorage->getFixturesCount('fixture_with_required_fixture.php')); + $this->assertEquals(1, $this->fixtureCallStorage->getFixturesCount('fixture3_second_module.php')); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiDataFixture/RemoveFixtureTest.php b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiDataFixture/RemoveFixtureTest.php new file mode 100644 index 0000000000000..7521770873b0a --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiDataFixture/RemoveFixtureTest.php @@ -0,0 +1,84 @@ +fixtureCallStorage = $this->objectManager->get(FixtureCallStorage::class); + } + + /** + * Checks that fixture can be removed in test class node + * + * @magentoDataFixture Magento/TestModuleOverrideConfig/_files/fixture1_first_module.php + * + * @return void + */ + public function testRemoveFixtureForClass(): void + { + $this->assertEmpty($this->fixtureCallStorage->getFixturesCount('fixture1_first_module.php')); + } + + /** + * Checks that fixture can be removed in method and data set nodes + * + * @magentoDataFixture Magento/TestModuleOverrideConfig/_files/fixture2_first_module.php + * @magentoDataFixture Magento/TestModuleOverrideConfig/_files/fixture3_first_module.php + * + * @dataProvider testDataProvider + * + * @param string $fixtureName + * @return void + */ + public function testRemoveFixtureForMethod(string $fixtureName): void + { + $this->assertEmpty($this->fixtureCallStorage->getFixturesCount($fixtureName)); + } + + /** + * @return array + */ + public function testDataProvider(): array + { + return [ + 'first_data_set' => ['fixture2_first_module.php'], + 'second_data_set' => ['fixture3_first_module.php'], + ]; + } + + /** + * Checks that same fixtures can be removed few times + * + * @magentoDataFixture Magento/TestModuleOverrideConfig/_files/fixture2_first_module.php + * @magentoDataFixture Magento/TestModuleOverrideConfig/_files/fixture2_first_module.php + * + * @return void + */ + public function testRemoveSameFixtures(): void + { + $this->assertEmpty($this->fixtureCallStorage->getFixturesCount('fixture3_first_module.php')); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiDataFixture/ReplaceFixtureTest.php b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiDataFixture/ReplaceFixtureTest.php new file mode 100644 index 0000000000000..a1892cf6af32a --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiDataFixture/ReplaceFixtureTest.php @@ -0,0 +1,136 @@ +fixtureCallStorage = $this->objectManager->get(FixtureCallStorage::class); + } + + /** + * Checks that fixture can be replaced in test class node + * + * @magentoApiDataFixture Magento/TestModuleOverrideConfig/_files/fixture1_first_module.php + * + * @return void + */ + public function testReplaceFixtureForClass(): void + { + $this->assertEquals(0, $this->fixtureCallStorage->getFixturesCount('fixture1_first_module.php')); + $this->assertEquals(1, $this->fixtureCallStorage->getFixturesCount('fixture1_second_module.php')); + } + + /** + * Checks that fixture can be replaced in method and data set nodes + * + * @dataProvider replacedFixturesProvider + * + * @magentoApiDataFixture Magento/TestModuleOverrideConfig/_files/fixture1_first_module.php + * + * @param string $fixture + * @return void + */ + public function testReplaceFixturesForMethod(string $fixture): void + { + $this->assertEquals(0, $this->fixtureCallStorage->getFixturesCount('fixture1_first_module.php')); + $this->assertEquals(1, $this->fixtureCallStorage->getFixturesCount($fixture)); + } + + /** + * @return array + */ + public function replacedFixturesProvider(): array + { + return [ + 'first_data_set' => [ + 'fixture2_second_module.php', + ], + 'second_data_set' => [ + 'fixture3_second_module.php', + ], + ]; + } + + /** + * Checks that replace config from last loaded file will be applied + * + * @dataProvider dataProvider + * + * @magentoApiDataFixture Magento/TestModuleOverrideConfig/_files/fixture1_first_module.php + * + * @param string $fixture + * @return void + */ + public function testReplaceFixtureViaThirdModule(string $fixture): void + { + $this->assertEquals(0, $this->fixtureCallStorage->getFixturesCount('fixture1_first_module.php')); + $this->assertEquals(1, $this->fixtureCallStorage->getFixturesCount($fixture)); + } + + /** + * @return array + */ + public function dataProvider(): array + { + return [ + 'first_data_set' => [ + 'fixture2_second_module.php', + ], + 'second_data_set' => [ + 'fixture3_second_module.php', + ], + ]; + } + + /** + * Checks that fixture required in the another fixture can be replaced using override + * + * @magentoApiDataFixture Magento/TestModuleOverrideConfig2/_files/fixture_with_required_fixture.php + * + * @return void + */ + public function testReplaceRequiredFixture(): void + { + $this->assertEquals(1, $this->fixtureCallStorage->getFixturesCount('fixture_with_required_fixture.php')); + $this->assertEquals(1, $this->fixtureCallStorage->getFixturesCount('fixture2_second_module.php')); + $this->assertEmpty($this->fixtureCallStorage->getFixturesCount('fixture3_second_module.php')); + } + + /** + * Checks that fixture required in the another fixture will be replaced according to last loaded override + * + * @magentoApiDataFixture Magento/TestModuleOverrideConfig2/_files/fixture_with_required_fixture.php + * + * @return void + */ + public function testReplaceRequiredFixtureViaThirdModule(): void + { + $this->assertEquals(1, $this->fixtureCallStorage->getFixturesCount('fixture_with_required_fixture.php')); + $this->assertEquals(1, $this->fixtureCallStorage->getFixturesCount('fixture1_third_module.php')); + $this->assertEmpty($this->fixtureCallStorage->getFixturesCount('fixture2_second_module.php')); + $this->assertEmpty($this->fixtureCallStorage->getFixturesCount('fixture3_second_module.php')); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiDataFixture/SortFixturesTest.php b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiDataFixture/SortFixturesTest.php new file mode 100644 index 0000000000000..dc5b7d9e167a5 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/MagentoApiDataFixture/SortFixturesTest.php @@ -0,0 +1,78 @@ +fixtureCallStorage = $this->objectManager->get(FixtureCallStorage::class); + } + + /** + * Checks that fixtures can be placed to specific place according to config + * + * @dataProvider sortFixturesProvider + * + * @magentoApiDataFixture Magento/TestModuleOverrideConfig/_files/fixture1_first_module.php + * @magentoApiDataFixture Magento/TestModuleOverrideConfig/_files/fixture2_first_module.php + * @magentoApiDataFixture Magento/TestModuleOverrideConfig/_files/fixture3_first_module.php + * + * @param array $sortedFixtures + * @return void + */ + public function testSortFixtures(array $sortedFixtures): void + { + $this->assertEquals($sortedFixtures, $this->fixtureCallStorage->getStorage()); + } + + /** + * @return array + */ + public function sortFixturesProvider(): array + { + return [ + 'first_data_set' => [ + 'sorted_fixtures' => [ + 'fixture3_second_module.php', + 'fixture1_first_module.php', + 'fixture1_second_module.php', + 'fixture2_first_module.php', + 'fixture1_third_module.php', + 'fixture3_first_module.php', + 'fixture2_second_module.php', + ], + ], + 'second_data_set' => [ + 'sorted_fixtures' => [ + 'fixture1_first_module.php', + 'fixture1_second_module.php', + 'fixture2_first_module.php', + 'fixture3_first_module.php', + 'fixture2_second_module.php', + ], + ], + ]; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/Skip/SkipClassTest.php b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/Skip/SkipClassTest.php new file mode 100644 index 0000000000000..05f6648c0559a --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/Skip/SkipClassTest.php @@ -0,0 +1,28 @@ +fail('This test should be skipped via override config in test class node'); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/Skip/SkipDataSetTest.php b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/Skip/SkipDataSetTest.php new file mode 100644 index 0000000000000..fec1caa5370e7 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/Skip/SkipDataSetTest.php @@ -0,0 +1,43 @@ +dataName() === 'first_data_set') { + $this->fail('This test should be skipped via override config in data set node'); + } + } + + /** + * @return array + */ + public function testDataProvider(): array + { + return [ + 'first_data_set' => [], + 'second_data_set' => [], + ]; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/Skip/SkipMethodTest.php b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/Skip/SkipMethodTest.php new file mode 100644 index 0000000000000..f824755650e1a --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/TestModuleOverrideConfig/Skip/SkipMethodTest.php @@ -0,0 +1,28 @@ +fail('This test should be skipped via override config in method node'); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/WebApiTest.php b/dev/tests/api-functional/testsuite/Magento/WebApiTest.php new file mode 100644 index 0000000000000..32670dfeb7b1b --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/WebApiTest.php @@ -0,0 +1,71 @@ +get(self::getConfigurationFile()); + $suitesConfig = $configuration->testSuite(); + $suite = new TestSuite(); + /** @var \PHPUnit\TextUI\Configuration\TestSuite $suiteConfig */ + foreach ($suitesConfig as $suiteConfig) { + $suites = (new TestSuiteMapper())->map(TestSuiteCollection::fromArray([$suiteConfig]), ''); + /** @var TestSuite $testSuite */ + foreach ($suites as $testSuite) { + /** @var TestSuite $test */ + foreach ($testSuite as $test) { + $testName = $test->getName(); + + if ($overrideConfig->hasSkippedTest($testName) && !$test instanceof SkippableInterface) { + $reflectionClass = new \ReflectionClass($testName); + $resultTest = $generator->generateTestWrapper($reflectionClass); + $suite->addTest(new TestSuite($resultTest, $testName)); + } else { + $suite->addTest($test); + } + } + } + } + + return $suite; + } + + /** + * Returns config file name from command line params. + * + * @return string + */ + private static function getConfigurationFile(): string + { + $params = getopt('c:', ['configuration:']); + $longConfig = $params['configuration'] ?? ''; + $shortConfig = $params['c'] ?? ''; + + return $shortConfig ? $shortConfig : $longConfig; + } +} diff --git a/dev/tests/integration/_files/Magento/TestModuleOverrideConfig/Model/FixtureCallStorage.php b/dev/tests/integration/_files/Magento/TestModuleOverrideConfig/Model/FixtureCallStorage.php index daf4f7284b2d8..aa21cba143841 100644 --- a/dev/tests/integration/_files/Magento/TestModuleOverrideConfig/Model/FixtureCallStorage.php +++ b/dev/tests/integration/_files/Magento/TestModuleOverrideConfig/Model/FixtureCallStorage.php @@ -10,6 +10,8 @@ /** * Class represent simple container to save data + * + * phpcs:disable Generic.Classes.DuplicateClassName */ class FixtureCallStorage { @@ -30,11 +32,11 @@ public function addFixtureToStorage(string $fixture): void * Get fixture position in storage * * @param string $fixture - * @return false|int + * @return null|int */ - public function getFixturePosition(string $fixture) + public function getFixturePosition(string $fixture): ?int { - return array_search($fixture, $this->storage); + return array_search($fixture, $this->storage) ?: null; } /** diff --git a/dev/tests/integration/_files/Magento/TestModuleOverrideConfig/composer.json b/dev/tests/integration/_files/Magento/TestModuleOverrideConfig/composer.json index 85dfc1f4499e6..47ac2d4ac4a3b 100644 --- a/dev/tests/integration/_files/Magento/TestModuleOverrideConfig/composer.json +++ b/dev/tests/integration/_files/Magento/TestModuleOverrideConfig/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0||~7.3.0", + "php": "~7.3.0||~7.4.0", "magento/framework": "*", "magento/module-integration": "*" }, diff --git a/dev/tests/integration/_files/Magento/TestModuleOverrideConfig2/Test/Integration/_files/overrides.xml b/dev/tests/integration/_files/Magento/TestModuleOverrideConfig2/Test/Integration/_files/overrides.xml index 3d0db9f3c4283..45bc6115e3704 100644 --- a/dev/tests/integration/_files/Magento/TestModuleOverrideConfig2/Test/Integration/_files/overrides.xml +++ b/dev/tests/integration/_files/Magento/TestModuleOverrideConfig2/Test/Integration/_files/overrides.xml @@ -152,12 +152,13 @@ - - + + + diff --git a/dev/tests/integration/_files/Magento/TestModuleOverrideConfig2/composer.json b/dev/tests/integration/_files/Magento/TestModuleOverrideConfig2/composer.json index 315db25a09731..43b7bec56945d 100644 --- a/dev/tests/integration/_files/Magento/TestModuleOverrideConfig2/composer.json +++ b/dev/tests/integration/_files/Magento/TestModuleOverrideConfig2/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0||~7.3.0", + "php": "~7.3.0||~7.4.0", "magento/framework": "*", "magento/module-integration": "*" }, diff --git a/dev/tests/integration/_files/Magento/TestModuleOverrideConfig3/Test/Integration/_files/overrides.xml b/dev/tests/integration/_files/Magento/TestModuleOverrideConfig3/Test/Integration/_files/overrides.xml index fd22cc21f6c6a..bbb3fad88e5cd 100644 --- a/dev/tests/integration/_files/Magento/TestModuleOverrideConfig3/Test/Integration/_files/overrides.xml +++ b/dev/tests/integration/_files/Magento/TestModuleOverrideConfig3/Test/Integration/_files/overrides.xml @@ -35,12 +35,15 @@ + + + diff --git a/dev/tests/integration/_files/Magento/TestModuleOverrideConfig3/composer.json b/dev/tests/integration/_files/Magento/TestModuleOverrideConfig3/composer.json index 6ada46e46fbe3..432b2ef703a57 100644 --- a/dev/tests/integration/_files/Magento/TestModuleOverrideConfig3/composer.json +++ b/dev/tests/integration/_files/Magento/TestModuleOverrideConfig3/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0||~7.3.0", + "php": "~7.3.0||~7.4.0", "magento/framework": "*", "magento/module-integration": "*" }, diff --git a/dev/tests/integration/framework/Magento/TestFramework/Annotation/AbstractDataFixture.php b/dev/tests/integration/framework/Magento/TestFramework/Annotation/AbstractDataFixture.php index a835e73cce826..2f4b7bf79c1d6 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Annotation/AbstractDataFixture.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Annotation/AbstractDataFixture.php @@ -107,7 +107,6 @@ protected function _applyOneFixture($fixture) * * @param array $fixtures * @return void - * @throws LocalizedException */ protected function _applyFixtures(array $fixtures) { diff --git a/dev/tests/integration/framework/Magento/TestFramework/Annotation/ConfigFixture.php b/dev/tests/integration/framework/Magento/TestFramework/Annotation/ConfigFixture.php index e2c54584db41d..4c1e31c85ec77 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Annotation/ConfigFixture.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Annotation/ConfigFixture.php @@ -35,26 +35,21 @@ class ConfigFixture * * @var array */ - private $globalConfigValues = []; + protected $globalConfigValues = []; /** * Original values for website-scoped configuration options that need to be restored * * @var array */ - private $websiteConfigValues = []; + protected $websiteConfigValues = []; /** * Original values for store-scoped configuration options that need to be restored * * @var array */ - private $storeConfigValues = []; - - /** - * @var string - */ - protected $annotation = 'magentoConfigFixture'; + protected $storeConfigValues = []; /** * Retrieve configuration node value @@ -164,33 +159,66 @@ protected function _assignConfigData(TestCase $test) ); foreach ($testAnnotations as $configPathAndValue) { if (preg_match('/^.+?(?=_store\s)/', $configPathAndValue, $matches)) { - /* Store-scoped config value */ - $storeCode = $matches[0] != 'current' ? $matches[0] : null; - $parts = preg_split('/\s+/', $configPathAndValue, 3); - list($configScope, $configPath, $requiredValue) = $parts + ['', '', '']; - $originalValue = $this->_getConfigValue($configPath, $storeCode); - $this->storeConfigValues[$storeCode][$configPath] = $originalValue; - $this->_setConfigValue($configPath, $requiredValue, $storeCode); + $this->setStoreConfigValue($matches ?? [], $configPathAndValue); } elseif (preg_match('/^.+?(?=_website\s)/', $configPathAndValue, $matches)) { - /* Website-scoped config value */ - $websiteCode = $matches[0] != 'current' ? $matches[0] : null; - $parts = preg_split('/\s+/', $configPathAndValue, 3); - list($configScope, $configPath, $requiredValue) = $parts + ['', '', '']; - $originalValue = $this->getScopeConfigValue($configPath, ScopeInterface::SCOPE_WEBSITES, $websiteCode); - $this->websiteConfigValues[$websiteCode][$configPath] = $originalValue; - $this->setScopeConfigValue($configPath, $requiredValue, ScopeInterface::SCOPE_WEBSITES, $websiteCode); + $this->setWebsiteConfigValue($matches ?? [], $configPathAndValue); } else { - /* Global config value */ - list($configPath, $requiredValue) = preg_split('/\s+/', $configPathAndValue, 2); - - $originalValue = $this->_getConfigValue($configPath); - $this->globalConfigValues[$configPath] = $originalValue; - - $this->_setConfigValue($configPath, $requiredValue); + $this->setGlobalConfigValue($configPathAndValue); } } } + /** + * Sets store-scoped config value + * + * @param array $matches + * @param string $configPathAndValue + * @return void + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + protected function setStoreConfigValue(array $matches, $configPathAndValue): void + { + $storeCode = $matches[0] != 'current' ? $matches[0] : null; + $parts = preg_split('/\s+/', $configPathAndValue, 3); + list($configScope, $configPath, $requiredValue) = $parts + ['', '', '']; + $originalValue = $this->_getConfigValue($configPath, $storeCode); + $this->storeConfigValues[$storeCode][$configPath] = $originalValue; + $this->_setConfigValue($configPath, $requiredValue, $storeCode); + } + + /** + * Sets website-scoped config value + * + * @param array $matches + * @param string $configPathAndValue + * @return void + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + protected function setWebsiteConfigValue(array $matches, $configPathAndValue): void + { + $websiteCode = $matches[0] != 'current' ? $matches[0] : null; + $parts = preg_split('/\s+/', $configPathAndValue, 3); + list($configScope, $configPath, $requiredValue) = $parts + ['', '', '']; + $originalValue = $this->getScopeConfigValue($configPath, ScopeInterface::SCOPE_WEBSITES, $websiteCode); + $this->websiteConfigValues[$websiteCode][$configPath] = $originalValue; + $this->setScopeConfigValue($configPath, $requiredValue, ScopeInterface::SCOPE_WEBSITES, $websiteCode); + } + + /** + * Sets global config value + * + * @param string $configPathAndValue + * @return void + */ + protected function setGlobalConfigValue($configPathAndValue): void + { + /* Global config value */ + list($configPath, $requiredValue) = preg_split('/\s+/', $configPathAndValue, 2); + $originalValue = $this->_getConfigValue($configPath); + $this->globalConfigValues[$configPath] = $originalValue; + $this->_setConfigValue($configPath, $requiredValue); + } + /** * Restore original values for changed config options * diff --git a/dev/tests/integration/framework/Magento/TestFramework/Config/Model/ConfigStorage.php b/dev/tests/integration/framework/Magento/TestFramework/Config/Model/ConfigStorage.php new file mode 100644 index 0000000000000..7fe25f3a6f61c --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/Config/Model/ConfigStorage.php @@ -0,0 +1,123 @@ +configResource = $configResource; + $this->storeManager = $storeManager; + } + + /** + * Get value from db + * + * @param string $path + * @param string $scope + * @param string|null $scopeCode + * @return string|false + */ + public function getValueFromDb( + string $path, + string $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, + ?string $scopeCode = null + ) { + $connect = $this->configResource->getConnection(); + $scope = $this->normalizeScope($scope); + $scopeId = $this->getIdByScope($scope, $scopeCode); + + $select = $connect->select()->from(['main_table' => $this->configResource->getMainTable()], 'value') + ->where('main_table.path = ?', $path) + ->where('main_table.scope = ?', $scope) + ->where('main_table.scope_id = ?', $scopeId); + + return $connect->fetchOne($select); + } + + /** + * Check is record exist in DB + * + * @param string $path + * @param string $scope + * @param string|null $scopeCode + * @return bool + */ + public function checkIsRecordExist( + string $path, + string $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, + ?string $scopeCode = null + ): bool { + $connect = $this->configResource->getConnection(); + $scope = $this->normalizeScope($scope); + $scopeId = $this->getIdByScope($scope, $scopeCode); + + $select = $connect->select()->from(['main_table' => $this->configResource->getMainTable()], 'COUNT(*)') + ->where('main_table.path = ?', $path) + ->where('main_table.scope = ?', $scope) + ->where('main_table.scope_id = ?', $scopeId); + + return (bool)$connect->fetchOne($select); + } + + /** + * Get scope id by scope code + * + * @param string $scope + * @param string|null $scopeCode + * @return int + */ + private function getIdByScope(string $scope, ?string $scopeCode): int + { + $scopeId = 0; + + if ($scope === ScopeInterface::SCOPE_WEBSITES) { + $scopeId = (int)$this->storeManager->getWebsite($scopeCode)->getId(); + } elseif ($scope === ScopeInterface::SCOPE_STORES) { + $scopeId = (int)$this->storeManager->getStore($scopeCode)->getId(); + } + + return $scopeId; + } + + /** + * Normalize scope + * + * @param string $scope + * @return string + */ + private function normalizeScope(string $scope): string + { + if ($scope === ScopeInterface::SCOPE_WEBSITE) { + $scope = ScopeInterface::SCOPE_WEBSITES; + } + if ($scope === ScopeInterface::SCOPE_STORE) { + $scope = ScopeInterface::SCOPE_STORES; + } + + return $scope; + } +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/Workaround/Override/Config.php b/dev/tests/integration/framework/Magento/TestFramework/Workaround/Override/Config.php index 5d2d0e385b9e3..4a0ad01a909e3 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Workaround/Override/Config.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Workaround/Override/Config.php @@ -8,13 +8,17 @@ namespace Magento\TestFramework\Workaround\Override; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Config\ConverterInterface; use Magento\Framework\Config\Reader\Filesystem; +use Magento\Framework\Config\SchemaLocatorInterface; +use Magento\Framework\Config\ValidationStateInterface; use Magento\Framework\View\File\Collector\Decorator\ModuleDependency; use Magento\Framework\View\File\Collector\Decorator\ModuleOutput; +use Magento\Framework\View\File\CollectorInterface; use Magento\TestFramework\Workaround\Override\Config\Converter; +use Magento\TestFramework\Workaround\Override\Config\Dom; use Magento\TestFramework\Workaround\Override\Config\FileCollector; use Magento\TestFramework\Workaround\Override\Config\FileResolver; -use Magento\TestFramework\Workaround\Override\Config\Dom; use Magento\TestFramework\Workaround\Override\Config\SchemaLocator; use Magento\TestFramework\Workaround\Override\Config\ValidationState; use PHPUnit\Framework\TestCase; @@ -24,7 +28,7 @@ * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Config +class Config implements ConfigInterface { /** * @var self @@ -37,18 +41,67 @@ class Config private $config; /** - * @param array $config + * Self instance getter. + * + * @return ConfigInterface */ - public function __construct(array $config) + public static function getInstance(): ConfigInterface { - $this->config = $config; + if (empty(self::$instance)) { + throw new \RuntimeException('Override config isn\'t initialized'); + } + + return self::$instance; } /** - * Returns an array with skip key and skipMessage key if test is skipped. + * Self instance setter. * - * @param TestCase $test - * @return array + * @param ConfigInterface $config + * @return void + */ + public static function setInstance(ConfigInterface $config): void + { + self::$instance = $config; + } + + /** + * Reads configuration from files. + * + * @return void + */ + public function init(): void + { + if (empty($this->config)) { + $data = []; + $useConfig = (defined('USE_OVERRIDE_CONFIG') && USE_OVERRIDE_CONFIG === 'enabled'); + + if ($useConfig) { + $reader = ObjectManager::getInstance()->create( + Filesystem::class, + [ + 'fileName' => 'overrides.xml', + 'fileResolver' => $this->getFileResolver(), + 'idAttributes' => [ + '/overrides/test' => 'class', + '/overrides/test/method' => 'name', + '/overrides/test/method/dataSet' => 'name', + ], + 'schemaLocator' => $this->getSchemaLocator(), + 'validationState' => $this->getValidationState(), + 'converter' => $this->getConverter(), + 'domDocumentClass' => $this->getDomClass(), + ] + ); + $data = $reader->read(); + } + + $this->config = $data; + } + } + + /** + * @inheritdoc */ public function getSkipConfiguration(TestCase $test): array { @@ -69,10 +122,7 @@ public function getSkipConfiguration(TestCase $test): array } /** - * Test has configuration flag. - * - * @param string $className - * @return bool + * @inheritdoc */ public function hasSkippedTest(string $className): bool { @@ -82,129 +132,143 @@ public function hasSkippedTest(string $className): bool } /** - * Check that class has even one test skipped - * - * @param array $config - * @return bool + * @inheritdoc */ - private function isSkippedByConfig(array $config): bool + public function getClassConfig(TestCase $test, ?string $fixtureType = null): array { - if (isset($config['skip']) && $config['skip']) { - return true; + $result = $this->config[$this->getOriginalClassName($test)] ?? []; + if ($fixtureType) { + $result = $result[$fixtureType] ?? []; } - foreach ($config as $lowerLevelConfig) { - if (is_array($lowerLevelConfig)) { - return $this->isSkippedByConfig($lowerLevelConfig); - } + return $result; + } + + /** + * @inheritdoc + */ + public function getMethodConfig(TestCase $test, ?string $fixtureType = null): array + { + $config = $this->getClassConfig($test)[$test->getName(false)] ?? []; + + if ($fixtureType) { + $config = $config[$fixtureType] ?? []; } - return false; + return $config; } /** - * Self instance getter. - * - * @return static + * @inheritdoc */ - public static function getInstance(): self + public function getDataSetConfig(TestCase $test, ?string $fixtureType = null): array { - if (empty(self::$instance)) { - $data = []; - $objectManager = ObjectManager::getInstance(); - $useConfig = (defined('USE_OVERRIDE_CONFIG') && USE_OVERRIDE_CONFIG === 'enabled'); + $config = $this->getClassConfig($test)[$test->getName(false)][(string)$test->dataName()] ?? []; + if ($fixtureType) { + $config = $config[$fixtureType] ?? []; + } - if ($useConfig) { - $fileResolver = $objectManager->create( - FileResolver::class, + return $config; + } + + /** + * Returns file resolver. + * + * @return FileResolver + */ + protected function getFileResolver(): FileResolver + { + return ObjectManager::getInstance()->create( + FileResolver::class, + [ + 'baseFiles' => ObjectManager::getInstance()->create( + ModuleDependency::class, [ - 'baseFiles' => $objectManager->create( - ModuleDependency::class, + 'subject' => ObjectManager::getInstance()->create( + ModuleOutput::class, [ - 'subject' => $objectManager->create( - ModuleOutput::class, - [ - 'subject' => $objectManager->create(FileCollector::class) - ] - ) + 'subject' => $this->getFileCollector() ] ) ] - ); - $reader = $objectManager->create( - Filesystem::class, - [ - 'fileName' => 'overrides.xml', - 'fileResolver' => $fileResolver, - 'idAttributes' => [ - '/overrides/test' => 'class', - '/overrides/test/method' => 'name', - '/overrides/test/method/dataSet' => 'name', - ], - 'schemaLocator' => $objectManager->create(SchemaLocator::class), - 'validationState' => $objectManager->create(ValidationState::class), - 'converter' => $objectManager->create(Converter::class), - 'domDocumentClass' => Dom::class, - ] - ); - $data = $reader->read(); - } - - self::$instance = new self($data); - } + ) + ] + ); + } - return self::$instance; + /** + * Returns schema locator. + * + * @return SchemaLocatorInterface + */ + protected function getSchemaLocator(): SchemaLocatorInterface + { + return ObjectManager::getInstance()->create(SchemaLocator::class); } /** - * Get config from class node + * Returns validation state. * - * @param TestCase $test - * @param string|null $fixtureType - * @return array + * @return ValidationStateInterface */ - public function getClassConfig(TestCase $test, ?string $fixtureType = null): array + protected function getValidationState(): ValidationStateInterface { - $result = $this->config[$this->getOriginalClassName($test)] ?? []; - if ($fixtureType) { - $result = $result[$fixtureType] ?? []; - } + return ObjectManager::getInstance()->create(ValidationState::class); + } - return $result; + /** + * Returns converter for config files. + * + * @return ConverterInterface + */ + protected function getConverter(): ConverterInterface + { + return ObjectManager::getInstance()->create(Converter::class); } /** - * Get config from method node + * Returns DOM class name. * - * @param TestCase $test - * @param string|null $fixtureType - * @return array + * @return string */ - public function getMethodConfig(TestCase $test, ?string $fixtureType = null): array + protected function getDomClass(): string { - $config = $this->getClassConfig($test)[$test->getName(false)] ?? []; - if ($fixtureType) { - $config = $config[$fixtureType] ?? []; - } + return Dom::class; + } - return $config; + /** + * Returns file collector. + * + * @return CollectorInterface + */ + protected function getFileCollector(): CollectorInterface + { + return ObjectManager::getInstance()->create(FileCollector::class); } /** - * Get config from dataSet node + * Check that class has even one test skipped * - * @param TestCase $test - * @param string|null $fixtureType - * @return array + * @param array $config + * @return bool */ - public function getDataSetConfig(TestCase $test, ?string $fixtureType = null): array + private function isSkippedByConfig(array $config): bool { - $config = $this->getClassConfig($test)[$test->getName(false)][(string)$test->dataName()] ?? []; - if ($fixtureType) { - $config = $config[$fixtureType] ?? []; + $result = false; + if (isset($config['skip']) && $config['skip']) { + $result = true; + } else { + foreach ($config as $lowerLevelConfig) { + if (is_array($lowerLevelConfig)) { + $result = $this->isSkippedByConfig($lowerLevelConfig); + if ($result === true) { + break; + } + } + } } - return $config; + return $result; } /** diff --git a/dev/tests/integration/framework/Magento/TestFramework/Workaround/Override/Config/Converter.php b/dev/tests/integration/framework/Magento/TestFramework/Workaround/Override/Config/Converter.php index 571bcdd7007bf..22d88279e8a9a 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Workaround/Override/Config/Converter.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Workaround/Override/Config/Converter.php @@ -18,7 +18,7 @@ */ class Converter implements ConverterInterface { - private const FIXTURE_TYPES = [ + protected const FIXTURE_TYPES = [ DataFixture::ANNOTATION, DataFixtureBeforeTransaction::ANNOTATION, ConfigFixture::ANNOTATION, @@ -46,7 +46,7 @@ public function convert($source) foreach ($this->xpath->query('./dataSet', $method) as $dataSet) { $setName = $dataSet->getAttribute('name'); - $config[$className][$methodName][$setName] = $config[$className][$methodName][$setName] ?? []; + $config[$className][$methodName][$setName] = $config[$className][$methodName][$setName] ?? []; $config[$className][$methodName][$setName] = $this->fillSkipSection( $dataSet, $config[$className][$methodName][$setName] @@ -81,7 +81,7 @@ private function fillSkipSection(\DOMElement $node, array $config): array */ private function getTestConfigByFixtureType(\DOMElement $node): array { - foreach (self::FIXTURE_TYPES as $fixtureType) { + foreach ($this::FIXTURE_TYPES as $fixtureType) { $currentTestNodePath = sprintf("//test[@class ='%s']/%s", $node->getAttribute('class'), $fixtureType); foreach ($this->xpath->query($currentTestNodePath) as $classDataFixture) { $config[$fixtureType][] = $this->fillAttributes($classDataFixture); @@ -111,7 +111,7 @@ private function getTestConfigByFixtureType(\DOMElement $node): array * @param \DOMElement $fixture * @return array */ - private function fillAttributes(\DOMElement $fixture): array + protected function fillAttributes(\DOMElement $fixture): array { $result = []; switch ($fixture->nodeName) { @@ -138,7 +138,7 @@ private function fillAttributes(\DOMElement $fixture): array * @param \DOMElement $fixture * @return array */ - private function fillDataFixtureAttributes(\DOMElement $fixture): array + protected function fillDataFixtureAttributes(\DOMElement $fixture): array { return [ 'path' => $fixture->getAttribute('path'), @@ -155,7 +155,7 @@ private function fillDataFixtureAttributes(\DOMElement $fixture): array * @param \DOMElement $fixture * @return array */ - private function fillConfigFixtureAttributes(\DOMElement $fixture): array + protected function fillConfigFixtureAttributes(\DOMElement $fixture): array { return [ 'path' => $fixture->getAttribute('path'), @@ -173,7 +173,7 @@ private function fillConfigFixtureAttributes(\DOMElement $fixture): array * @param \DOMElement $fixture * @return array */ - private function fillAdminConfigFixtureAttributes(\DOMElement $fixture): array + protected function fillAdminConfigFixtureAttributes(\DOMElement $fixture): array { return [ 'path' => $fixture->getAttribute('path'), diff --git a/dev/tests/integration/framework/Magento/TestFramework/Workaround/Override/ConfigInterface.php b/dev/tests/integration/framework/Magento/TestFramework/Workaround/Override/ConfigInterface.php new file mode 100644 index 0000000000000..dc5e885dcacc1 --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/Workaround/Override/ConfigInterface.php @@ -0,0 +1,71 @@ +getFixturePosition($attributes['before'], $fixtures); if ($attributes['before'] === '-' || $offset === 0) { $beforeFixtures[] = $attributes['path']; } else { @@ -115,7 +117,7 @@ private function sortFixtures(array $fixtures, array $attributes): array if ($attributes['after'] === '-') { $afterFixtures[] = $attributes['path']; } else { - $offset = array_search($attributes['after'], $fixtures); + $offset = $this->getFixturePosition($attributes['after'], $fixtures); $fixtures = $this->insertFixture($fixtures, $attributes['path'], $offset + 1); } } elseif (empty($attributes['before'])) { @@ -125,6 +127,27 @@ private function sortFixtures(array $fixtures, array $attributes): array return array_merge($beforeFixtures, $fixtures, $afterFixtures); } + /** + * Get fixture position in added fixtures list + * + * @param string $fixtureToFind + * @param array $existingFixtures + * @return int + * @throws LocalizedException if fixture which have to be found does not exist in added fixtures list + */ + private function getFixturePosition(string $fixtureToFind, array $existingFixtures): int + { + $offset = 0; + if ($fixtureToFind !== '-') { + $offset = array_search($fixtureToFind, $existingFixtures); + if ($offset === false) { + throw new LocalizedException(__('The fixture %1 does not exist in fixtures list', $fixtureToFind)); + } + } + + return $offset; + } + /** * Insert fixture into position * diff --git a/dev/tests/integration/framework/Magento/TestFramework/Workaround/Override/Fixture/Resolver.php b/dev/tests/integration/framework/Magento/TestFramework/Workaround/Override/Fixture/Resolver.php index 932870448f85b..351ecb60ae34d 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Workaround/Override/Fixture/Resolver.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Workaround/Override/Fixture/Resolver.php @@ -7,16 +7,16 @@ namespace Magento\TestFramework\Workaround\Override\Fixture; +use Magento\Framework\Component\ComponentRegistrar; use Magento\Framework\Component\ComponentRegistrarInterface; use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Component\ComponentRegistrar; use Magento\Framework\ObjectManagerInterface; use Magento\TestFramework\Annotation\AdminConfigFixture; use Magento\TestFramework\Annotation\ConfigFixture; use Magento\TestFramework\Annotation\DataFixture; use Magento\TestFramework\Annotation\DataFixtureBeforeTransaction; use Magento\TestFramework\Helper\Bootstrap; -use Magento\TestFramework\Workaround\Override\Config; +use Magento\TestFramework\Workaround\Override\ConfigInterface; use Magento\TestFramework\Workaround\Override\Fixture\Applier\AdminConfigFixture as AdminConfigFixtureApplier; use Magento\TestFramework\Workaround\Override\Fixture\Applier\ApplierInterface; use Magento\TestFramework\Workaround\Override\Fixture\Applier\Base; @@ -29,30 +29,30 @@ * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Resolver +class Resolver implements ResolverInterface { + /** @var ObjectManagerInterface */ + protected $objectManager; + /** @var self */ private static $instance; /** @var TestCase */ private $currentTest; - /** @var Config */ + /** @var ConfigInterface */ private $config; /** @var ApplierInterface[] */ private $appliersList; - /** @var ObjectManagerInterface */ - private $objectManager; - /** @var string */ private $currentFixtureType = null; /** - * @param Config $config + * @param ConfigInterface $config */ - public function __construct(Config $config) + public function __construct(ConfigInterface $config) { $this->config = $config; $this->objectManager = Bootstrap::getObjectManager(); @@ -61,32 +61,38 @@ public function __construct(Config $config) /** * Get class instance * - * @return self + * @return ResolverInterface */ - public static function getInstance(): self + public static function getInstance(): ResolverInterface { if (empty(self::$instance)) { - self::$instance = new self(Config::getInstance()); + throw new \RuntimeException('Override fixture resolver isn\'t initialized'); } return self::$instance; } /** - * Set current test to instance + * Instance setter. * - * @param TestCase $currentTest + * @param ResolverInterface $instance * @return void */ + public static function setInstance(ResolverInterface $instance): void + { + self::$instance = $instance; + } + + /** + * @inheritdoc + */ public function setCurrentTest(?TestCase $currentTest): void { $this->currentTest = $currentTest; } /** - * Get current test - * - * @return TestCase|null + * @inheritdoc */ public function getCurrentTest(): ?TestCase { @@ -94,10 +100,7 @@ public function getCurrentTest(): ?TestCase } /** - * Set which fixture type is executed - * - * @param null|string $fixtureType - * @return void + * @inheritdoc */ public function setCurrentFixtureType(?string $fixtureType): void { @@ -105,10 +108,7 @@ public function setCurrentFixtureType(?string $fixtureType): void } /** - * Require fixture wrapper - * - * @param string $path - * @return void + * @inheritdoc */ public function requireDataFixture(string $path): void { @@ -123,12 +123,7 @@ public function requireDataFixture(string $path): void } /** - * Apply override configurations to config fixtures list - * - * @param TestCase $test - * @param array $fixtures - * @param string $fixtureType - * @return array + * @inheritdoc */ public function applyConfigFixtures(TestCase $test, array $fixtures, string $fixtureType): array { @@ -136,12 +131,7 @@ public function applyConfigFixtures(TestCase $test, array $fixtures, string $fix } /** - * Apply override configurations to data fixtures list - * - * @param TestCase $test - * @param array $fixtures - * @param string $fixtureType - * @return array + * @inheritdoc */ public function applyDataFixtures(TestCase $test, array $fixtures, string $fixtureType): array { @@ -155,6 +145,32 @@ public function applyDataFixtures(TestCase $test, array $fixtures, string $fixtu return $result; } + /** + * Get appropriate fixture applier according to fixture type + * + * @param string $fixtureType + * @return ApplierInterface + */ + protected function getApplierByFixtureType(string $fixtureType): ApplierInterface + { + switch ($fixtureType) { + case DataFixture::ANNOTATION: + case DataFixtureBeforeTransaction::ANNOTATION: + $applier = $this->objectManager->get(DataFixtureApplier::class); + break; + case ConfigFixture::ANNOTATION: + $applier = $this->objectManager->get(ConfigFixtureApplier::class); + break; + case AdminConfigFixture::ANNOTATION: + $applier = $this->objectManager->get(AdminConfigFixtureApplier::class); + break; + default: + throw new \InvalidArgumentException(sprintf('Unsupported fixture type %s provided', $fixtureType)); + } + + return $applier; + } + /** * Get ComponentRegistrar object * @@ -190,32 +206,6 @@ private function getApplier(TestCase $test, string $fixtureType): ApplierInterfa return $applier; } - /** - * Get appropriate fixture applier according to fixture type - * - * @param string $fixtureType - * @return ApplierInterface - */ - private function getApplierByFixtureType(string $fixtureType): ApplierInterface - { - switch ($fixtureType) { - case DataFixture::ANNOTATION: - case DataFixtureBeforeTransaction::ANNOTATION: - $applier = $this->objectManager->get(DataFixtureApplier::class); - break; - case ConfigFixture::ANNOTATION: - $applier = $this->objectManager->get(ConfigFixtureApplier::class); - break; - case AdminConfigFixture::ANNOTATION: - $applier = $this->objectManager->get(AdminConfigFixtureApplier::class); - break; - default: - throw new \InvalidArgumentException(sprintf('Unsupported fixture type %s provided', $fixtureType)); - } - - return $applier; - } - /** * Converts fixture path. * diff --git a/dev/tests/integration/framework/Magento/TestFramework/Workaround/Override/Fixture/ResolverInterface.php b/dev/tests/integration/framework/Magento/TestFramework/Workaround/Override/Fixture/ResolverInterface.php new file mode 100644 index 0000000000000..3701ba033802f --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/Workaround/Override/Fixture/ResolverInterface.php @@ -0,0 +1,69 @@ +create( + Magento\TestFramework\Workaround\Override\Config::class + ); + $overrideConfig->init(); + Magento\TestFramework\Workaround\Override\Config::setInstance($overrideConfig); + Magento\TestFramework\Workaround\Override\Fixture\Resolver::setInstance( + new \Magento\TestFramework\Workaround\Override\Fixture\Resolver($overrideConfig) + ); /* Unset declared global variables to release the PHPUnit from maintaining their values between tests */ - unset($testsBaseDir, $logWriter, $settings, $shell, $application, $bootstrap); + unset($testsBaseDir, $settings, $shell, $application, $bootstrap, $overrideConfig); } catch (\Exception $e) { // phpcs:ignore Magento2.Security.LanguageConstruct.DirectOutput echo $e . PHP_EOL; diff --git a/dev/tests/integration/testsuite/Magento/TestModuleOverrideConfig/MagentoDataFixtureBeforeTransaction/RemoveFixtureTest.php b/dev/tests/integration/testsuite/Magento/TestModuleOverrideConfig/MagentoDataFixtureBeforeTransaction/RemoveFixtureTest.php index d9c97f5ab7a88..c8d83f4d4e382 100644 --- a/dev/tests/integration/testsuite/Magento/TestModuleOverrideConfig/MagentoDataFixtureBeforeTransaction/RemoveFixtureTest.php +++ b/dev/tests/integration/testsuite/Magento/TestModuleOverrideConfig/MagentoDataFixtureBeforeTransaction/RemoveFixtureTest.php @@ -39,6 +39,6 @@ protected function setUp(): void */ public function testRemoveFixture(): void { - $this->assertFalse($this->fixtureCallStorage->getFixturePosition('fixture1_first_module.php')); + $this->assertNull($this->fixtureCallStorage->getFixturePosition('fixture1_first_module.php')); } } From e4ea0d0cb520ba9f448f41f6998f97c450088e92 Mon Sep 17 00:00:00 2001 From: Vasya Tsviklinskyi Date: Wed, 3 Jun 2020 12:34:36 +0300 Subject: [PATCH 22/24] MC-34483: Moving a store view to a different website resets URLs --- .../CatalogUrlRewrite/Model/Category/Plugin/Store/View.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/View.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/View.php index 4beb63a3b2b56..31bb630718e55 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/View.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/View.php @@ -106,8 +106,8 @@ public function afterSave( ): Store { if ($this->origStore->isObjectNew() || $this->origStore->dataHasChangedFor('group_id')) { $categoryRewriteUrls = $this->generateCategoryUrls( - $this->origStore->getRootCategoryId(), - $this->origStore->getId() + (int)$this->origStore->getRootCategoryId(), + (int)$this->origStore->getId() ); $this->urlPersist->replace($categoryRewriteUrls); @@ -135,7 +135,7 @@ protected function generateProductUrls(int $storeId): array ->addAttributeToSelect(['name', 'url_path', 'url_key', 'visibility']) ->addStoreFilter($storeId); foreach ($collection as $product) { - /** @var \Magento\Catalog\Model\Product $product */ + /** @var Product $product */ $product->setStoreId($storeId); $urls[] = $this->productUrlRewriteGenerator->generate($product); } From 981a100e33c5ab096dbc3b06f638023db2e4ebc0 Mon Sep 17 00:00:00 2001 From: Vasya Tsviklinskyi Date: Wed, 3 Jun 2020 12:57:56 +0300 Subject: [PATCH 23/24] MC-34152: [Magento Cloud] Unable to cancel PayPal Payflow order / Declined: 10601-Authorization has expired. --- .../testsuite/Magento/Paypal/Model/PayflowproVoidTest.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/PayflowproVoidTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Model/PayflowproVoidTest.php index 3c7ae6c79132b..dc1c97e593fae 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/PayflowproVoidTest.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/PayflowproVoidTest.php @@ -31,11 +31,13 @@ use Magento\Sales\Model\Order\Item; use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class PayflowproVoidTest extends \PHPUnit\Framework\TestCase +class PayflowproVoidTest extends TestCase { /** * @var ObjectManagerInterface @@ -208,7 +210,7 @@ private function getPaymentMethodInstance(DataObject $response): PaymentMethodIn ] ); - /** @var Payflowpro|\PHPUnit_Framework_MockObject_MockObject $instance */ + /** @var Payflowpro|MockObject $instance */ $instance = $this->getMockBuilder(Payflowpro::class) ->setMethods(['setStore', 'getInfoInstance']) ->setConstructorArgs( From 686e7473cce8e9262950078f047259c6d588456f Mon Sep 17 00:00:00 2001 From: Lena Orobei Date: Thu, 4 Jun 2020 17:07:15 -0500 Subject: [PATCH 24/24] Issue templates for PAP projects --- .github/ISSUE_TEMPLATE/story.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/story.md diff --git a/.github/ISSUE_TEMPLATE/story.md b/.github/ISSUE_TEMPLATE/story.md new file mode 100644 index 0000000000000..f4ba43d4c4389 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/story.md @@ -0,0 +1,14 @@ +--- +name: GraphQL Story +about: User story for GraphQL project +labels: 'Project: GraphQL' + +--- + +*As a ___ I want to ___ so that ___.* + +### AC +* a +* b +### Approved Schema +* a