Skip to content
This repository was archived by the owner on Apr 29, 2019. It is now read-only.

Commit 9678b65

Browse files
committed
Merge branch 'EPAM-PR-15' of github.com:magento-epam/magento2ce into EPAM-PR-11-14-15
2 parents 016ed5e + 7d3f513 commit 9678b65

File tree

6 files changed

+214
-6
lines changed

6 files changed

+214
-6
lines changed

app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,4 +419,17 @@
419419
<data key="status">1</data>
420420
<requiredEntity type="product_extension_attribute">EavStock100</requiredEntity>
421421
</entity>
422+
<entity name="ApiSimpleSingleQty" type="product2">
423+
<data key="sku" unique="suffix">api-simple-product</data>
424+
<data key="type_id">simple</data>
425+
<data key="attribute_set_id">4</data>
426+
<data key="visibility">4</data>
427+
<data key="name" unique="suffix">Api Simple Product</data>
428+
<data key="price">123.00</data>
429+
<data key="urlKey" unique="suffix">api-simple-product</data>
430+
<data key="status">1</data>
431+
<data key="quantity">1</data>
432+
<requiredEntity type="product_extension_attribute">EavStock1</requiredEntity>
433+
<requiredEntity type="custom_attribute">CustomAttributeProductAttribute</requiredEntity>
434+
</entity>
422435
</entities>

app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,7 @@
1717
<entity name="EavStock10" type="product_extension_attribute">
1818
<requiredEntity type="stock_item">Qty_10</requiredEntity>
1919
</entity>
20+
<entity name="EavStock1" type="product_extension_attribute">
21+
<requiredEntity type="stock_item">Qty_1</requiredEntity>
22+
</entity>
2023
</entities>

app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,8 @@
2828
<data key="qty">101</data>
2929
<data key="is_in_stock">true</data>
3030
</entity>
31+
<entity name="Qty_1" type="stock_item">
32+
<data key="qty">1</data>
33+
<data key="is_in_stock">true</data>
34+
</entity>
3135
</entities>

app/code/Magento/CatalogInventory/Model/Indexer/Stock/CacheCleaner.php

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010

1111
use Magento\CatalogInventory\Api\StockConfigurationInterface;
1212
use Magento\Framework\App\ResourceConnection;
13+
use Magento\Framework\App\ObjectManager;
1314
use Magento\Framework\DB\Adapter\AdapterInterface;
1415
use Magento\Framework\Event\ManagerInterface;
16+
use Magento\Framework\EntityManager\MetadataPool;
1517
use Magento\Framework\Indexer\CacheContext;
1618
use Magento\CatalogInventory\Model\Stock;
1719
use Magento\Catalog\Model\Product;
@@ -46,25 +48,35 @@ class CacheCleaner
4648
*/
4749
private $connection;
4850

51+
/**
52+
* @var MetadataPool
53+
*/
54+
private $metadataPool;
55+
4956
/**
5057
* @param ResourceConnection $resource
5158
* @param StockConfigurationInterface $stockConfiguration
5259
* @param CacheContext $cacheContext
5360
* @param ManagerInterface $eventManager
61+
* @param MetadataPool|null $metadataPool
5462
*/
5563
public function __construct(
5664
ResourceConnection $resource,
5765
StockConfigurationInterface $stockConfiguration,
5866
CacheContext $cacheContext,
59-
ManagerInterface $eventManager
67+
ManagerInterface $eventManager,
68+
MetadataPool $metadataPool = null
6069
) {
6170
$this->resource = $resource;
6271
$this->stockConfiguration = $stockConfiguration;
6372
$this->cacheContext = $cacheContext;
6473
$this->eventManager = $eventManager;
74+
$this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(MetadataPool::class);
6575
}
6676

6777
/**
78+
* Clean cache by product ids.
79+
*
6880
* @param array $productIds
6981
* @param callable $reindex
7082
* @return void
@@ -76,22 +88,37 @@ public function clean(array $productIds, callable $reindex)
7688
$productStatusesAfter = $this->getProductStockStatuses($productIds);
7789
$productIds = $this->getProductIdsForCacheClean($productStatusesBefore, $productStatusesAfter);
7890
if ($productIds) {
79-
$this->cacheContext->registerEntities(Product::CACHE_TAG, $productIds);
91+
$this->cacheContext->registerEntities(Product::CACHE_TAG, array_unique($productIds));
8092
$this->eventManager->dispatch('clean_cache_by_tags', ['object' => $this->cacheContext]);
8193
}
8294
}
8395

8496
/**
97+
* Get current stock statuses for product ids.
98+
*
8599
* @param array $productIds
86100
* @return array
87101
*/
88102
private function getProductStockStatuses(array $productIds)
89103
{
104+
$linkField = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
105+
->getLinkField();
90106
$select = $this->getConnection()->select()
91107
->from(
92-
$this->resource->getTableName('cataloginventory_stock_status'),
108+
['css' => $this->resource->getTableName('cataloginventory_stock_status')],
93109
['product_id', 'stock_status', 'qty']
94-
)->where('product_id IN (?)', $productIds)
110+
)
111+
->joinLeft(
112+
['cpr' => $this->resource->getTableName('catalog_product_relation')],
113+
'css.product_id = cpr.child_id',
114+
[]
115+
)
116+
->joinLeft(
117+
['cpe' => $this->resource->getTableName('catalog_product_entity')],
118+
'cpr.parent_id = cpe.' . $linkField,
119+
['parent_id' => 'cpe.entity_id']
120+
)
121+
->where('product_id IN (?)', $productIds)
95122
->where('stock_id = ?', Stock::DEFAULT_STOCK_ID)
96123
->where('website_id = ?', $this->stockConfiguration->getDefaultScopeId());
97124

@@ -125,13 +152,18 @@ private function getProductIdsForCacheClean(array $productStatusesBefore, array
125152
if ($statusBefore['stock_status'] !== $statusAfter['stock_status']
126153
|| ($stockThresholdQty && $statusAfter['qty'] <= $stockThresholdQty)) {
127154
$productIds[] = $productId;
155+
if (isset($statusAfter['parent_id'])) {
156+
$productIds[] = $statusAfter['parent_id'];
157+
}
128158
}
129159
}
130160

131161
return $productIds;
132162
}
133163

134164
/**
165+
* Get database connection.
166+
*
135167
* @return AdapterInterface
136168
*/
137169
private function getConnection()
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
9+
<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
11+
<test name="AssociatedProductToConfigurableOutOfStockTest">
12+
<annotations>
13+
<features value="CatalogInventory"/>
14+
<stories value="Add/remove images and videos for all product types and category"/>
15+
<title value="Out of stock associated products to configurable are not full page cache cleaned "/>
16+
<description value="After last configurable product was ordered it becomes out of stock"/>
17+
<severity value="MAJOR"/>
18+
<testCaseId value="MAGETWO-94135"/>
19+
<group value="CatalogInventory"/>
20+
</annotations>
21+
22+
<before>
23+
<createData entity="SimpleSubCategory" stepKey="simplecategory"/>
24+
25+
<!-- Create configurable product with two options -->
26+
<createData entity="ApiConfigurableProduct" stepKey="createConfigProduct">
27+
<requiredEntity createDataKey="simplecategory"/>
28+
</createData>
29+
30+
<createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/>
31+
<createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1">
32+
<requiredEntity createDataKey="createConfigProductAttribute"/>
33+
</createData>
34+
<createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2">
35+
<requiredEntity createDataKey="createConfigProductAttribute"/>
36+
</createData>
37+
38+
<createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet">
39+
<requiredEntity createDataKey="createConfigProductAttribute"/>
40+
</createData>
41+
42+
<getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1">
43+
<requiredEntity createDataKey="createConfigProductAttribute"/>
44+
</getData>
45+
<getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2">
46+
<requiredEntity createDataKey="createConfigProductAttribute"/>
47+
</getData>
48+
49+
<!-- Create child product with single quantity -->
50+
<createData entity="ApiSimpleSingleQty" stepKey="createConfigChildProduct1">
51+
<requiredEntity createDataKey="createConfigProductAttribute"/>
52+
<requiredEntity createDataKey="getConfigAttributeOption1"/>
53+
</createData>
54+
55+
<createData entity="ApiSimpleOne" stepKey="createConfigChildProduct2">
56+
<requiredEntity createDataKey="createConfigProductAttribute"/>
57+
<requiredEntity createDataKey="getConfigAttributeOption2"/>
58+
</createData>
59+
60+
<createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption">
61+
<requiredEntity createDataKey="createConfigProduct"/>
62+
<requiredEntity createDataKey="createConfigProductAttribute"/>
63+
<requiredEntity createDataKey="getConfigAttributeOption1"/>
64+
<requiredEntity createDataKey="getConfigAttributeOption2"/>
65+
</createData>
66+
67+
<createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1">
68+
<requiredEntity createDataKey="createConfigProduct"/>
69+
<requiredEntity createDataKey="createConfigChildProduct1"/>
70+
</createData>
71+
<createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2">
72+
<requiredEntity createDataKey="createConfigProduct"/>
73+
<requiredEntity createDataKey="createConfigChildProduct2"/>
74+
</createData>
75+
76+
<!-- Create customer -->
77+
<createData entity="Simple_US_Customer" stepKey="createSimpleUsCustomer">
78+
<field key="group_id">1</field>
79+
</createData>
80+
</before>
81+
82+
<after>
83+
<deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/>
84+
<deleteData createDataKey="simplecategory" stepKey="deleteSimpleCategory"/>
85+
<deleteData createDataKey="createSimpleUsCustomer" stepKey="deleteCustomer"/>
86+
<deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigChildProduct1"/>
87+
<deleteData createDataKey="createConfigChildProduct2" stepKey="deleteConfigChildProduct2"/>
88+
<deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/>
89+
</after>
90+
91+
<!-- Login as a customer -->
92+
<actionGroup ref="LoginToStorefrontActionGroup" stepKey="signUpNewUser">
93+
<argument name="Customer" value="$$createSimpleUsCustomer$$"/>
94+
</actionGroup>
95+
<amOnPage url="/{{ApiConfigurableProduct.urlKey}}2.html" stepKey="goToConfigProductPage"/>
96+
<!-- Go to configurable product page -->
97+
<waitForPageLoad stepKey="waitForProductPageLoad"/>
98+
99+
<!-- Order product with single quantity -->
100+
<selectOption userInput="$$createConfigProductAttributeOption1.option[store_labels][1][label]$$" selector="{{StorefrontProductInfoMainSection.optionByAttributeId($$createConfigProductAttribute.attribute_id$$)}}" stepKey="configProductFillOption" />
101+
<click stepKey="addSimpleProductToCart" selector="{{StorefrontProductActionSection.addToCart}}"/>
102+
<waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForProductAdded"/>
103+
<amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToShoppingCartPage"/>
104+
<actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/>
105+
<waitForElement selector="{{CheckoutShippingMethodsSection.next}}" time="30" stepKey="waitForNextButton"/>
106+
<click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/>
107+
<click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/>
108+
<waitForPageLoad stepKey="waitForOrderSuccessPage1"/>
109+
<grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/>
110+
<actionGroup ref="StorefrontSignOutActionGroup" stepKey="StorefrontSignOutActionGroup"/>
111+
<actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/>
112+
113+
<amOnPage url="{{AdminOrdersPage.url}}" stepKey="onOrdersPage"/>
114+
<waitForLoadingMaskToDisappear stepKey="waitForLoadingMask3"/>
115+
<fillField selector="{{AdminOrdersGridSection.search}}" userInput="{$grabOrderNumber}" stepKey="searchOrderNum"/>
116+
<click selector="{{AdminOrdersGridSection.submitSearch}}" stepKey="submitSearch"/>
117+
<waitForLoadingMaskToDisappear stepKey="waitForLoadingMask4"/>
118+
119+
<click selector="{{AdminOrdersGridSection.firstRow}}" stepKey="clickOrderRow"/>
120+
<click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoice"/>
121+
<click selector="{{AdminInvoiceMainActionsSection.submitInvoice}}" stepKey="clickSubmitInvoice"/>
122+
<see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="The invoice has been created." stepKey="seeSuccessMessage"/>
123+
<click selector="{{AdminOrderDetailsMainActionsSection.ship}}" stepKey="clickShip"/>
124+
<waitForLoadingMaskToDisappear stepKey="waitForShipLoadingMask"/>
125+
<click selector="{{AdminShipmentMainActionsSection.submitShipment}}" stepKey="submitShipment"/>
126+
<waitForPageLoad stepKey="waitShipmentCreated"/>
127+
<actionGroup ref="logout" stepKey="logout"/>
128+
<magentoCLI stepKey="runCron" command="cron:run --group='index'"/>
129+
130+
<!-- Wait till cron job runs for schedule updates -->
131+
<wait time="60" stepKey="waitForUpdateStarts"/>
132+
133+
<!-- Assert that product with single quantity is not available for order -->
134+
<amOnPage url="/{{ApiConfigurableProduct.urlKey}}2.html" stepKey="goToConfigProductPage2"/>
135+
<waitForPageLoad stepKey="waitForProductPageLoad2"/>
136+
<dontSee userInput="$$createConfigProductAttributeOption1.option[store_labels][1][label]$$" selector="{{StorefrontProductInfoMainSection.optionByAttributeId($$createConfigProductAttribute.attribute_id$$)}}" stepKey="assertOptionNotAvailable" />
137+
</test>
138+
</tests>

app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/CacheCleanerTest.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Magento\Framework\DB\Adapter\AdapterInterface;
1313
use Magento\Framework\DB\Select;
1414
use Magento\Framework\Event\ManagerInterface;
15+
use Magento\Framework\EntityManager\MetadataPool;
1516
use Magento\Framework\Indexer\CacheContext;
1617
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
1718
use Magento\Catalog\Model\Product;
@@ -43,6 +44,11 @@ class CacheCleanerTest extends \PHPUnit\Framework\TestCase
4344
*/
4445
private $cacheContextMock;
4546

47+
/**
48+
* @var MetadataPool |\PHPUnit_Framework_MockObject_MockObject
49+
*/
50+
private $metadataPoolMock;
51+
4652
/**
4753
* @var StockConfigurationInterface|\PHPUnit_Framework_MockObject_MockObject
4854
*/
@@ -61,6 +67,8 @@ protected function setUp()
6167
->setMethods(['getStockThresholdQty'])->getMockForAbstractClass();
6268
$this->cacheContextMock = $this->getMockBuilder(CacheContext::class)->disableOriginalConstructor()->getMock();
6369
$this->eventManagerMock = $this->getMockBuilder(ManagerInterface::class)->getMock();
70+
$this->metadataPoolMock = $this->getMockBuilder(MetadataPool::class)
71+
->setMethods(['getMetadata', 'getLinkField'])->disableOriginalConstructor()->getMock();
6472
$this->selectMock = $this->getMockBuilder(Select::class)->disableOriginalConstructor()->getMock();
6573

6674
$this->resourceMock->expects($this->any())
@@ -73,7 +81,8 @@ protected function setUp()
7381
'resource' => $this->resourceMock,
7482
'stockConfiguration' => $this->stockConfigurationMock,
7583
'cacheContext' => $this->cacheContextMock,
76-
'eventManager' => $this->eventManagerMock
84+
'eventManager' => $this->eventManagerMock,
85+
'metadataPool' => $this->metadataPoolMock
7786
]
7887
);
7988
}
@@ -90,6 +99,7 @@ public function testClean($stockStatusBefore, $stockStatusAfter, $qtyAfter, $sto
9099
$productId = 123;
91100
$this->selectMock->expects($this->any())->method('from')->willReturnSelf();
92101
$this->selectMock->expects($this->any())->method('where')->willReturnSelf();
102+
$this->selectMock->expects($this->any())->method('joinLeft')->willReturnSelf();
93103
$this->connectionMock->expects($this->exactly(2))->method('select')->willReturn($this->selectMock);
94104
$this->connectionMock->expects($this->exactly(2))->method('fetchAll')->willReturnOnConsecutiveCalls(
95105
[
@@ -105,7 +115,10 @@ public function testClean($stockStatusBefore, $stockStatusAfter, $qtyAfter, $sto
105115
->with(Product::CACHE_TAG, [$productId]);
106116
$this->eventManagerMock->expects($this->once())->method('dispatch')
107117
->with('clean_cache_by_tags', ['object' => $this->cacheContextMock]);
108-
118+
$this->metadataPoolMock->expects($this->exactly(2))->method('getMetadata')
119+
->willReturnSelf();
120+
$this->metadataPoolMock->expects($this->exactly(2))->method('getLinkField')
121+
->willReturn('row_id');
109122
$callback = function () {
110123
};
111124
$this->unit->clean([], $callback);
@@ -136,6 +149,7 @@ public function testNotCleanCache($stockStatusBefore, $stockStatusAfter, $qtyAft
136149
$productId = 123;
137150
$this->selectMock->expects($this->any())->method('from')->willReturnSelf();
138151
$this->selectMock->expects($this->any())->method('where')->willReturnSelf();
152+
$this->selectMock->expects($this->any())->method('joinLeft')->willReturnSelf();
139153
$this->connectionMock->expects($this->exactly(2))->method('select')->willReturn($this->selectMock);
140154
$this->connectionMock->expects($this->exactly(2))->method('fetchAll')->willReturnOnConsecutiveCalls(
141155
[
@@ -149,6 +163,10 @@ public function testNotCleanCache($stockStatusBefore, $stockStatusAfter, $qtyAft
149163
->willReturn($stockThresholdQty);
150164
$this->cacheContextMock->expects($this->never())->method('registerEntities');
151165
$this->eventManagerMock->expects($this->never())->method('dispatch');
166+
$this->metadataPoolMock->expects($this->exactly(2))->method('getMetadata')
167+
->willReturnSelf();
168+
$this->metadataPoolMock->expects($this->exactly(2))->method('getLinkField')
169+
->willReturn('row_id');
152170

153171
$callback = function () {
154172
};

0 commit comments

Comments
 (0)