Skip to content
32 changes: 27 additions & 5 deletions app/code/Magento/Catalog/Test/Unit/Helper/ProductTestHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ public function getData($key = '', $index = null)
if (!is_array($this->data)) {
$this->data = [];
}

// Check if there's a callback set for getData
if (isset($this->data['get_data_callback'])) {
return call_user_func($this->data['get_data_callback'], $key);
Expand All @@ -594,17 +594,17 @@ public function getData($key = '', $index = null)
return $this->data['product_data'] ?? [];
}
$productData = $this->data['product_data'] ?? [];

// If key doesn't exist, return null
if (!isset($productData[$key])) {
return null;
}

// If index is provided and value is an array, return indexed value
if ($index !== null && is_array($productData[$key])) {
return $productData[$key][$index] ?? null;
}

return $productData[$key];
}

Expand All @@ -621,7 +621,7 @@ public function setData($key, $value = null): self
if (!is_array($this->data)) {
$this->data = [];
}

// Use separate productData array for getData/setData to avoid conflicts
if (!isset($this->data['product_data'])) {
$this->data['product_data'] = [];
Expand Down Expand Up @@ -1624,4 +1624,26 @@ public function getCost()
{
return $this->getData('cost') ?? $this->cost;
}

/**
* Get initial qty
*
* @return int|null
*/
public function getInitialQty(): ?int
{
return $this->getData('initial_qty');
}

/**
* Set parent product id for tests.
*
* @param int|null $initialQty
* @return $this
*/
public function setInitialQty(?int $initialQty)
{
$this->setData('initial_qty', $initialQty);
return $this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public function convert(\Magento\Catalog\Model\Product $product)
'sku' => $product->getSku(),
'position' => $product->getPosition(),
'custom_attributes' => [
['attribute_code' => 'qty', 'value' => $product->getQty()],
['attribute_code' => 'qty', 'value' => $product->getInitialQty() ?? $product->getQty()],
]
];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ public function getAssociatedProducts($product)
);

foreach ($collection as $item) {
$item->setInitialQty($item->getQty());
$associatedProducts[] = $item;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php
/**
* Copyright 2025 Adobe
* All Rights Reserved.
*/
declare(strict_types=1);

namespace Magento\GroupedProduct\Test\Unit\Model\Product\Link;

use Magento\Catalog\Test\Unit\Helper\ProductTestHelper;
use Magento\GroupedProduct\Model\Product\Link\ProductEntity\Converter;
use PHPUnit\Framework\TestCase;

/**
* Tests for Converter
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class ConverterTest extends TestCase
{
/**
* @var Converter
*/
private Converter $converter;

protected function setUp(): void
{
$this->converter = new Converter();
}

public function testConvertUsesInitialQtyWhenAvailable(): void
{
$product = $this->createPartialMock(
ProductTestHelper::class,
['getTypeId', 'getSku', 'getQty', 'getPosition', 'getInitialQty']
);

$product->method('getTypeId')->willReturn('simple');
$product->method('getSku')->willReturn('sku-123');
$product->method('getPosition')->willReturn(5);

// Qty override
$product->method('getInitialQty')->willReturn(10);
$product->method('getQty')->willReturn(2); // fallback should NOT be used

$result = $this->converter->convert($product);

$this->assertSame('simple', $result['type']);
$this->assertSame('sku-123', $result['sku']);
$this->assertSame(5, $result['position']);

$this->assertSame(
[
['attribute_code' => 'qty', 'value' => 10]
],
$result['custom_attributes']
);
}

public function testConvertFallsBackToQtyIfInitialQtyIsNull(): void
{
$product = $this->createPartialMock(
ProductTestHelper::class,
['getTypeId', 'getSku', 'getQty', 'getPosition', 'getInitialQty']
);

$product->method('getTypeId')->willReturn('simple');
$product->method('getSku')->willReturn('sku-456');
$product->method('getPosition')->willReturn(7);
$product->method('getInitialQty')->willReturn(null);
$product->method('getQty')->willReturn(3);

$result = $this->converter->convert($product);

$this->assertSame(
[
['attribute_code' => 'qty', 'value' => 3]
],
$result['custom_attributes']
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace Magento\GroupedProduct\Test\Unit\Model\Product\Type;

use Magento\Catalog\Test\Unit\Helper\ProductTestHelper;
use PHPUnit\Framework\Attributes\DataProvider;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\Product\Attribute\Source\Status;
Expand All @@ -22,6 +23,7 @@
use Magento\GroupedProduct\Model\Product\Type\Grouped;
use Magento\GroupedProduct\Model\ResourceModel\Product\Link;
use Magento\MediaStorage\Helper\File\Storage\Database;
use PHPUnit\Framework\MockObject\Exception;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
Expand Down Expand Up @@ -164,6 +166,58 @@ public function testGetAssociatedProducts(): void
$this->assertEquals($associatedProducts, $this->_model->getAssociatedProducts($this->product));
}

/**
* Verify get associated products with setting initial qty
*
* @return void
*/
public function testGetAssociatedProductsSetsInitialQty(): void
{
$this->product->expects($this->atLeastOnce())
->method('hasData')
->willReturn(false);
$savedValue = null;
$this->product->expects($this->atLeastOnce())
->method('setData')
->willReturnCallback(function ($key, $value) use (&$savedValue) {
$savedValue = $value;
return null;
});
$this->product->expects($this->atLeastOnce())
->method('getData')
->willReturnCallback(function () use (&$savedValue) {
return $savedValue;
});
$itemMock = $this->createMock(ProductTestHelper::class);
$itemMock->expects($this->once())
->method('getQty')
->willReturn(10);
$itemMock->expects($this->once())
->method('setInitialQty')
->with(10);
$collectionMock = $this->createMock(Collection::class);
$collectionMock->method('addAttributeToSelect')->willReturnSelf();
$collectionMock->method('addFilterByRequiredOptions')->willReturnSelf();
$collectionMock->method('setPositionOrder')->willReturnSelf();
$collectionMock->method('addStoreFilter')->willReturnSelf();
$collectionMock->method('addAttributeToFilter')->willReturnSelf();
$collectionMock->method('setFlag')->willReturnSelf();
$collectionMock->method('setIsStrongMode')->willReturnSelf();
$collectionMock->expects($this->once())
->method('getIterator')
->willReturn(new \ArrayIterator([$itemMock]));
$linkMock = $this->createMock(Product\Link::class);
$linkMock->expects($this->once())
->method('getProductCollection')
->willReturn($collectionMock);
$this->product->expects($this->once())
->method('getLinkInstance')
->willReturn($linkMock);
$result = $this->_model->getAssociatedProducts($this->product);
$this->assertCount(1, $result);
$this->assertSame($itemMock, $result[0]);
}

/**
* Verify able to set status filter
*
Expand Down