Skip to content

Commit bcabea1

Browse files
committed
Merge remote-tracking branch 'origin/ACP2E-4192-Alternative' into PR_2025_10_01_flowers
2 parents e0991ff + 179c87a commit bcabea1

File tree

2 files changed

+211
-3
lines changed

2 files changed

+211
-3
lines changed

app/code/Magento/Checkout/Controller/Cart/UpdatePost.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ protected function _updateShoppingCart()
8484
}
8585
} catch (\Magento\Framework\Exception\LocalizedException $e) {
8686
$this->cart->save();
87-
$this->messageManager->addErrorMessage(
88-
$this->_objectManager->get(\Magento\Framework\Escaper::class)->escapeHtml($e->getMessage())
89-
);
87+
// We do not add an error message to messageManager here to prevent duplicate error messages
88+
// on the cart page. The frontend AJAX validation already displays this error, and the backend message
89+
// would cause duplication after page reload.
9090
} catch (\Exception $e) {
9191
$this->messageManager->addExceptionMessage($e, __('We can\'t update the shopping cart.'));
9292
$this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e);
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Checkout\Test\Unit\Controller\Cart;
9+
10+
use Magento\Checkout\Controller\Cart\UpdatePost;
11+
use Magento\Checkout\Model\Cart;
12+
use Magento\Checkout\Model\Cart\RequestQuantityProcessor;
13+
use Magento\Checkout\Model\Session as CheckoutSession;
14+
use Magento\Customer\Model\Session as CustomerSession;
15+
use Magento\Framework\App\Action\Context;
16+
use Magento\Framework\App\Config\ScopeConfigInterface;
17+
use Magento\Framework\App\RequestInterface;
18+
use Magento\Framework\App\Response\RedirectInterface;
19+
use Magento\Framework\Controller\Result\Redirect;
20+
use Magento\Framework\Controller\Result\RedirectFactory;
21+
use Magento\Framework\Data\Form\FormKey\Validator as FormKeyValidator;
22+
use Magento\Framework\Escaper;
23+
use Magento\Framework\Exception\LocalizedException;
24+
use Magento\Framework\Message\ManagerInterface;
25+
use Magento\Framework\ObjectManagerInterface;
26+
use Magento\Quote\Model\Quote;
27+
use Magento\Store\Model\StoreManagerInterface;
28+
use PHPUnit\Framework\MockObject\MockObject;
29+
use PHPUnit\Framework\TestCase;
30+
31+
/**
32+
* Unit test for UpdatePost controller
33+
*
34+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
35+
*/
36+
class UpdatePostTest extends TestCase
37+
{
38+
/**
39+
* @var UpdatePost
40+
*/
41+
private $controller;
42+
43+
/**
44+
* @var RequestInterface|MockObject
45+
*/
46+
private $requestMock;
47+
48+
/**
49+
* @var ManagerInterface|MockObject
50+
*/
51+
private $messageManagerMock;
52+
53+
/**
54+
* @var RequestQuantityProcessor|MockObject
55+
*/
56+
private $quantityProcessorMock;
57+
58+
/**
59+
* @var Cart|MockObject
60+
*/
61+
private $cartMock;
62+
63+
/**
64+
* @var CustomerSession|MockObject
65+
*/
66+
private $customerSessionMock;
67+
68+
/**
69+
* @var Escaper|MockObject
70+
*/
71+
private $escaperMock;
72+
73+
/**
74+
* @var ObjectManagerInterface|MockObject
75+
*/
76+
private $objectManagerMock;
77+
78+
/**
79+
* @var RedirectInterface|MockObject
80+
*/
81+
private $redirectMock;
82+
83+
/**
84+
* @inheritDoc
85+
* @throws \ReflectionException
86+
*/
87+
protected function setUp(): void
88+
{
89+
// Create mocks
90+
$this->requestMock = $this->getMockForAbstractClass(RequestInterface::class);
91+
$this->messageManagerMock = $this->getMockForAbstractClass(ManagerInterface::class);
92+
$this->quantityProcessorMock = $this->createMock(RequestQuantityProcessor::class);
93+
$this->cartMock = $this->createMock(Cart::class);
94+
$this->customerSessionMock = $this->createMock(CustomerSession::class);
95+
$this->escaperMock = $this->createMock(Escaper::class);
96+
$this->objectManagerMock = $this->createMock(ObjectManagerInterface::class);
97+
$this->redirectMock = $this->createMock(RedirectInterface::class);
98+
99+
// Setup context mock
100+
$contextMock = $this->createMock(Context::class);
101+
$contextMock->method('getRequest')->willReturn($this->requestMock);
102+
$contextMock->method('getMessageManager')->willReturn($this->messageManagerMock);
103+
104+
// Setup Escaper mock
105+
$this->escaperMock->method('escapeHtml')->willReturn('Test error message');
106+
$this->objectManagerMock->method('get')
107+
->with(Escaper::class)
108+
->willReturn($this->escaperMock);
109+
$this->redirectMock->method('getRefererUrl')->willReturn('http://example.com/cart');
110+
111+
$defaultFormKeyValidator = $this->createMock(FormKeyValidator::class);
112+
$defaultFormKeyValidator->method('validate')->willReturn(true);
113+
114+
$this->controller = $this->createController($contextMock, $defaultFormKeyValidator);
115+
}
116+
117+
/**
118+
* Create controller instance with required dependencies injected
119+
*
120+
* @param Context $context
121+
* @param FormKeyValidator $formKeyValidator
122+
* @param RedirectFactory|null $redirectFactory
123+
* @return UpdatePost
124+
* @throws \ReflectionException
125+
*/
126+
private function createController(
127+
Context $context,
128+
FormKeyValidator $formKeyValidator,
129+
?RedirectFactory $redirectFactory = null
130+
): UpdatePost {
131+
if ($redirectFactory === null) {
132+
$redirectFactory = $this->createMock(RedirectFactory::class);
133+
$redirectFactory->method('create')->willReturn($this->createMock(Redirect::class));
134+
}
135+
136+
$context->method('getResultRedirectFactory')->willReturn($redirectFactory);
137+
138+
$controller = new UpdatePost(
139+
$context,
140+
$this->getMockForAbstractClass(ScopeConfigInterface::class),
141+
$this->createMock(CheckoutSession::class),
142+
$this->getMockForAbstractClass(StoreManagerInterface::class),
143+
$formKeyValidator,
144+
$this->cartMock,
145+
$this->quantityProcessorMock
146+
);
147+
148+
$reflection = new \ReflectionClass($controller);
149+
$parent = $reflection->getParentClass();
150+
151+
$objectManagerProperty = $parent->getProperty('_objectManager');
152+
$objectManagerProperty->setValue($controller, $this->objectManagerMock);
153+
154+
$redirectProperty = $parent->getProperty('_redirect');
155+
$redirectProperty->setValue($controller, $this->redirectMock);
156+
157+
return $controller;
158+
}
159+
160+
/**
161+
* Test that messageManager->addErrorMessage() is never called in the catch block
162+
*
163+
* @return void
164+
* @throws \ReflectionException
165+
*/
166+
public function testCatchBlockDoesNotAddErrorMessage(): void
167+
{
168+
// Setup request to trigger update_qty action and return cart data
169+
$this->requestMock->method('getParam')
170+
->willReturnMap([
171+
['update_cart_action', null, 'update_qty'],
172+
['cart', null, ['1' => ['qty' => 10]]]
173+
]);
174+
175+
// Setup form key validation to pass
176+
$formKeyValidatorMock = $this->createMock(FormKeyValidator::class);
177+
$formKeyValidatorMock->method('validate')->willReturn(true);
178+
179+
// Recreate controller with test-specific dependencies
180+
$contextMock = $this->createMock(Context::class);
181+
$contextMock->method('getRequest')->willReturn($this->requestMock);
182+
$contextMock->method('getMessageManager')->willReturn($this->messageManagerMock);
183+
184+
$this->controller = $this->createController($contextMock, $formKeyValidatorMock);
185+
186+
// Setup quantity processor to throw LocalizedException
187+
$this->quantityProcessorMock->method('process')
188+
->willThrowException(new LocalizedException(__('Test error message')));
189+
190+
// Setup cart methods
191+
$quoteMock = $this->createMock(Quote::class);
192+
$this->cartMock->method('getCustomerSession')->willReturn($this->customerSessionMock);
193+
$this->cartMock->method('getQuote')->willReturn($quoteMock);
194+
$this->cartMock->method('suggestItemsQty')->willReturn(['1' => ['qty' => 10]]);
195+
$this->cartMock->method('updateItems')->willReturnSelf();
196+
$this->cartMock->method('save')->willReturnSelf();
197+
198+
// Setup customer session mock
199+
$this->customerSessionMock->method('getCustomerId')->willReturn(null);
200+
201+
// Verify that messageManager->addErrorMessage() is NEVER called
202+
$this->messageManagerMock->expects($this->never())
203+
->method('addErrorMessage');
204+
205+
// Call the public execute() method - this will trigger the _updateShoppingCart logic
206+
$this->controller->execute();
207+
}
208+
}

0 commit comments

Comments
 (0)