Skip to content

Commit

Permalink
MAGETWO-73528: Out of stock associated products to configurable are n…
Browse files Browse the repository at this point in the history
…ot full page cache cleaned #8009
  • Loading branch information
OlgaVasyltsun committed Nov 1, 2018
1 parent 38bc4bf commit 6940d20
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 6 deletions.
4 changes: 4 additions & 0 deletions app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
Original file line number Diff line number Diff line change
Expand Up @@ -263,4 +263,8 @@
<requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity>
<requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity>
</entity>
<entity name="ApiSimpleSingleQty" extends="ApiSimpleOne">
<data key="quantity">1</data>
<requiredEntity type="product_extension_attribute">EavStock1</requiredEntity>
</entity>
</entities>
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@
<entity name="EavStockItem" type="product_extension_attribute">
<requiredEntity type="stock_item">Qty_1000</requiredEntity>
</entity>
<entity name="EavStock1" type="product_extension_attribute">
<requiredEntity type="stock_item">Qty_1</requiredEntity>
</entity>
</entities>
4 changes: 4 additions & 0 deletions app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@
<data key="qty">1000</data>
<data key="is_in_stock">true</data>
</entity>
<entity name="Qty_1" type="stock_item">
<data key="qty">1</data>
<data key="is_in_stock">true</data>
</entity>
</entities>
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@

use Magento\CatalogInventory\Api\StockConfigurationInterface;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\Event\ManagerInterface;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Indexer\CacheContext;
use Magento\CatalogInventory\Model\Stock;
use Magento\Catalog\Model\Product;
Expand Down Expand Up @@ -46,25 +48,35 @@ class CacheCleaner
*/
private $connection;

/**
* @var MetadataPool
*/
private $metadataPool;

/**
* @param ResourceConnection $resource
* @param StockConfigurationInterface $stockConfiguration
* @param CacheContext $cacheContext
* @param ManagerInterface $eventManager
* @param MetadataPool|null $metadataPool
*/
public function __construct(
ResourceConnection $resource,
StockConfigurationInterface $stockConfiguration,
CacheContext $cacheContext,
ManagerInterface $eventManager
ManagerInterface $eventManager,
MetadataPool $metadataPool = null
) {
$this->resource = $resource;
$this->stockConfiguration = $stockConfiguration;
$this->cacheContext = $cacheContext;
$this->eventManager = $eventManager;
$this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(MetadataPool::class);
}

/**
* Clean cache by product ids.
*
* @param array $productIds
* @param callable $reindex
* @return void
Expand All @@ -76,22 +88,37 @@ public function clean(array $productIds, callable $reindex)
$productStatusesAfter = $this->getProductStockStatuses($productIds);
$productIds = $this->getProductIdsForCacheClean($productStatusesBefore, $productStatusesAfter);
if ($productIds) {
$this->cacheContext->registerEntities(Product::CACHE_TAG, $productIds);
$this->cacheContext->registerEntities(Product::CACHE_TAG, array_unique($productIds));
$this->eventManager->dispatch('clean_cache_by_tags', ['object' => $this->cacheContext]);
}
}

/**
* Get current stock statuses for product ids.
*
* @param array $productIds
* @return array
*/
private function getProductStockStatuses(array $productIds)
{
$linkField = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
->getLinkField();
$select = $this->getConnection()->select()
->from(
$this->resource->getTableName('cataloginventory_stock_status'),
['css' => $this->resource->getTableName('cataloginventory_stock_status')],
['product_id', 'stock_status', 'qty']
)->where('product_id IN (?)', $productIds)
)
->joinLeft(
['cpr' => $this->resource->getTableName('catalog_product_relation')],
'css.product_id = cpr.child_id',
[]
)
->joinLeft(
['cpe' => $this->resource->getTableName('catalog_product_entity')],
'cpr.parent_id = cpe.' . $linkField,
['parent_id' => 'cpe.entity_id']
)
->where('product_id IN (?)', $productIds)
->where('stock_id = ?', Stock::DEFAULT_STOCK_ID)
->where('website_id = ?', $this->stockConfiguration->getDefaultScopeId());

Expand Down Expand Up @@ -125,13 +152,18 @@ private function getProductIdsForCacheClean(array $productStatusesBefore, array
if ($statusBefore['stock_status'] !== $statusAfter['stock_status']
|| ($stockThresholdQty && $statusAfter['qty'] <= $stockThresholdQty)) {
$productIds[] = $productId;
if (isset($statusAfter['parent_id'])) {
$productIds[] = $statusAfter['parent_id'];
}
}
}

return $productIds;
}

/**
* Get database connection.
*
* @return AdapterInterface
*/
private function getConnection()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->

<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
<test name="AssociatedProductToConfigurableOutOfStockTest">
<annotations>
<features value="CatalogInventory"/>
<stories value="Add/remove images and videos for all product types and category"/>
<title value="Out of stock associated products to configurable are not full page cache cleaned "/>
<description value="After last configurable product was ordered it becomes out of stock"/>
<severity value="MAJOR"/>
<testCaseId value="MAGETWO-96031"/>
<group value="CatalogInventory"/>
</annotations>

<before>
<!--Create Configurable product-->
<actionGroup ref="AdminCreateConfigurableProductChildQty1ActionGroup" stepKey="createConfigurableProduct">
<argument name="productName" value="ApiConfigurableProduct"/>
</actionGroup>
<!-- Create customer -->
<createData entity="Simple_US_Customer" stepKey="createSimpleUsCustomer">
<field key="group_id">1</field>
</createData>
<!--Update index mode, reindex, flush cache-->
<magentoCLI command="indexer:set-mode schedule" stepKey="setScheduleIndexMode"/>
<magentoCLI command="indexer:reindex" stepKey="reindexBefore"/>
<magentoCLI command="cache:flush" stepKey="cacheFlushBefore"/>
</before>

<after>
<!--Delete configurable product-->
<deleteData createDataKey="createConfigProductCreateConfigurableProduct" stepKey="deleteConfigProduct"/>
<deleteData createDataKey="createConfigChildProduct1CreateConfigurableProduct" stepKey="deleteConfigChildProduct"/>
<deleteData createDataKey="createConfigChildProduct2CreateConfigurableProduct" stepKey="deleteConfigChildProduct1"/>
<deleteData createDataKey="createConfigProductAttributeCreateConfigurableProduct" stepKey="deleteConfigProductAttribute"/>
<!--Delete customer-->
<deleteData createDataKey="createSimpleUsCustomer" stepKey="deleteCustomer"/>
<!--Update index mode, reindex, flush cache-->
<magentoCLI command="indexer:set-mode realtime" stepKey="setRealTimeIndexMode"/>
<magentoCLI command="indexer:reindex" stepKey="reindexAfter"/>
<magentoCLI command="cache:flush" stepKey="cacheFlushAfter"/>
</after>

<!-- Login as a customer -->
<actionGroup ref="CustomerLoginOnStorefront" stepKey="signUpNewUser">
<argument name="customer" value="$$createSimpleUsCustomer$$"/>
</actionGroup>

<!-- Go to configurable product page -->
<amOnPage url="{{StorefrontProductPage.url('apiconfigurableproduct')}}" stepKey="goToConfigurableProductPage"/>

<!-- Order product with single quantity -->
<selectOption userInput="$$createConfigProductAttributeOption1CreateConfigurableProduct.option[store_labels][1][label]$$"
selector="{{StorefrontProductInfoMainSection.optionByAttributeId($$createConfigProductAttributeCreateConfigurableProduct.attribute_id$$)}}"
stepKey="configProductFillOption"
/>
<click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/>
<waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForProductAdded"/>
<actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/>
<actionGroup ref="CheckoutSelectFlatRateShippingMethodActionGroup" stepKey="selectShippingMehod"/>
<click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/>
<actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectPaymentMethod"/>
<actionGroup ref="CheckoutPlaceOrderActionGroup" stepKey="placeorder">
<argument name="orderNumberMessage" value="CONST.successCheckoutOrderNumberMessage" />
<argument name="emailYouMessage" value="CONST.successCheckoutEmailYouMessage" />
</actionGroup>

<!--Run cron to reindex products-->
<magentoCLI command="cron:run --group='index'" stepKey="runCron"/>
<magentoCLI command="cron:run --group='index'" stepKey="runCron1"/>

<!-- Go to configurable product page -->
<amOnPage url="{{StorefrontProductPage.url('apiconfigurableproduct')}}" stepKey="goToConfigurableProductPage1"/>

<!-- Assert that ordered product with single quantity is not available for order -->
<dontSee userInput="$$createConfigProductAttributeOption1CreateConfigurableProduct.option[store_labels][1][label]$$"
selector="{{StorefrontProductInfoMainSection.optionByAttributeId($$createConfigProductAttributeCreateConfigurableProduct.attribute_id$$)}}"
stepKey="assertOptionNotAvailable"
/>

<!-- Logout customer on Storefront-->
<actionGroup ref="CustomerLogoutStorefrontActionGroup" stepKey="customerLogoutStorefront"/>
</test>
</tests>
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\DB\Select;
use Magento\Framework\Event\ManagerInterface;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Indexer\CacheContext;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\Catalog\Model\Product;
Expand Down Expand Up @@ -43,6 +44,11 @@ class CacheCleanerTest extends \PHPUnit\Framework\TestCase
*/
private $cacheContextMock;

/**
* @var MetadataPool |\PHPUnit_Framework_MockObject_MockObject
*/
private $metadataPoolMock;

/**
* @var StockConfigurationInterface|\PHPUnit_Framework_MockObject_MockObject
*/
Expand All @@ -61,6 +67,8 @@ protected function setUp()
->setMethods(['getStockThresholdQty'])->getMockForAbstractClass();
$this->cacheContextMock = $this->getMockBuilder(CacheContext::class)->disableOriginalConstructor()->getMock();
$this->eventManagerMock = $this->getMockBuilder(ManagerInterface::class)->getMock();
$this->metadataPoolMock = $this->getMockBuilder(MetadataPool::class)
->setMethods(['getMetadata', 'getLinkField'])->disableOriginalConstructor()->getMock();
$this->selectMock = $this->getMockBuilder(Select::class)->disableOriginalConstructor()->getMock();

$this->resourceMock->expects($this->any())
Expand All @@ -73,7 +81,8 @@ protected function setUp()
'resource' => $this->resourceMock,
'stockConfiguration' => $this->stockConfigurationMock,
'cacheContext' => $this->cacheContextMock,
'eventManager' => $this->eventManagerMock
'eventManager' => $this->eventManagerMock,
'metadataPool' => $this->metadataPoolMock,
]
);
}
Expand All @@ -90,6 +99,7 @@ public function testClean($stockStatusBefore, $stockStatusAfter, $qtyAfter, $sto
$productId = 123;
$this->selectMock->expects($this->any())->method('from')->willReturnSelf();
$this->selectMock->expects($this->any())->method('where')->willReturnSelf();
$this->selectMock->expects($this->any())->method('joinLeft')->willReturnSelf();
$this->connectionMock->expects($this->exactly(2))->method('select')->willReturn($this->selectMock);
$this->connectionMock->expects($this->exactly(2))->method('fetchAll')->willReturnOnConsecutiveCalls(
[
Expand All @@ -105,7 +115,10 @@ public function testClean($stockStatusBefore, $stockStatusAfter, $qtyAfter, $sto
->with(Product::CACHE_TAG, [$productId]);
$this->eventManagerMock->expects($this->once())->method('dispatch')
->with('clean_cache_by_tags', ['object' => $this->cacheContextMock]);

$this->metadataPoolMock->expects($this->exactly(2))->method('getMetadata')
->willReturnSelf();
$this->metadataPoolMock->expects($this->exactly(2))->method('getLinkField')
->willReturn('row_id');
$callback = function () {
};
$this->unit->clean([], $callback);
Expand Down Expand Up @@ -136,6 +149,7 @@ public function testNotCleanCache($stockStatusBefore, $stockStatusAfter, $qtyAft
$productId = 123;
$this->selectMock->expects($this->any())->method('from')->willReturnSelf();
$this->selectMock->expects($this->any())->method('where')->willReturnSelf();
$this->selectMock->expects($this->any())->method('joinLeft')->willReturnSelf();
$this->connectionMock->expects($this->exactly(2))->method('select')->willReturn($this->selectMock);
$this->connectionMock->expects($this->exactly(2))->method('fetchAll')->willReturnOnConsecutiveCalls(
[
Expand All @@ -149,6 +163,10 @@ public function testNotCleanCache($stockStatusBefore, $stockStatusAfter, $qtyAft
->willReturn($stockThresholdQty);
$this->cacheContextMock->expects($this->never())->method('registerEntities');
$this->eventManagerMock->expects($this->never())->method('dispatch');
$this->metadataPoolMock->expects($this->exactly(2))->method('getMetadata')
->willReturnSelf();
$this->metadataPoolMock->expects($this->exactly(2))->method('getLinkField')
->willReturn('row_id');

$callback = function () {
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd">
<!-- Checkout select Check/Money Order payment -->
<actionGroup name="CheckoutSelectCheckMoneyOrderPaymentActionGroup">
<waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/>
<waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoaded"/>
<conditionalClick selector="{{CheckoutPaymentSection.checkMoneyOrderPayment}}" dependentSelector="{{CheckoutPaymentSection.checkMoneyOrderPayment}}" visible="true" stepKey="clickCheckMoneyOrderPayment"/>
</actionGroup>
Expand Down Expand Up @@ -66,4 +67,10 @@
<click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/>
<see selector="{{CheckoutSuccessMainSection.successTitle}}" userInput="Thank you for your purchase!" stepKey="waitForLoadSuccessPage"/>
</actionGroup>

<!-- Checkout select Flat Rate shipping method -->
<actionGroup name="CheckoutSelectFlatRateShippingMethodActionGroup">
<conditionalClick selector="{{CheckoutShippingMethodsSection.checkShippingMethodByName('Flat Rate')}}" dependentSelector="{{CheckoutShippingMethodsSection.checkShippingMethodByName('Flat Rate')}}" visible="true" stepKey="selectFlatRateShippingMethod"/>
<waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskForNextButton"/>
</actionGroup>
</actionGroups>
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,14 @@
<requiredEntity createDataKey="createConfigChildProduct2"/>
</createData>
</actionGroup>

<actionGroup name="AdminCreateConfigurableProductChildQty1ActionGroup" extends="AdminCreateApiConfigurableProductActionGroup">
<createData entity="ApiConfigurableProductWithOutCategory" stepKey="createConfigProduct">
<field key="name">{{productName}}</field>
</createData>
<createData entity="ApiSimpleSingleQty" stepKey="createConfigChildProduct1">
<requiredEntity createDataKey="createConfigProductAttribute"/>
<requiredEntity createDataKey="getConfigAttributeOption1"/>
</createData>
</actionGroup>
</actionGroups>
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
<element name="productAttributeOptions" type="select" selector="#product-options-wrapper div[tabindex='0'] option"/>
<element name="stockIndication" type="block" selector=".stock" />
<element name="productAttributeOptionsSelectButton" type="select" selector="#product-options-wrapper .super-attribute-select"/>
<element name="optionByAttributeId" type="input" selector="#attribute{{var1}}" parameterized="true"/>
</section>
</sections>

0 comments on commit 6940d20

Please sign in to comment.