Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions app/code/Magento/QuoteGraphQl/Model/Resolver/CartItemPrices.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,24 +64,35 @@ public function resolve(Field $field, $context, ResolveInfo $info, ?array $value
}
/** @var Item $cartItem */
$cartItem = $value['model'];
if (!$this->totals) {

// Collect totals only if discount, original item price and original rowtotal is there in the request
// avoid retrieve totals with the below keys if its not absolutely required
// except discounts can be removed if original_item_price and original_row_total is saved in db
if (!$this->totals && !empty(array_intersect(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Magic array of field names is hardcoded please define constant for these critical field names

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@engcom-Hotel define one const per field or total array as a single array const ?

[
'discounts', 'original_item_price', 'original_row_total',
'catalog_discount', 'row_catalog_discount'
],
array_keys($info->getFieldSelection(1))
))
) {
// The totals calculation is based on quote address.
// But the totals should be calculated even if no address is set
$this->totals = $this->totalsCollector->collectQuoteTotals($cartItem->getQuote());
}

$currencyCode = $cartItem->getQuote()->getQuoteCurrencyCode();

/** calculate bundle product discount */
$discountAmount = 0;
if ($cartItem->getProductType() == 'bundle') {
$discounts = $cartItem->getExtensionAttributes()->getDiscounts() ?? [];
$discountAmount = 0;
foreach ($discounts as $discount) {
$discountAmount += $discount->getDiscountData()->getAmount();
foreach ($cartItem->getChildren() as $childItem) {
$discountAmount += $childItem->getDiscountAmount();
}
} else {
$discountAmount = $cartItem->getDiscountAmount();
}

$discountAmount += $cartItem->getDiscountAmount();

return [
'model' => $cartItem,
'price' => [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
<?php
/**
* Copyright 2024 Adobe
* All Rights Reserved.
*/
declare(strict_types=1);

namespace Magento\QuoteGraphQl\Test\Unit\Model\Resolver;

use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\GraphQl\Model\Query\Context;
use Magento\Quote\Model\Quote;
use Magento\Quote\Model\Quote\Item;
use Magento\QuoteGraphQl\Model\Cart\TotalsCollector;
use Magento\QuoteGraphQl\Model\Resolver\CartItemPrices;
use Magento\QuoteGraphQl\Model\GetDiscounts;
use Magento\Framework\Pricing\PriceCurrencyInterface;
use Magento\QuoteGraphQl\Model\GetOptionsRegularPrice;
use Magento\Framework\Api\ExtensionAttributesInterface;
use Magento\Catalog\Model\Product;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;

/**
* @see CartItemPrices
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class CartItemPricesTest extends TestCase
{
/**
* @var CartItemPrices
*/
private CartItemPrices $cartItemPrices;

/**
* @var TotalsCollector|MockObject
*/
private TotalsCollector $totalsCollectorMock;

/**
* @var GetDiscounts |MockObject
*/
private GetDiscounts $getDiscountsMock;

/**
* @var PriceCurrencyInterface |MockObject
*/
private PriceCurrencyInterface $priceCurrencyMock;

/**
* @var GetOptionsRegularPrice |MockObject
*/
private GetOptionsRegularPrice $getOptionsRegularPriceMock;

/**
* @var Field|MockObject
*/
private Field $fieldMock;

/**
* @var ResolveInfo|MockObject
*/
private ResolveInfo $resolveInfoMock;

/**
* @var Context|MockObject
*/
private Context $contextMock;

/**
* @var Quote|MockObject
*/
private Quote $quoteMock;

/**
* @var Item|MockObject
*/
private Item $itemMock;

/**
* @var Product|MockObject
*/
private Product $productMock;

/**
* @var ExtensionAttributesInterface|MockObject
*/
private ExtensionAttributesInterface $itemExtensionMock;

/**
* @var array
*/
private array $valueMock = [];

protected function setUp(): void
{
$this->totalsCollectorMock = $this->createMock(TotalsCollector::class);
$this->getDiscountsMock = $this->createMock(GetDiscounts::class);
$this->priceCurrencyMock = $this->createMock(PriceCurrencyInterface::class);
$this->getOptionsRegularPriceMock = $this->createMock(GetOptionsRegularPrice::class);
$this->productMock = $this->getMockBuilder(Product::class)
->disableOriginalConstructor()
->onlyMethods(['getCustomOption'])
->getMock();
$this->fieldMock = $this->createMock(Field::class);
$this->resolveInfoMock = $this->createMock(ResolveInfo::class);
$this->contextMock = $this->createMock(Context::class);
$this->quoteMock = $this->getMockBuilder(Quote::class)
->disableOriginalConstructor()
->addMethods(['getQuoteCurrencyCode'])
->getMock();
$this->itemMock = $this->getMockBuilder(Item::class)
->addMethods([
'getPriceInclTax', 'getRowTotal',
'getRowTotalInclTax', 'getDiscountAmount'
])
->onlyMethods([
'getCalculationPrice', 'getQuote', 'getExtensionAttributes',
'getProduct', 'getOriginalPrice'
])
->disableOriginalConstructor()
->getMock();
$this->itemExtensionMock = $this->getMockBuilder(
ExtensionAttributesInterface::class
)->addMethods(['getDiscounts'])->getMockForAbstractClass();

$this->cartItemPrices = new CartItemPrices(
$this->totalsCollectorMock,
$this->getDiscountsMock,
$this->priceCurrencyMock,
$this->getOptionsRegularPriceMock
);
}

public function testResolve(): void
{
$this->valueMock = ['model' => $this->itemMock];

$this->resolveInfoMock->expects($this->once())
->method('getFieldSelection')
->with(1)
->willReturn([]);

$this->itemMock
->expects($this->exactly(2))
->method('getQuote')
->willReturn($this->quoteMock);

$this->quoteMock
->expects($this->once())
->method('getQuoteCurrencyCode')
->willReturn('USD');

$this->itemMock
->expects($this->once())
->method('getDiscountAmount');

$this->itemMock
->expects($this->once())
->method('getCalculationPrice');

$this->itemMock
->expects($this->once())
->method('getPriceInclTax');

$this->itemMock
->expects($this->any())
->method('getOriginalPrice')
->willReturn(0);

$this->itemMock
->expects($this->once())
->method('getRowTotal');

$this->itemMock
->expects($this->once())
->method('getRowTotalInclTax');

$this->itemMock
->expects($this->once())
->method('getExtensionAttributes')
->willReturn($this->itemExtensionMock);

$this->itemMock
->expects($this->any())
->method('getProduct')
->willReturn($this->productMock);

$this->productMock
->expects($this->exactly(2))
->method('getCustomOption')
->willReturn(null);

$this->itemExtensionMock
->expects($this->once())
->method('getDiscounts')
->willReturn([]);

$this->getDiscountsMock
->expects($this->once())
->method('execute')
->with($this->quoteMock, []);

$this->cartItemPrices->resolve($this->fieldMock, $this->contextMock, $this->resolveInfoMock, $this->valueMock);
}

public function testResolveWithoutModelInValueParameter(): void
{
$this->expectException(LocalizedException::class);
$this->expectExceptionMessage('"model" value should be specified');
$this->cartItemPrices->resolve($this->fieldMock, $this->contextMock, $this->resolveInfoMock, $this->valueMock);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface;
use Magento\Quote\Model\QuoteFactory;

/**
* Get masked quote id by reserved order id
*/
class GetMaskedQuoteIdByReservedOrderId
{
/**
Expand Down Expand Up @@ -51,15 +48,21 @@ public function __construct(
* Get masked quote id by reserved order id
*
* @param string $reservedOrderId
* @param bool $forceCollectTotal
* @return string
* @throws NoSuchEntityException
*/
public function execute(string $reservedOrderId): string
public function execute(string $reservedOrderId, bool $forceCollectTotal = false): string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameter name $forceCollectTotal doesn't follow Magento naming conventions (should be $shouldCollectTotals for boolean)

{
$quote = $this->quoteFactory->create();
$quote->setSharedStoreIds(['*']);
$this->quoteResource->load($quote, $reservedOrderId, 'reserved_order_id');

// If dataprovider is used, we need to collect totals manually and save quote
if ($forceCollectTotal) {
$this->quoteResource->save($quote->collectTotals());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess saving quote introduced in a method named "get" which will violates single responsibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Datafixture apply different tax while trigger this function from ‎dev/tests/api-functional/testsuite/Magento/GraphQl/Weee/CartItemPricesWithFPTTest.php‎ given the tax update totals should be saved. Given its a test I would suggest to keep this as-is unless you think differently.

}

return $this->quoteIdToMaskedId->execute((int)$quote->getId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ private function writeConfig(array $settings): void
public function testCartItemFixedProductTax(array $taxSettings, array $expectedFtps): void
{
$this->writeConfig($taxSettings);
$maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
// Second argument true is to ensure collectTotals and save after every quote update
$maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote', true);
$query = $this->getQuery($maskedQuoteId);
$result = $this->graphQlQuery($query);
$this->assertArrayNotHasKey('errors', $result);
Expand Down