Skip to content

Commit 988c928

Browse files
committed
Merge remote-tracking branch 'origin/ACP2E-4283' into PR_2025_11_04_flowers
2 parents dcaa30e + 2041752 commit 988c928

File tree

3 files changed

+172
-2
lines changed

3 files changed

+172
-2
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\QuoteGraphQl\Observer;
9+
10+
use Magento\Framework\Event\Observer;
11+
use Magento\Framework\Event\ObserverInterface;
12+
13+
class CheckAndUpdateQtyObserver implements ObserverInterface
14+
{
15+
/**
16+
* Check and update the item qty in case of error
17+
*
18+
* @param Observer $observer
19+
* @return void
20+
*/
21+
public function execute(Observer $observer):void
22+
{
23+
$item = $observer->getEvent()->getItem();
24+
25+
if ($item && $item->getHasError()) {
26+
$item->setUseOldQty(true);
27+
$item->getQuote()->setHasError(true);
28+
}
29+
}
30+
}

app/code/Magento/QuoteGraphQl/etc/graphql/events.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,7 @@
1010
<observer name="sendEmail" instance="Magento\Quote\Observer\SubmitObserver"/>
1111
<observer name="sendInvoiceEmail" instance="Magento\Quote\Observer\SendInvoiceEmailObserver"/>
1212
</event>
13+
<event name="sales_quote_item_qty_set_after">
14+
<observer name="checkAndUpdateQty" instance="Magento\QuoteGraphQl\Observer\CheckAndUpdateQtyObserver"/>
15+
</event>
1316
</config>

dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/UpdateCartItemsTest.php

Lines changed: 139 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
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

88
namespace Magento\GraphQl\Quote\Customer;
99

1010
use Magento\Catalog\Api\ProductRepositoryInterface;
11+
use Magento\Catalog\Test\Fixture\Product as ProductFixture;
12+
use Magento\Checkout\Test\Fixture\SetShippingAddress as SetShippingAddressFixture;
13+
use Magento\Customer\Test\Fixture\Customer as CustomerFixture;
14+
use Magento\Framework\Exception\NoSuchEntityException;
1115
use Magento\Integration\Api\CustomerTokenServiceInterface;
1216
use Magento\Quote\Model\QuoteFactory;
1317
use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface;
1418
use Magento\Quote\Model\ResourceModel\Quote as QuoteResource;
19+
use Magento\Quote\Test\Fixture\AddProductToCart as AddProductToCartFixture;
20+
use Magento\Quote\Test\Fixture\CustomerCart as CustomerCartFixture;
21+
use Magento\Quote\Test\Fixture\QuoteIdMask;
22+
use Magento\TestFramework\Fixture\DataFixture;
23+
use Magento\TestFramework\Fixture\DataFixtureStorage;
24+
use Magento\TestFramework\Fixture\DataFixtureStorageManager;
1525
use Magento\TestFramework\Helper\Bootstrap;
1626
use Magento\TestFramework\TestCase\GraphQlAbstract;
1727

@@ -45,6 +55,11 @@ class UpdateCartItemsTest extends GraphQlAbstract
4555
*/
4656
private $productRepository;
4757

58+
/**
59+
* @var DataFixtureStorage
60+
*/
61+
private $fixtures;
62+
4863
protected function setUp(): void
4964
{
5065
$objectManager = Bootstrap::getObjectManager();
@@ -53,6 +68,7 @@ protected function setUp(): void
5368
$this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
5469
$this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
5570
$this->productRepository = $objectManager->get(ProductRepositoryInterface::class);
71+
$this->fixtures = DataFixtureStorageManager::getStorage();
5672
}
5773

5874
/**
@@ -136,6 +152,126 @@ public function testUpdateNonExistentItem()
136152
$this->assertEquals('COULD_NOT_FIND_CART_ITEM', $responseError['code']);
137153
}
138154

155+
/**
156+
* Test and check update with not enough quantity exception
157+
*/
158+
#[
159+
DataFixture(ProductFixture::class, as: 'product'),
160+
DataFixture(CustomerFixture::class, ['email' => 'customer@example.com'], as: 'customer'),
161+
DataFixture(CustomerCartFixture::class, ['customer_id' => '$customer.id$'], as: 'cart'),
162+
DataFixture(
163+
AddProductToCartFixture::class,
164+
['cart_id' => '$cart.id$', 'product_id' => '$product.id$', 'qty' => 100]
165+
),
166+
DataFixture(SetShippingAddressFixture::class, ['cart_id' => '$cart.id$']),
167+
DataFixture(QuoteIdMask::class, ['cart_id' => '$cart.id$'], 'quoteIdMask'),
168+
]
169+
public function testUpdateWithNotEnoughQuantityException()
170+
{
171+
$productSku = $this->fixtures->get('product')->getSku();
172+
$maskedQuoteId = $this->fixtures->get('quoteIdMask')->getMaskedId();
173+
$query = $this->getCartQuery($maskedQuoteId);
174+
$cartResponse = $this->graphQlQuery($query, [], '', $this->getHeaderMap());
175+
176+
$this->assertArrayHasKey('cart', $cartResponse);
177+
$this->assertArrayHasKey('itemsV2', $cartResponse['cart']);
178+
$items = $cartResponse['cart']['itemsV2']['items'];
179+
$itemId = $items[0]['uid'];
180+
$this->assertNotEmpty($itemId);
181+
182+
$updateCartItemsMutation = $this->updateCartItemsMutation(
183+
$maskedQuoteId,
184+
$itemId,
185+
1000
186+
);
187+
$updatedCartResponse = $this->graphQlMutation($updateCartItemsMutation, [], '', $this->getHeaderMap());
188+
$this->assertArrayHasKey('errors', $updatedCartResponse['updateCartItems']);
189+
190+
$responseError = $updatedCartResponse['updateCartItems']['errors'][0];
191+
192+
$this->assertEquals('INSUFFICIENT_STOCK', $responseError['code']);
193+
$this->assertEquals(
194+
"Could not update the product with SKU {$productSku}: Not enough items for sale",
195+
$responseError['message']
196+
);
197+
}
198+
199+
/**
200+
* Generates GraphQl query for retrieving cart items prices [original_item_price & original_row_total]
201+
*
202+
* @param string $customer_cart_id
203+
* @return string
204+
*/
205+
private function getCartQuery(string $customer_cart_id): string
206+
{
207+
return <<<QUERY
208+
{
209+
cart(cart_id: "$customer_cart_id") {
210+
itemsV2 {
211+
total_count
212+
items {
213+
uid
214+
product {
215+
name
216+
sku
217+
}
218+
quantity
219+
}
220+
}
221+
}
222+
}
223+
QUERY;
224+
}
225+
226+
/**
227+
* Generate GraphQL mutation for updating product to cart
228+
*
229+
* @param string $cartId
230+
* @param string $cartItemId
231+
* @param int $qty
232+
* @return string
233+
*/
234+
private function updateCartItemsMutation(string $cartId, string $cartItemId, int $qty = 1): string
235+
{
236+
return <<<MUTATION
237+
mutation{
238+
updateCartItems(
239+
input: {
240+
cart_id: "{$cartId}",
241+
cart_items: [
242+
{
243+
cart_item_uid: "{$cartItemId}"
244+
quantity: {$qty}
245+
}
246+
]
247+
}
248+
) {
249+
cart {
250+
itemsV2 {
251+
items {
252+
product {
253+
name
254+
}
255+
quantity
256+
}
257+
}
258+
prices {
259+
grand_total{
260+
value
261+
currency
262+
}
263+
}
264+
}
265+
errors {
266+
code
267+
message
268+
}
269+
270+
}
271+
}
272+
MUTATION;
273+
}
274+
139275
/**
140276
* @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
141277
* @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php
@@ -232,6 +368,7 @@ public function testUpdateItemInAnotherCustomerCart()
232368
* @param string $errorCode
233369
* @dataProvider dataProviderUpdateWithMissedRequiredParameters
234370
* @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
371+
* @throws NoSuchEntityException
235372
*/
236373
public function testUpdateWithMissedItemRequiredParameters(string $input, string $message, string $errorCode)
237374
{

0 commit comments

Comments
 (0)