Skip to content

Commit 346678c

Browse files
MC-30313: Out of stock child of grouped product prevents other children to be added to cart
1 parent 3e4db87 commit 346678c

File tree

2 files changed

+147
-53
lines changed

2 files changed

+147
-53
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
@@ -344,7 +344,7 @@ protected function getProductInfo(\Magento\Framework\DataObject $buyRequest, $pr
344344
}
345345
foreach ($associatedProducts as $subProduct) {
346346
if (!isset($productsInfo[$subProduct->getId()])) {
347-
if ($isStrictProcessMode && !$subProduct->getQty()) {
347+
if ($isStrictProcessMode && !$subProduct->getQty() && $subProduct->isSalable()) {
348348
return __('Please specify the quantity of product(s).')->render();
349349
}
350350
$productsInfo[$subProduct->getId()] = $subProduct->isSalable() ? (float)$subProduct->getQty() : 0;

app/code/Magento/GroupedProduct/Test/Unit/Model/Product/Type/GroupedTest.php

Lines changed: 146 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,7 @@ protected function setUp()
5454
$productFactoryMock = $this->createMock(\Magento\Catalog\Model\ProductFactory::class);
5555
$this->catalogProductLink = $this->createMock(\Magento\GroupedProduct\Model\ResourceModel\Product\Link::class);
5656
$this->productStatusMock = $this->createMock(\Magento\Catalog\Model\Product\Attribute\Source\Status::class);
57-
$this->serializer = $this->getMockBuilder(\Magento\Framework\Serialize\Serializer\Json::class)
58-
->setMethods(['serialize'])
59-
->getMockForAbstractClass();
57+
$this->serializer = $this->objectHelper->getObject(\Magento\Framework\Serialize\Serializer\Json::class);
6058

6159
$this->_model = $this->objectHelper->getObject(
6260
\Magento\GroupedProduct\Model\Product\Type\Grouped::class,
@@ -419,9 +417,6 @@ public function testPrepareForCartAdvancedNoProductsStrictFalse()
419417
->expects($this->atLeastOnce())
420418
->method('getData')
421419
->will($this->returnValue($associatedProducts));
422-
$this->serializer->expects($this->any())
423-
->method('serialize')
424-
->willReturn(json_encode($buyRequest->getData()));
425420

426421
$this->assertEquals(
427422
[0 => $this->product],
@@ -521,10 +516,6 @@ public function testPrepareForCartAdvancedWithProductsStrictFalse()
521516
$buyRequest = new \Magento\Framework\DataObject();
522517
$buyRequest->setSuperGroup([$associatedId => 1]);
523518

524-
$this->serializer->expects($this->any())
525-
->method('serialize')
526-
->willReturn(json_encode($buyRequest->getData()));
527-
528519
$cached = true;
529520
$this->product
530521
->expects($this->atLeastOnce())
@@ -541,49 +532,36 @@ public function testPrepareForCartAdvancedWithProductsStrictFalse()
541532
);
542533
}
543534

544-
public function testPrepareForCartAdvancedWithProductsStrictTrue()
545-
{
546-
$associatedProduct = $this->createMock(\Magento\Catalog\Model\Product::class);
547-
$associatedId = 9384;
548-
$associatedProduct->expects($this->atLeastOnce())->method('getId')->will($this->returnValue($associatedId));
549-
550-
$typeMock = $this->createPartialMock(
551-
\Magento\Catalog\Model\Product\Type\AbstractType::class,
552-
['_prepareProduct', 'deleteTypeSpecificData']
553-
);
554-
$associatedPrepareResult = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
555-
->setMockClassName('resultProduct')
556-
->disableOriginalConstructor()
557-
->getMock();
558-
$typeMock->expects($this->once())->method('_prepareProduct')->willReturn([$associatedPrepareResult]);
559-
560-
$associatedProduct->expects($this->once())->method('getTypeInstance')->willReturn($typeMock);
561-
562-
$buyRequest = new \Magento\Framework\DataObject();
563-
$buyRequest->setSuperGroup([$associatedId => 1]);
564-
565-
$this->serializer->expects($this->any())
566-
->method('serialize')
567-
->willReturn(json_encode($buyRequest->getData()));
568-
569-
$cached = true;
570-
$this->product
571-
->expects($this->atLeastOnce())
572-
->method('hasData')
573-
->will($this->returnValue($cached));
574-
$this->product
575-
->expects($this->atLeastOnce())
576-
->method('getData')
577-
->will($this->returnValue([$associatedProduct]));
578-
579-
$associatedPrepareResult->expects($this->at(1))->method('addCustomOption')->with(
580-
'product_type',
581-
'grouped',
582-
$this->product
583-
);
535+
/**
536+
* Test prepareForCartAdvanced() method in full mode
537+
*
538+
* @dataProvider prepareForCartAdvancedWithProductsStrictTrueDataProvider
539+
* @param array $subProducts
540+
* @param array $buyRequest
541+
* @param mixed $expectedResult
542+
*/
543+
public function testPrepareForCartAdvancedWithProductsStrictTrue(
544+
array $subProducts,
545+
array $buyRequest,
546+
$expectedResult
547+
) {
548+
$associatedProducts = $this->configureProduct($subProducts);
549+
$buyRequestObject = new \Magento\Framework\DataObject();
550+
$buyRequestObject->setSuperGroup($buyRequest);
551+
$associatedProductsById = [];
552+
foreach ($associatedProducts as $associatedProduct) {
553+
$associatedProductsById[$associatedProduct->getId()] = $associatedProduct;
554+
}
555+
if (is_array($expectedResult)) {
556+
$expectedResultArray = $expectedResult;
557+
$expectedResult = [];
558+
foreach ($expectedResultArray as $id) {
559+
$expectedResult[] = $associatedProductsById[$id];
560+
}
561+
}
584562
$this->assertEquals(
585-
[$associatedPrepareResult],
586-
$this->_model->prepareForCartAdvanced($buyRequest, $this->product)
563+
$expectedResult,
564+
$this->_model->prepareForCartAdvanced($buyRequestObject, $this->product)
587565
);
588566
}
589567

@@ -618,4 +596,120 @@ public function testFlushAssociatedProductsCache()
618596
->willReturnSelf();
619597
$this->assertEquals($productMock, $this->_model->flushAssociatedProductsCache($productMock));
620598
}
599+
600+
/**
601+
* @return array
602+
*/
603+
public function prepareForCartAdvancedWithProductsStrictTrueDataProvider(): array
604+
{
605+
return [
606+
[
607+
[
608+
[
609+
'getId' => 1,
610+
'getQty' => 100,
611+
'isSalable' => true
612+
],
613+
[
614+
'getId' => 2,
615+
'getQty' => 200,
616+
'isSalable' => true
617+
]
618+
],
619+
[
620+
1 => 2,
621+
2 => 1,
622+
],
623+
[1, 2]
624+
],
625+
[
626+
[
627+
[
628+
'getId' => 1,
629+
'getQty' => 100,
630+
'isSalable' => true
631+
],
632+
[
633+
'getId' => 2,
634+
'getQty' => 0,
635+
'isSalable' => false
636+
]
637+
],
638+
[
639+
1 => 2,
640+
],
641+
[1]
642+
],
643+
[
644+
[
645+
[
646+
'getId' => 1,
647+
'getQty' => 0,
648+
'isSalable' => true
649+
],
650+
[
651+
'getId' => 2,
652+
'getQty' => 0,
653+
'isSalable' => false
654+
]
655+
],
656+
[
657+
],
658+
'Please specify the quantity of product(s).'
659+
],
660+
[
661+
[
662+
[
663+
'getId' => 1,
664+
'getQty' => 0,
665+
'isSalable' => false
666+
],
667+
[
668+
'getId' => 2,
669+
'getQty' => 0,
670+
'isSalable' => false
671+
]
672+
],
673+
[
674+
],
675+
'Please specify the quantity of product(s).'
676+
]
677+
];
678+
}
679+
680+
/**
681+
* Configure sub-products of grouped product
682+
*
683+
* @param array $subProducts
684+
* @return array
685+
*/
686+
private function configureProduct(array $subProducts): array
687+
{
688+
$associatedProducts = [];
689+
foreach ($subProducts as $data) {
690+
$associatedProduct = $this->createMock(\Magento\Catalog\Model\Product::class);
691+
foreach ($data as $method => $value) {
692+
$associatedProduct->method($method)->willReturn($value);
693+
}
694+
$associatedProducts[] = $associatedProduct;
695+
696+
$typeMock = $this->createPartialMock(
697+
\Magento\Catalog\Model\Product\Type\AbstractType::class,
698+
['_prepareProduct', 'deleteTypeSpecificData']
699+
);
700+
$typeMock->method('_prepareProduct')->willReturn([$associatedProduct]);
701+
$associatedProduct->method('getTypeInstance')->willReturn($typeMock);
702+
}
703+
$this->product
704+
->expects($this->atLeastOnce())
705+
->method('hasData')
706+
->with('_cache_instance_associated_products')
707+
->willReturn(true);
708+
$this->product
709+
->expects($this->atLeastOnce())
710+
->method('getData')
711+
->with('_cache_instance_associated_products')
712+
->willReturn($associatedProducts);
713+
return $associatedProducts;
714+
}
621715
}

0 commit comments

Comments
 (0)