Skip to content

Commit 8f21537

Browse files
committed
ACP2E-4248: [GraphQl] cart query cart item discount issue on virtual quotes
1 parent e885088 commit 8f21537

File tree

4 files changed

+240
-6
lines changed

4 files changed

+240
-6
lines changed

app/code/Magento/QuoteGraphQl/Model/GetDiscounts.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ class GetDiscounts
2222
*/
2323
public function execute(Quote $quote, array $discounts): ?array
2424
{
25-
$discounts = $discounts ?: $quote->getBillingAddress()->getExtensionAttributes()->getDiscounts() ?? [];
26-
2725
if (empty($discounts)) {
2826
return null;
2927
}

app/code/Magento/QuoteGraphQl/Model/Resolver/Discounts.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2019 Adobe
4+
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
77

@@ -12,6 +12,7 @@
1212
use Magento\Framework\GraphQl\Query\ResolverInterface;
1313
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
1414
use Magento\QuoteGraphQl\Model\GetDiscounts;
15+
use Magento\Quote\Model\Quote;
1516

1617
/**
1718
* @inheritdoc
@@ -37,11 +38,16 @@ public function resolve(Field $field, $context, ResolveInfo $info, ?array $value
3738
if (!isset($value['model'])) {
3839
throw new LocalizedException(__('"model" value should be specified'));
3940
}
41+
/** @var Quote $quote */
4042
$quote = $value['model'];
41-
43+
if ($quote->getIsVirtual()) {
44+
$discounts = $quote->getBillingAddress()->getExtensionAttributes()->getDiscounts();
45+
} else {
46+
$discounts = $quote->getShippingAddress()->getExtensionAttributes()->getDiscounts();
47+
}
4248
return $this->getDiscounts->execute(
4349
$quote,
44-
$quote->getShippingAddress()->getExtensionAttributes()->getDiscounts() ?? []
50+
$discounts
4551
);
4652
}
4753
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\QuoteGraphQl\Test\Unit\Model;
9+
10+
use Magento\Quote\Model\Quote;
11+
use Magento\QuoteGraphQl\Model\GetDiscounts;
12+
use Magento\SalesRule\Api\Data\RuleDiscountInterface;
13+
use Magento\SalesRule\Api\Data\DiscountDataInterface;
14+
use PHPUnit\Framework\TestCase;
15+
use PHPUnit\Framework\MockObject\MockObject;
16+
17+
/**
18+
* Unit test for GetDiscounts class
19+
*/
20+
class GetDiscountsTest extends TestCase
21+
{
22+
/**
23+
* @var GetDiscounts
24+
*/
25+
private $getDiscounts;
26+
27+
/**
28+
* @var Quote|MockObject
29+
*/
30+
private $quoteMock;
31+
32+
protected function setUp(): void
33+
{
34+
$this->quoteMock = $this->getMockBuilder(Quote::class)
35+
->addMethods(['getQuoteCurrencyCode'])
36+
->disableOriginalConstructor()
37+
->getMock();
38+
$this->getDiscounts = new GetDiscounts();
39+
}
40+
41+
/**
42+
* Test execute method with empty discounts array
43+
*/
44+
public function testExecuteWithEmptyDiscounts(): void
45+
{
46+
$result = $this->getDiscounts->execute($this->quoteMock, []);
47+
$this->assertNull($result);
48+
}
49+
50+
/**
51+
* Test execute method with discounts
52+
*/
53+
public function testExecuteWithDiscountsAndRuleLabel(): void
54+
{
55+
$discountMock = $this->createMock(RuleDiscountInterface::class);
56+
$discountDataMock = $this->getMockBuilder(DiscountDataInterface::class)
57+
->addMethods(['getAppliedTo'])
58+
->onlyMethods(['getAmount', 'getBaseAmount', 'getOriginalAmount', 'getBaseOriginalAmount'])
59+
->getMock();
60+
$discountMock->method('getRuleLabel')->willReturn('Summer Sale');
61+
$discountMock->method('getDiscountData')->willReturn($discountDataMock);
62+
$discountDataMock->method('getAmount')->willReturn(10.50);
63+
$discountDataMock->method('getAppliedTo')->willReturn('TEST');
64+
$this->quoteMock->method('getQuoteCurrencyCode')->willReturn('USD');
65+
$discounts = [$discountMock];
66+
$expectedResult = [
67+
[
68+
'label' => 'Summer Sale',
69+
'applied_to' => 'TEST',
70+
'amount' => [
71+
'value' => 10.50,
72+
'currency' => 'USD'
73+
],
74+
'discount_model' => $discountMock,
75+
'quote_model' => $this->quoteMock
76+
]
77+
];
78+
$result = $this->getDiscounts->execute($this->quoteMock, $discounts);
79+
$this->assertEquals($expectedResult, $result);
80+
}
81+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\QuoteGraphQl\Test\Unit\Model\Resolver;
9+
10+
use Magento\Framework\GraphQl\Config\Element\Field;
11+
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
12+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
13+
use Magento\Framework\Exception\LocalizedException;
14+
use Magento\Quote\Model\Quote;
15+
use Magento\QuoteGraphQl\Model\GetDiscounts;
16+
use Magento\Quote\Api\Data\AddressExtensionInterface;
17+
use Magento\Quote\Model\Quote\Address;
18+
use Magento\QuoteGraphQl\Model\Resolver\Discounts;
19+
use PHPUnit\Framework\TestCase;
20+
use PHPUnit\Framework\MockObject\MockObject;
21+
22+
/**
23+
* Unit test for Discounts resolver
24+
*/
25+
class DiscountsTest extends TestCase
26+
{
27+
/**
28+
* @var GetDiscounts|MockObject
29+
*/
30+
private $getDiscountsMock;
31+
32+
/**
33+
* @var Discounts
34+
*/
35+
private $discountsResolver;
36+
37+
/**
38+
* @var Field|MockObject
39+
*/
40+
private $fieldMock;
41+
42+
/**
43+
* @var ResolveInfo|MockObject
44+
*/
45+
private $resolveInfoMock;
46+
47+
/**
48+
* @var ContextInterface|MockObject
49+
*/
50+
private $contextMock;
51+
52+
/**
53+
* @var Quote|MockObject
54+
*/
55+
private $quoteMock;
56+
57+
/**
58+
* @var Address|MockObject
59+
*/
60+
private $billingAddressMock;
61+
62+
/**
63+
* @var Address|MockObject
64+
*/
65+
private $shippingAddressMock;
66+
67+
/**
68+
* @var AddressExtensionInterface|MockObject
69+
*/
70+
private $extensionAttributesMock;
71+
72+
protected function setUp(): void
73+
{
74+
$this->getDiscountsMock = $this->createMock(GetDiscounts::class);
75+
$this->discountsResolver = new Discounts($this->getDiscountsMock);
76+
$this->fieldMock = $this->createMock(Field::class);
77+
$this->resolveInfoMock = $this->createMock(ResolveInfo::class);
78+
$this->contextMock = $this->createMock(ContextInterface::class);
79+
$this->quoteMock = $this->createMock(Quote::class);
80+
$this->billingAddressMock = $this->createMock(Address::class);
81+
$this->shippingAddressMock = $this->createMock(Address::class);
82+
$this->extensionAttributesMock = $this->createMock(AddressExtensionInterface::class);
83+
$this->extensionAttributesMock->method('getDiscounts')->willReturn(['discount1', 'discount2']);
84+
}
85+
86+
/**
87+
* Test resolve method when model is not provided
88+
*/
89+
public function testResolveThrowsExceptionWhenModelIsMissing(): void
90+
{
91+
$this->expectException(LocalizedException::class);
92+
$this->expectExceptionMessage('"model" value should be specified');
93+
$this->discountsResolver->resolve($this->fieldMock, $this->contextMock, $this->resolveInfoMock);
94+
}
95+
96+
/**
97+
* Test resolve method for virtual quote
98+
*/
99+
public function testResolveForVirtualQuote(): void
100+
{
101+
$this->quoteMock->method('getIsVirtual')->willReturn(true);
102+
$this->quoteMock->method('getBillingAddress')->willReturn($this->billingAddressMock);
103+
$this->billingAddressMock->method('getExtensionAttributes')->willReturn($this->extensionAttributesMock);
104+
105+
$discounts = ['discount1', 'discount2'];
106+
$expectedResult = ['formatted_discounts'];
107+
$this->getDiscountsMock->expects($this->once())
108+
->method('execute')
109+
->with($this->quoteMock, $discounts)
110+
->willReturn($expectedResult);
111+
112+
$result = $this->discountsResolver->resolve(
113+
$this->fieldMock,
114+
$this->contextMock,
115+
$this->resolveInfoMock,
116+
['model' => $this->quoteMock],
117+
null
118+
);
119+
120+
$this->assertEquals($expectedResult, $result);
121+
}
122+
123+
/**
124+
* Test resolve method for non-virtual quote
125+
*/
126+
public function testResolveForNonVirtualQuote(): void
127+
{
128+
$this->quoteMock->method('getIsVirtual')->willReturn(false);
129+
$this->quoteMock->method('getShippingAddress')->willReturn($this->shippingAddressMock);
130+
$this->shippingAddressMock->method('getExtensionAttributes')->willReturn($this->extensionAttributesMock);
131+
132+
$discounts = ['discount1', 'discount2'];
133+
$expectedResult = ['formatted_discounts'];
134+
$this->getDiscountsMock->expects($this->once())
135+
->method('execute')
136+
->with($this->quoteMock, $discounts)
137+
->willReturn($expectedResult);
138+
139+
$result = $this->discountsResolver->resolve(
140+
$this->fieldMock,
141+
$this->contextMock,
142+
$this->resolveInfoMock,
143+
['model' => $this->quoteMock],
144+
null
145+
);
146+
147+
$this->assertEquals($expectedResult, $result);
148+
}
149+
}

0 commit comments

Comments
 (0)