Skip to content

Commit 7d44656

Browse files
committed
#12970: Can't add grouped product, with out of stock sub product, to cart.
1 parent 79d8e9a commit 7d44656

File tree

6 files changed

+331
-16
lines changed

6 files changed

+331
-16
lines changed

app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ protected function getProductInfo(\Magento\Framework\DataObject $buyRequest, $pr
341341
if ($isStrictProcessMode && !$subProduct->getQty()) {
342342
return __('Please specify the quantity of product(s).')->render();
343343
}
344-
$productsInfo[$subProduct->getId()] = intval($subProduct->getQty());
344+
$productsInfo[$subProduct->getId()] = $subProduct->isSalable() ? intval($subProduct->getQty()) : 0;
345345
}
346346
}
347347

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
/** @var $product \Magento\Catalog\Model\Product */
8+
$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class);
9+
$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL)
10+
->setId(31)
11+
->setAttributeSetId(4)
12+
->setWebsiteIds([1])
13+
->setName('Virtual Product Out')
14+
->setSku('virtual-product-out')
15+
->setPrice(10)
16+
->setTaxClassId(0)
17+
->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH)
18+
->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED)
19+
->setStockData(['is_in_stock' => 0])
20+
->save();
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
/** @var \Magento\Framework\Registry $registry */
8+
$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
9+
->get(\Magento\Framework\Registry::class);
10+
11+
$registry->unregister('isSecureArea');
12+
$registry->register('isSecureArea', true);
13+
14+
/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */
15+
$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
16+
->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
17+
18+
try {
19+
$product = $productRepository->get('virtual-product-out', false, null, true);
20+
$productRepository->delete($product);
21+
} catch (\Magento\Framework\Exception\NoSuchEntityException $exception) {
22+
//Product already removed
23+
}
24+
25+
$registry->unregister('isSecureArea');
26+
$registry->register('isSecureArea', false);

dev/tests/integration/testsuite/Magento/GroupedProduct/Model/Product/Type/GroupedTest.php

Lines changed: 199 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,19 @@
55
*/
66
namespace Magento\GroupedProduct\Model\Product\Type;
77

8+
use Magento\Catalog\Api\ProductRepositoryInterface;
9+
use Magento\Catalog\Model\Product;
10+
use Magento\CatalogInventory\Model\Configuration;
11+
use Magento\Framework\App\Config\ReinitableConfigInterface;
12+
use Magento\Framework\App\Config\Value;
13+
814
class GroupedTest extends \PHPUnit\Framework\TestCase
915
{
16+
/**
17+
* @var ReinitableConfigInterface
18+
*/
19+
private $reinitableConfig;
20+
1021
/**
1122
* @var \Magento\Framework\ObjectManagerInterface
1223
*/
@@ -20,16 +31,21 @@ class GroupedTest extends \PHPUnit\Framework\TestCase
2031
protected function setUp()
2132
{
2233
$this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
23-
2434
$this->_productType = $this->objectManager->get(\Magento\Catalog\Model\Product\Type::class);
35+
$this->reinitableConfig = $this->objectManager->get(ReinitableConfigInterface::class);
36+
}
37+
38+
protected function tearDown()
39+
{
40+
$this->dropConfigValue(Configuration::XML_PATH_SHOW_OUT_OF_STOCK);
2541
}
2642

2743
public function testFactory()
2844
{
2945
$product = new \Magento\Framework\DataObject();
30-
$product->setTypeId(\Magento\GroupedProduct\Model\Product\Type\Grouped::TYPE_CODE);
46+
$product->setTypeId(Grouped::TYPE_CODE);
3147
$type = $this->_productType->factory($product);
32-
$this->assertInstanceOf(\Magento\GroupedProduct\Model\Product\Type\Grouped::class, $type);
48+
$this->assertInstanceOf(Grouped::class, $type);
3349
}
3450

3551
/**
@@ -38,12 +54,12 @@ public function testFactory()
3854
*/
3955
public function testGetAssociatedProducts()
4056
{
41-
$productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
57+
$productRepository = $this->objectManager->create(ProductRepositoryInterface::class);
4258

43-
/** @var \Magento\Catalog\Model\Product $product */
59+
/** @var Product $product */
4460
$product = $productRepository->get('grouped-product');
4561
$type = $product->getTypeInstance();
46-
$this->assertInstanceOf(\Magento\GroupedProduct\Model\Product\Type\Grouped::class, $type);
62+
$this->assertInstanceOf(Grouped::class, $type);
4763

4864
$associatedProducts = $type->getAssociatedProducts($product);
4965
$this->assertCount(2, $associatedProducts);
@@ -53,7 +69,7 @@ public function testGetAssociatedProducts()
5369
}
5470

5571
/**
56-
* @param \Magento\Catalog\Model\Product $product
72+
* @param Product $product
5773
*/
5874
private function assertProductInfo($product)
5975
{
@@ -92,25 +108,25 @@ public function testPrepareProduct()
92108
\Magento\Framework\DataObject::class,
93109
['data' => ['value' => ['qty' => 2]]]
94110
);
95-
/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */
96-
$productRepository = $this->objectManager->get(\Magento\Catalog\Api\ProductRepositoryInterface::class);
111+
/** @var ProductRepositoryInterface $productRepository */
112+
$productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
97113
$product = $productRepository->get('grouped-product');
98114

99-
/** @var \Magento\GroupedProduct\Model\Product\Type\Grouped $type */
100-
$type = $this->objectManager->get(\Magento\GroupedProduct\Model\Product\Type\Grouped::class);
115+
/** @var Grouped $type */
116+
$type = $this->objectManager->get(Grouped::class);
101117

102118
$processModes = [
103-
\Magento\GroupedProduct\Model\Product\Type\Grouped::PROCESS_MODE_FULL,
104-
\Magento\GroupedProduct\Model\Product\Type\Grouped::PROCESS_MODE_LITE
119+
Grouped::PROCESS_MODE_FULL,
120+
Grouped::PROCESS_MODE_LITE
105121
];
106122
$expectedData = [
107-
\Magento\GroupedProduct\Model\Product\Type\Grouped::PROCESS_MODE_FULL => [
123+
Grouped::PROCESS_MODE_FULL => [
108124
1 => '{"super_product_config":{"product_type":"grouped","product_id":"'
109125
. $product->getId() . '"}}',
110126
21 => '{"super_product_config":{"product_type":"grouped","product_id":"'
111127
. $product->getId() . '"}}',
112128
],
113-
\Magento\GroupedProduct\Model\Product\Type\Grouped::PROCESS_MODE_LITE => [
129+
Grouped::PROCESS_MODE_LITE => [
114130
$product->getId() => '{"value":{"qty":2}}',
115131
]
116132
];
@@ -127,4 +143,172 @@ public function testPrepareProduct()
127143
}
128144
}
129145
}
146+
147+
/**
148+
* Test adding grouped product to cart then one of subproducts is out of stock.
149+
*
150+
* @magentoDataFixture Magento/GroupedProduct/_files/product_grouped_with_out_of_stock.php
151+
* @magentoAppArea frontend
152+
* @magentoAppIsolation enabled
153+
* @magentoDbIsolation disabled
154+
* @dataProvider outOfStockSubProductDataProvider
155+
* @param bool $outOfStockShown
156+
* @param array $data
157+
* @param array $expected
158+
*/
159+
public function testOutOfStockSubProduct(bool $outOfStockShown, array $data, array $expected)
160+
{
161+
$this->changeConfigValue(Configuration::XML_PATH_SHOW_OUT_OF_STOCK, $outOfStockShown);
162+
$buyRequest = new \Magento\Framework\DataObject($data);
163+
$productRepository = $this->objectManager->create(ProductRepositoryInterface::class);
164+
/** @var Product $product */
165+
$product = $productRepository->get('grouped-product');
166+
/** @var Grouped $groupedProduct */
167+
$groupedProduct = $this->objectManager->get(Grouped::class);
168+
$actual = $groupedProduct->prepareForCartAdvanced($buyRequest, $product, Grouped::PROCESS_MODE_FULL);
169+
self::assertEquals(
170+
count($expected),
171+
count($actual)
172+
);
173+
/** @var array $element */
174+
foreach ($expected as $number => $element) {
175+
foreach ($element as $key => $value) {
176+
self::assertEquals(
177+
$value,
178+
$actual[$number]->getData($key),
179+
"Failed asserting that value $key matches expected"
180+
);
181+
}
182+
}
183+
184+
}
185+
186+
/**
187+
* Data provider for testOutOfStockSubProduct.
188+
*
189+
* @return array
190+
*/
191+
public function outOfStockSubProductDataProvider()
192+
{
193+
return [
194+
'Out of stock product are shown #1' => [
195+
true,
196+
[
197+
'product' => 3,
198+
'qty' => 1,
199+
'super_group' => [
200+
1 => 4,
201+
21 => 5
202+
],
203+
],
204+
[
205+
[
206+
'sku' => 'virtual-product',
207+
'cart_qty' => 5
208+
],
209+
[
210+
'sku' => 'simple',
211+
'cart_qty' => 4
212+
],
213+
]
214+
],
215+
'Out of stock product are shown #2' => [
216+
true,
217+
[
218+
'product' => 3,
219+
'qty' => 1,
220+
'super_group' => [
221+
1 => 0,
222+
],
223+
],
224+
[
225+
[
226+
'sku' => 'virtual-product',
227+
'cart_qty' => 2 // This is a default quantity.
228+
],
229+
]
230+
],
231+
'Out of stock product are hidden #1' => [
232+
false,
233+
[
234+
'product' => 3,
235+
'qty' => 1,
236+
'super_group' => [
237+
1 => 4,
238+
21 => 5
239+
],
240+
],
241+
[
242+
[
243+
'sku' => 'virtual-product',
244+
'cart_qty' => 5
245+
],
246+
[
247+
'sku' => 'simple',
248+
'cart_qty' => 4
249+
],
250+
]
251+
],
252+
'Out of stock product are hidden #2' => [
253+
false,
254+
[
255+
'product' => 3,
256+
'qty' => 1,
257+
'super_group' => [
258+
1 => 0,
259+
],
260+
],
261+
[
262+
[
263+
'sku' => 'virtual-product',
264+
'cart_qty' => 2 // This is a default quantity.
265+
],
266+
]
267+
],
268+
];
269+
}
270+
271+
/**
272+
* Write config value to database.
273+
*
274+
* @param string $path
275+
* @param string $value
276+
* @param string $scope
277+
* @param int $scopeId
278+
*/
279+
private function changeConfigValue(string $path, string $value, string $scope = 'default', int $scopeId = 0)
280+
{
281+
$configValue = $this->objectManager->create(Value::class);
282+
$configValue->setPath($path)
283+
->setValue($value)
284+
->setScope($scope)
285+
->setScopeId($scopeId)
286+
->save();
287+
$this->reinitConfig();
288+
}
289+
290+
/**
291+
* Delete config value from database.
292+
*
293+
* @param string $path
294+
*/
295+
private function dropConfigValue(string $path)
296+
{
297+
$configValue = $this->objectManager->create(Value::class);
298+
try {
299+
$configValue->load($path, 'path');
300+
$configValue->delete();
301+
} catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
302+
// do nothing
303+
}
304+
$this->reinitConfig();
305+
}
306+
307+
/**
308+
* Reinit config.
309+
*/
310+
private function reinitConfig()
311+
{
312+
$this->reinitableConfig->reinit();
313+
}
130314
}

0 commit comments

Comments
 (0)