From dfc2a18aba7321aeb0d81e0d097fc3d672675c80 Mon Sep 17 00:00:00 2001 From: Senthilkumar Muppidathi Date: Sat, 11 Oct 2025 13:55:39 +0700 Subject: [PATCH 1/9] #40209 - Conditional TotalCollect Trigger for mutation and Virtual Carts --- .../Model/Resolver/CartPrices.php | 105 +++++++++++++---- .../Unit/Model/Resolver/CartPricesTest.php | 109 +++++++++++++++++- 2 files changed, 186 insertions(+), 28 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php index cf27abfb558cc..12f74c50fb43e 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php @@ -1,7 +1,7 @@ totalsCollector = $totalsCollector; $this->scopeConfig = $scopeConfig ?? ObjectManager::getInstance()->get(ScopeConfigInterface::class); @@ -56,16 +69,29 @@ public function resolve(Field $field, $context, ResolveInfo $info, ?array $value /** @var Quote $quote */ $quote = $value['model']; - /** - * To calculate a right discount value - * before calculate totals - * need to reset Cart Fixed Rules in the quote - */ - $quote->setCartFixedRules([]); - $cartTotals = $this->totalsCollector->collectQuoteTotals($quote); $currency = $quote->getQuoteCurrencyCode(); - $appliedTaxes = $this->getAppliedTaxes($cartTotals, $currency); + if(!$quote->isVirtual() && $info->operation->operation == self::QUERY_TYPE){ + $addressTotalsData = $quote->getShippingAddress()->getData(); + $cartTotals = $this->totalsFactory->create(); + $this->dataObjectHelper->populateWithArray( + $cartTotals, + $addressTotalsData, + QuoteTotalsInterface::class + ); + + $appliedTaxes = $this->getAppliedTaxes($quote->getShippingAddress(), $currency); + } else { + /** + * To calculate a right discount value + * before calculate totals + * need to reset Cart Fixed Rules in the quote + */ + $quote->setCartFixedRules([]); + $cartTotals = $this->totalsCollector->collectQuoteTotals($quote); + $appliedTaxes = $this->getAppliedTaxes($cartTotals, $currency); + } + $grandTotal = $cartTotals->getGrandTotal(); $totalAppliedTaxes = 0; @@ -92,14 +118,22 @@ public function resolve(Field $field, $context, ResolveInfo $info, ?array $value /** * Returns taxes applied to the current quote * - * @param Total $total + * @param \Magento\Quote\Model\Quote\Address|Total $addressOrTotals * @param string $currency * @return array + * @throws \InvalidArgumentException */ - private function getAppliedTaxes(Total $total, string $currency): array + private function getAppliedTaxes($addressOrTotals, string $currency): array { + if ( + !$addressOrTotals instanceof Total && + !$addressOrTotals instanceof \Magento\Quote\Model\Quote\Address + ) { + throw new \InvalidArgumentException('Unsupported totals type: ' . get_class($addressOrTotals)); + } + $appliedTaxesData = []; - $appliedTaxes = $total->getAppliedTaxes(); + $appliedTaxes = $addressOrTotals->getAppliedTaxes(); if (empty($appliedTaxes)) { return $appliedTaxesData; @@ -133,37 +167,60 @@ private function getAppliedTaxes(Total $total, string $currency): array /** * Returns information about an applied discount * - * @param Total $total + * @param @param Total|CartTotals $totals * @param string $currency * @return array|null + * @throws \InvalidArgumentException */ - private function getDiscount(Total $total, string $currency) + private function getDiscount($totals, string $currency) { - if ($total->getDiscountAmount() === 0) { + $this->validateTotalsInstance($totals); + + if ($totals->getDiscountAmount() === 0) { return null; } return [ - 'label' => $total->getDiscountDescription() !== null ? explode(', ', $total->getDiscountDescription()) : [], - 'amount' => ['value' => $total->getDiscountAmount(), 'currency' => $currency] + 'label' => $totals->getDiscountDescription() !== null ? explode(', ', $totals->getDiscountDescription()) : [], + 'amount' => ['value' => $totals->getDiscountAmount(), 'currency' => $currency] ]; } /** * Get Subtotal with discount excluding tax. * - * @param Total $cartTotals + * @param @param Total|CartTotals $totals * @return float + * @throws \InvalidArgumentException */ - private function getSubtotalWithDiscountExcludingTax(Total $cartTotals): float + private function getSubtotalWithDiscountExcludingTax($totals): float { + $this->validateTotalsInstance($totals); + $discountIncludeTax = $this->scopeConfig->getValue( 'tax/calculation/discount_tax', ScopeInterface::SCOPE_STORE ) ?? 0; $discountExclTax = $discountIncludeTax ? - $cartTotals->getDiscountAmount() + $cartTotals->getDiscountTaxCompensationAmount() : - $cartTotals->getDiscountAmount(); + $totals->getDiscountAmount() + $totals->getDiscountTaxCompensationAmount() : + $totals->getDiscountAmount(); + + return $totals->getSubtotal() + $discountExclTax; + } + + /** + * Validates the provided totals instance to ensure it is of a supported type. + * + * @param Total|CartTotals $totals + * @return void + * @throws \InvalidArgumentException If the provided totals instance is of an unsupported type. + */ + private function validateTotalsInstance($totals) { - return $cartTotals->getSubtotal() + $discountExclTax; + if ( + !$totals instanceof Total && + !$totals instanceof CartTotals + ) { + throw new \InvalidArgumentException('Unsupported totals type: ' . get_class($totals)); + } } } diff --git a/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php b/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php index 955cdfbe3f0d2..dc60454f3d032 100644 --- a/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php +++ b/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php @@ -11,11 +11,16 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Framework\Api\DataObjectHelper; use Magento\GraphQl\Model\Query\Context; +use Magento\Quote\Api\Data\TotalsInterface; +use Magento\Quote\Api\Data\TotalsInterfaceFactory; use Magento\Quote\Model\Quote; +use Magento\Quote\Model\Quote\Address; use Magento\Quote\Model\Quote\Address\Total; use Magento\QuoteGraphQl\Model\Cart\TotalsCollector; use Magento\QuoteGraphQl\Model\Resolver\CartPrices; +use GraphQL\Language\AST\OperationDefinitionNode; use PHPUnit\Framework\TestCase; use PHPUnit\Framework\MockObject\MockObject; @@ -49,6 +54,11 @@ class CartPricesTest extends TestCase */ private ResolveInfo $resolveInfoMock; + /** + * @var DataObjectHelper|MockObject + */ + private DataObjectHelper $dataObjectHelperMock; + /** * @var Context|MockObject */ @@ -64,6 +74,16 @@ class CartPricesTest extends TestCase */ private Total $totalMock; + /** + * @var TotalsInterfaceFactory|MockObject + */ + private $totalsFactoryMock; + + /** + * @var Address|MockObject + */ + private $shippingAddressMock; + /** * @var array */ @@ -72,13 +92,31 @@ class CartPricesTest extends TestCase protected function setUp(): void { $this->totalsCollectorMock = $this->createMock(TotalsCollector::class); + $this->dataObjectHelperMock = $this->createMock(DataObjectHelper::class); + $this->totalsFactoryMock = $this->getMockBuilder(TotalsInterfaceFactory::class) + ->disableOriginalConstructor() + ->onlyMethods(['create']) + ->addMethods( + [ + 'getSubtotal', + 'getSubtotalInclTax', + 'getGrandTotal', + 'getDiscountTaxCompensationAmount', + 'getDiscountAmount', + 'getDiscountDescription', + 'getAppliedTaxes' + ] + ) + ->getMock(); $this->scopeConfigMock = $this->createMock(ScopeConfigInterface::class); $this->fieldMock = $this->createMock(Field::class); $this->resolveInfoMock = $this->createMock(ResolveInfo::class); + $this->resolveInfoMock->operation = new OperationDefinitionNode([]); $this->contextMock = $this->createMock(Context::class); $this->quoteMock = $this->getMockBuilder(Quote::class) ->disableOriginalConstructor() ->addMethods(['getQuoteCurrencyCode']) + ->onlyMethods(['isVirtual', 'getShippingAddress']) ->getMock(); $this->totalMock = $this->getMockBuilder(Total::class) ->disableOriginalConstructor() @@ -96,6 +134,8 @@ protected function setUp(): void ->getMock(); $this->cartPrices = new CartPrices( $this->totalsCollectorMock, + $this->totalsFactoryMock, + $this->dataObjectHelperMock, $this->scopeConfigMock ); } @@ -107,7 +147,71 @@ public function testResolveWithoutModelInValueParameter(): void $this->cartPrices->resolve($this->fieldMock, $this->contextMock, $this->resolveInfoMock, $this->valueMock); } - public function testResolve(): void + public function testResolveQuery(): void + { + $this->resolveInfoMock->operation->operation = 'query'; + + $this->shippingAddressMock = $this->getMockBuilder(Address::class) + ->disableOriginalConstructor() + ->onlyMethods(['getData']) + ->getMock(); + + $this->shippingAddressMock->expects($this->any()) + ->method('getData') + ->willReturn([]); + + $this->quoteMock + ->expects($this->once()) + ->method('isVirtual') + ->willReturn(0); + + $this->quoteMock + ->expects($this->any()) + ->method('getShippingAddress') + ->willReturn($this->shippingAddressMock); + + $this->dataObjectHelperMock->expects($this->once()) + ->method('populateWithArray') + ->with( + $this->identicalTo($this->totalMock), + [], + TotalsInterface::class + ); + + $this->totalsFactoryMock + ->expects($this->once()) + ->method('create') + ->willReturn($this->totalMock); + + $this->resolve(); + + } + + public function testResolveQueryVirtual(): void + { + $this->quoteMock + ->expects($this->once()) + ->method('isVirtual') + ->willReturn(1); + + $this->totalMock + ->expects($this->once()) + ->method('getAppliedTaxes'); + + $this->resolve(); + } + public function testResolveMutation(): void + { + $this->resolveInfoMock->operation->operation = 'mutation'; + + $this->totalMock + ->expects($this->once()) + ->method('getAppliedTaxes'); + + $this->resolve(); + } + + private function resolve(): void { $this->valueMock = ['model' => $this->quoteMock]; $this->quoteMock @@ -126,9 +230,6 @@ public function testResolve(): void $this->totalMock ->method('getDiscountDescription') ->willReturn('Discount Description'); - $this->totalMock - ->expects($this->once()) - ->method('getAppliedTaxes'); $this->scopeConfigMock ->expects($this->once()) ->method('getValue') From 82f2786f7da9c7e28a0ad96ebaab715598d6b207 Mon Sep 17 00:00:00 2001 From: Senthilkumar Muppidathi Date: Sat, 11 Oct 2025 14:44:26 +0700 Subject: [PATCH 2/9] #40209 - Fixing Implicit Null of ScopeConfig --- app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php | 2 +- .../QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php index 12f74c50fb43e..f8c8c380624ca 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php @@ -52,7 +52,7 @@ public function __construct( TotalsCollector $totalsCollector, private TotalsInterfaceFactory $totalsFactory, private DataObjectHelper $dataObjectHelper, - ScopeConfigInterface $scopeConfig = null + ?ScopeConfigInterface $scopeConfig = null ) { $this->totalsCollector = $totalsCollector; $this->scopeConfig = $scopeConfig ?? ObjectManager::getInstance()->get(ScopeConfigInterface::class); diff --git a/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php b/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php index dc60454f3d032..4d7c3a47be51d 100644 --- a/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php +++ b/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php @@ -1,7 +1,7 @@ Date: Sat, 11 Oct 2025 18:50:08 +0700 Subject: [PATCH 3/9] #40209 - Fixing Static Tests --- .../Model/Resolver/CartPrices.php | 24 +++++++++---------- .../Unit/Model/Resolver/CartPricesTest.php | 3 ++- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php index f8c8c380624ca..9d28e15f0a4d6 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php @@ -24,6 +24,8 @@ /** * @inheritdoc + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CartPrices implements ResolverInterface { @@ -71,7 +73,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, ?array $value $quote = $value['model']; $currency = $quote->getQuoteCurrencyCode(); - if(!$quote->isVirtual() && $info->operation->operation == self::QUERY_TYPE){ + if (!$quote->isVirtual() && $info->operation->operation == self::QUERY_TYPE) { $addressTotalsData = $quote->getShippingAddress()->getData(); $cartTotals = $this->totalsFactory->create(); $this->dataObjectHelper->populateWithArray( @@ -125,10 +127,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, ?array $value */ private function getAppliedTaxes($addressOrTotals, string $currency): array { - if ( - !$addressOrTotals instanceof Total && - !$addressOrTotals instanceof \Magento\Quote\Model\Quote\Address - ) { + if (!$addressOrTotals instanceof Total && !$addressOrTotals instanceof \Magento\Quote\Model\Quote\Address) { throw new \InvalidArgumentException('Unsupported totals type: ' . get_class($addressOrTotals)); } @@ -167,7 +166,7 @@ private function getAppliedTaxes($addressOrTotals, string $currency): array /** * Returns information about an applied discount * - * @param @param Total|CartTotals $totals + * @param Total|CartTotals $totals * @param string $currency * @return array|null * @throws \InvalidArgumentException @@ -180,7 +179,8 @@ private function getDiscount($totals, string $currency) return null; } return [ - 'label' => $totals->getDiscountDescription() !== null ? explode(', ', $totals->getDiscountDescription()) : [], + 'label' => $totals->getDiscountDescription() !== null ? + explode(', ', $totals->getDiscountDescription()) : [], 'amount' => ['value' => $totals->getDiscountAmount(), 'currency' => $currency] ]; } @@ -188,7 +188,7 @@ private function getDiscount($totals, string $currency) /** * Get Subtotal with discount excluding tax. * - * @param @param Total|CartTotals $totals + * @param Total|CartTotals $totals * @return float * @throws \InvalidArgumentException */ @@ -214,12 +214,10 @@ private function getSubtotalWithDiscountExcludingTax($totals): float * @return void * @throws \InvalidArgumentException If the provided totals instance is of an unsupported type. */ - private function validateTotalsInstance($totals) { + private function validateTotalsInstance($totals) + { - if ( - !$totals instanceof Total && - !$totals instanceof CartTotals - ) { + if (!$totals instanceof Total && !$totals instanceof CartTotals) { throw new \InvalidArgumentException('Unsupported totals type: ' . get_class($totals)); } } diff --git a/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php b/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php index 4d7c3a47be51d..082be124f4863 100644 --- a/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php +++ b/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php @@ -26,6 +26,8 @@ /** * @see CartPrices + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CartPricesTest extends TestCase { @@ -184,7 +186,6 @@ public function testResolveQuery(): void ->willReturn($this->totalMock); $this->resolve(); - } public function testResolveQueryVirtual(): void From 3fd68ba51b0f54c5fe09a2d8f8dcf6f501ee0bd9 Mon Sep 17 00:00:00 2001 From: Senthilkumar Muppidathi Date: Mon, 13 Oct 2025 14:39:04 +0700 Subject: [PATCH 4/9] #40209 - Removing Extenstion Attribute after Shipping getData() --- app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php index 9d28e15f0a4d6..c688cbd5f40ec 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php @@ -14,6 +14,7 @@ use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Framework\Api\DataObjectHelper; +use Magento\Framework\Api\ExtensibleDataInterface; use Magento\Quote\Api\Data\TotalsInterface as QuoteTotalsInterface; use Magento\Quote\Api\Data\TotalsInterfaceFactory; use Magento\Quote\Model\Quote; @@ -75,6 +76,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, ?array $value if (!$quote->isVirtual() && $info->operation->operation == self::QUERY_TYPE) { $addressTotalsData = $quote->getShippingAddress()->getData(); + unset($addressTotalsData[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]); $cartTotals = $this->totalsFactory->create(); $this->dataObjectHelper->populateWithArray( $cartTotals, From 2c13e99ff84d0eae4b997ffc8115b3ef5ebe16c7 Mon Sep 17 00:00:00 2001 From: Senthil Kumar Muppidathi Date: Tue, 21 Oct 2025 22:46:29 +0530 Subject: [PATCH 5/9] Fix copyright comment formatting in CartPrices.php --- app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php index c688cbd5f40ec..337caff1a2c2f 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php @@ -1,7 +1,7 @@ Date: Tue, 21 Oct 2025 22:47:04 +0530 Subject: [PATCH 6/9] Fix copyright notice formatting in CartPricesTest.php --- .../QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php b/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php index 082be124f4863..4792da6745b73 100644 --- a/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php +++ b/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php @@ -1,7 +1,7 @@ Date: Wed, 29 Oct 2025 23:49:39 +0530 Subject: [PATCH 7/9] #40209 - Fixing discount lable null and WebAPI Test Failures --- app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php index 337caff1a2c2f..5bafcc83dd09b 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php @@ -84,6 +84,10 @@ public function resolve(Field $field, $context, ResolveInfo $info, ?array $value QuoteTotalsInterface::class ); + if (isset($addressTotalsData['discount_description'])) { + $cartTotals->setDiscountDescription($addressTotalsData['discount_description']); + } + $appliedTaxes = $this->getAppliedTaxes($quote->getShippingAddress(), $currency); } else { /** @@ -177,7 +181,7 @@ private function getDiscount($totals, string $currency) { $this->validateTotalsInstance($totals); - if ($totals->getDiscountAmount() === 0) { + if ($totals->getDiscountAmount() == 0) { return null; } return [ From 0495e4162ebc73de4731524a03fcd1aa3763c351 Mon Sep 17 00:00:00 2001 From: Senthilkumar Muppidathi Date: Thu, 30 Oct 2025 09:49:31 +0530 Subject: [PATCH 8/9] #40209 - Adding Discounts to graphql response retriving from extension_attributes --- .../Model/Resolver/CartPrices.php | 22 ++++++++++++++++++- .../Unit/Model/Resolver/CartPricesTest.php | 19 +++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php index 5bafcc83dd09b..c8fdaba3c94fc 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php @@ -17,6 +17,7 @@ use Magento\Framework\Api\ExtensibleDataInterface; use Magento\Quote\Api\Data\TotalsInterface as QuoteTotalsInterface; use Magento\Quote\Api\Data\TotalsInterfaceFactory; +use Magento\Quote\Api\Data\TotalsExtensionInterfaceFactory; use Magento\Quote\Model\Quote; use Magento\Quote\Model\Quote\Address\Total; use Magento\Quote\Model\Cart\Totals as CartTotals; @@ -45,20 +46,29 @@ class CartPrices implements ResolverInterface */ private ScopeConfigInterface $scopeConfig; + /** + * @var TotalsExtensionInterfaceFactory + */ + private TotalsExtensionInterfaceFactory $totalsExtension; + /** * @param TotalsCollector $totalsCollector * @param TotalsInterfaceFactory $totalsFactory * @param DataObjectHelper $dataObjectHelper * @param ScopeConfigInterface|null $scopeConfig + * @param TotalsExtensionInterfaceFactory|null $totalsExtensionFactory */ public function __construct( TotalsCollector $totalsCollector, private TotalsInterfaceFactory $totalsFactory, private DataObjectHelper $dataObjectHelper, - ?ScopeConfigInterface $scopeConfig = null + ?ScopeConfigInterface $scopeConfig = null, + ?TotalsExtensionInterfaceFactory $totalsExtensionFactory = null ) { $this->totalsCollector = $totalsCollector; $this->scopeConfig = $scopeConfig ?? ObjectManager::getInstance()->get(ScopeConfigInterface::class); + $this->totalsExtension = $totalsExtensionFactory ?? + ObjectManager::getInstance()->create(TotalsExtensionInterfaceFactory::class); } /** @@ -76,6 +86,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, ?array $value if (!$quote->isVirtual() && $info->operation->operation == self::QUERY_TYPE) { $addressTotalsData = $quote->getShippingAddress()->getData(); + $extensionAttributes = $quote->getShippingAddress()->getExtensionAttributes()->__toArray(); unset($addressTotalsData[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]); $cartTotals = $this->totalsFactory->create(); $this->dataObjectHelper->populateWithArray( @@ -84,6 +95,15 @@ public function resolve(Field $field, $context, ResolveInfo $info, ?array $value QuoteTotalsInterface::class ); + if ($extensionAttributes) { + $this->dataObjectHelper->populateWithArray( + $this->totalsExtension, + $extensionAttributes, + \Magento\Quote\Api\Data\TotalsExtensionInterface::class + ); + $cartTotals->setExtensionAttributes($this->totalsExtension); + } + if (isset($addressTotalsData['discount_description'])) { $cartTotals->setDiscountDescription($addressTotalsData['discount_description']); } diff --git a/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php b/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php index 4792da6745b73..ef5d1aedfb831 100644 --- a/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php +++ b/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php @@ -15,6 +15,8 @@ use Magento\GraphQl\Model\Query\Context; use Magento\Quote\Api\Data\TotalsInterface; use Magento\Quote\Api\Data\TotalsInterfaceFactory; +use Magento\Quote\Api\Data\TotalsExtensionInterfaceFactory; +use Magento\Quote\Api\Data\TotalsExtensionInterface; use Magento\Quote\Model\Quote; use Magento\Quote\Model\Quote\Address; use Magento\Quote\Model\Quote\Address\Total; @@ -46,6 +48,11 @@ class CartPricesTest extends TestCase */ private ScopeConfigInterface $scopeConfigMock; + /** + * @var TotalsExtensionInterfaceFactory|MockObject + */ + private TotalsExtensionInterfaceFactory $totalExtensionMock + /** * @var Field|MockObject */ @@ -111,6 +118,7 @@ protected function setUp(): void ) ->getMock(); $this->scopeConfigMock = $this->createMock(ScopeConfigInterface::class); + $this->totalExtensionMock = $this->createMock(TotalsExtensionInterfaceFactory::class); $this->fieldMock = $this->createMock(Field::class); $this->resolveInfoMock = $this->createMock(ResolveInfo::class); $this->resolveInfoMock->operation = new OperationDefinitionNode([]); @@ -138,7 +146,8 @@ protected function setUp(): void $this->totalsCollectorMock, $this->totalsFactoryMock, $this->dataObjectHelperMock, - $this->scopeConfigMock + $this->scopeConfigMock, + $this->totalExtensionConfigMock ); } @@ -185,6 +194,14 @@ public function testResolveQuery(): void ->method('create') ->willReturn($this->totalMock); + $this->dataObjectHelperMock->expects($this->once()) + ->method('populateWithArray') + ->with( + $this->identicalTo($this->totalExtensionMock), + [], + TotalsExtensionInterface::class + ); + $this->resolve(); } From f4ee24a6b7be21e3526e5990cfb21a74e1342d67 Mon Sep 17 00:00:00 2001 From: Senthilkumar Muppidathi Date: Thu, 30 Oct 2025 15:54:25 +0530 Subject: [PATCH 9/9] #40209 - Made new params in constructor as optional and updated unit tests --- .../Model/Resolver/CartPrices.php | 29 +++++-- .../Unit/Model/Resolver/CartPricesTest.php | 86 +++++++++++++------ 2 files changed, 83 insertions(+), 32 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php index c8fdaba3c94fc..5833eafd3d2a9 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php @@ -46,6 +46,16 @@ class CartPrices implements ResolverInterface */ private ScopeConfigInterface $scopeConfig; + /** + * @var TotalsInterfaceFactory + */ + private TotalsInterfaceFactory $totalsFactory; + + /** + * @var DataObjectHelper + */ + private DataObjectHelper $dataObjectHelper; + /** * @var TotalsExtensionInterfaceFactory */ @@ -53,22 +63,26 @@ class CartPrices implements ResolverInterface /** * @param TotalsCollector $totalsCollector - * @param TotalsInterfaceFactory $totalsFactory - * @param DataObjectHelper $dataObjectHelper * @param ScopeConfigInterface|null $scopeConfig + * @param TotalsInterfaceFactory|null $totalsFactory + * @param DataObjectHelper|null $dataObjectHelper * @param TotalsExtensionInterfaceFactory|null $totalsExtensionFactory */ public function __construct( TotalsCollector $totalsCollector, - private TotalsInterfaceFactory $totalsFactory, - private DataObjectHelper $dataObjectHelper, ?ScopeConfigInterface $scopeConfig = null, + ?TotalsInterfaceFactory $totalsFactory = null, + ?DataObjectHelper $dataObjectHelper = null, ?TotalsExtensionInterfaceFactory $totalsExtensionFactory = null ) { $this->totalsCollector = $totalsCollector; $this->scopeConfig = $scopeConfig ?? ObjectManager::getInstance()->get(ScopeConfigInterface::class); + $this->totalsFactory = $totalsFactory ?? + ObjectManager::getInstance()->get(TotalsInterfaceFactory::class); + $this->dataObjectHelper = $dataObjectHelper ?? + ObjectManager::getInstance()->get(DataObjectHelper::class); $this->totalsExtension = $totalsExtensionFactory ?? - ObjectManager::getInstance()->create(TotalsExtensionInterfaceFactory::class); + ObjectManager::getInstance()->get(TotalsExtensionInterfaceFactory::class); } /** @@ -96,12 +110,13 @@ public function resolve(Field $field, $context, ResolveInfo $info, ?array $value ); if ($extensionAttributes) { + $newExtensionAttributes = $this->totalsExtension->create(); $this->dataObjectHelper->populateWithArray( - $this->totalsExtension, + $newExtensionAttributes, $extensionAttributes, \Magento\Quote\Api\Data\TotalsExtensionInterface::class ); - $cartTotals->setExtensionAttributes($this->totalsExtension); + $cartTotals->setExtensionAttributes($newExtensionAttributes); } if (isset($addressTotalsData['discount_description'])) { diff --git a/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php b/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php index ef5d1aedfb831..a0962437c7f2c 100644 --- a/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php +++ b/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Resolver/CartPricesTest.php @@ -17,6 +17,8 @@ use Magento\Quote\Api\Data\TotalsInterfaceFactory; use Magento\Quote\Api\Data\TotalsExtensionInterfaceFactory; use Magento\Quote\Api\Data\TotalsExtensionInterface; +use Magento\Quote\Api\Data\TotalsExtension; +use Magento\Quote\Api\Data\AddressExtension; use Magento\Quote\Model\Quote; use Magento\Quote\Model\Quote\Address; use Magento\Quote\Model\Quote\Address\Total; @@ -48,11 +50,6 @@ class CartPricesTest extends TestCase */ private ScopeConfigInterface $scopeConfigMock; - /** - * @var TotalsExtensionInterfaceFactory|MockObject - */ - private TotalsExtensionInterfaceFactory $totalExtensionMock - /** * @var Field|MockObject */ @@ -86,12 +83,27 @@ class CartPricesTest extends TestCase /** * @var TotalsInterfaceFactory|MockObject */ - private $totalsFactoryMock; + private TotalsInterfaceFactory $totalsFactoryMock; + + /** + * @var TotalsExtensionInterfaceFactory|MockObject + */ + private TotalsExtensionInterfaceFactory $totalExtensionFactoryMock; + + /** + * @var TotalsExtension|MockObject + */ + private TotalsExtension $totalExtensionMock; /** * @var Address|MockObject */ - private $shippingAddressMock; + private Address $shippingAddressMock; + + /** + * @var AddressExtension|MockObject + */ + private AddressExtension $addressExtensionMock; /** * @var array @@ -118,7 +130,8 @@ protected function setUp(): void ) ->getMock(); $this->scopeConfigMock = $this->createMock(ScopeConfigInterface::class); - $this->totalExtensionMock = $this->createMock(TotalsExtensionInterfaceFactory::class); + $this->totalExtensionFactoryMock = $this->createMock(TotalsExtensionInterfaceFactory::class); + $this->totalExtensionMock = $this->createMock(TotalsExtension::class); $this->fieldMock = $this->createMock(Field::class); $this->resolveInfoMock = $this->createMock(ResolveInfo::class); $this->resolveInfoMock->operation = new OperationDefinitionNode([]); @@ -138,16 +151,18 @@ protected function setUp(): void 'getDiscountTaxCompensationAmount', 'getDiscountAmount', 'getDiscountDescription', - 'getAppliedTaxes' + 'getAppliedTaxes', + 'setExtensionAttributes' ] ) ->getMock(); + $this->cartPrices = new CartPrices( $this->totalsCollectorMock, + $this->scopeConfigMock, $this->totalsFactoryMock, $this->dataObjectHelperMock, - $this->scopeConfigMock, - $this->totalExtensionConfigMock + $this->totalExtensionFactoryMock ); } @@ -161,16 +176,28 @@ public function testResolveWithoutModelInValueParameter(): void public function testResolveQuery(): void { $this->resolveInfoMock->operation->operation = 'query'; + $extAttributes = ['custom_field' => 'custom_value']; + + $this->addressExtensionMock = $this->createMock(AddressExtension::class); $this->shippingAddressMock = $this->getMockBuilder(Address::class) ->disableOriginalConstructor() - ->onlyMethods(['getData']) + ->onlyMethods(['getData', 'getExtensionAttributes']) ->getMock(); $this->shippingAddressMock->expects($this->any()) ->method('getData') ->willReturn([]); + $this->shippingAddressMock->expects($this->once()) + ->method('getExtensionAttributes') + ->willReturn($this->addressExtensionMock); + + $this->addressExtensionMock + ->expects($this->once()) + ->method('__toArray') + ->willReturn($extAttributes); + $this->quoteMock ->expects($this->once()) ->method('isVirtual') @@ -181,27 +208,36 @@ public function testResolveQuery(): void ->method('getShippingAddress') ->willReturn($this->shippingAddressMock); - $this->dataObjectHelperMock->expects($this->once()) - ->method('populateWithArray') - ->with( - $this->identicalTo($this->totalMock), - [], - TotalsInterface::class - ); - $this->totalsFactoryMock ->expects($this->once()) ->method('create') ->willReturn($this->totalMock); - $this->dataObjectHelperMock->expects($this->once()) + $this->totalExtensionFactoryMock + ->expects($this->once()) + ->method('create') + ->willReturn($this->totalExtensionMock); + + $this->totalMock->expects($this->once()) + ->method('setExtensionAttributes') + ->with($this->totalExtensionMock); + + $this->dataObjectHelperMock->expects($this->atLeastOnce()) ->method('populateWithArray') - ->with( - $this->identicalTo($this->totalExtensionMock), - [], - TotalsExtensionInterface::class + ->withConsecutive( + [ + $this->identicalTo($this->totalMock), + [], + $this->equalTo(TotalsInterface::class) + ], + [ + $this->identicalTo($this->totalExtensionMock), + $this->equalTo($extAttributes), + $this->equalTo(TotalsExtensionInterface::class) + ] ); + $this->resolve(); }