Skip to content

Commit 3ca04a5

Browse files
committed
Merge branch 'ACP2E-3211' of https://github.com/adobe-commerce-tier-4/magento2ce into PR-08-29-2024
2 parents 25d77b8 + 86d8bc7 commit 3ca04a5

File tree

7 files changed

+607
-11
lines changed

7 files changed

+607
-11
lines changed

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

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
*/
77
namespace Magento\Checkout\Controller\Cart;
88

9-
use Magento\Checkout\Model\Cart\RequestQuantityProcessor;
10-
use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
119
use Magento\Catalog\Api\ProductRepositoryInterface;
10+
use Magento\Checkout\Model\AddProductToCart;
1211
use Magento\Checkout\Model\Cart as CustomerCart;
12+
use Magento\Checkout\Model\Cart\RequestQuantityProcessor;
13+
use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
1314
use Magento\Framework\App\ObjectManager;
1415
use Magento\Framework\App\ResponseInterface;
1516
use Magento\Framework\Controller\ResultInterface;
@@ -33,6 +34,11 @@ class Add extends \Magento\Checkout\Controller\Cart implements HttpPostActionInt
3334
*/
3435
private $quantityProcessor;
3536

37+
/**
38+
* @var AddProductToCart
39+
*/
40+
private AddProductToCart $addProductToCart;
41+
3642
/**
3743
* @param \Magento\Framework\App\Action\Context $context
3844
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
@@ -42,6 +48,7 @@ class Add extends \Magento\Checkout\Controller\Cart implements HttpPostActionInt
4248
* @param CustomerCart $cart
4349
* @param ProductRepositoryInterface $productRepository
4450
* @param RequestQuantityProcessor|null $quantityProcessor
51+
* @param AddProductToCart|null $addProductToCart
4552
* @codeCoverageIgnore
4653
*/
4754
public function __construct(
@@ -52,7 +59,8 @@ public function __construct(
5259
\Magento\Framework\Data\Form\FormKey\Validator $formKeyValidator,
5360
CustomerCart $cart,
5461
ProductRepositoryInterface $productRepository,
55-
?RequestQuantityProcessor $quantityProcessor = null
62+
?RequestQuantityProcessor $quantityProcessor = null,
63+
?AddProductToCart $addProductToCart = null
5664
) {
5765
parent::__construct(
5866
$context,
@@ -65,6 +73,8 @@ public function __construct(
6573
$this->productRepository = $productRepository;
6674
$this->quantityProcessor = $quantityProcessor
6775
?? ObjectManager::getInstance()->get(RequestQuantityProcessor::class);
76+
$this->addProductToCart = $addProductToCart
77+
?? ObjectManager::getInstance()->get(AddProductToCart::class);
6878
}
6979

7080
/**
@@ -123,11 +133,7 @@ public function execute()
123133
return $this->goBack();
124134
}
125135

126-
$this->cart->addProduct($product, $params);
127-
if (!empty($related)) {
128-
$this->cart->addProductsByIds(explode(',', $related));
129-
}
130-
$this->cart->save();
136+
$this->addProductToCart->execute($this->cart, $product, $params, $related ? explode(',', $related) : []);
131137

132138
/**
133139
* @todo remove wishlist observer \Magento\Wishlist\Observer\AddToCart
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
/**
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Checkout\Model;
9+
10+
use Magento\Catalog\Model\Product;
11+
use Magento\Framework\Stdlib\DateTime\DateTime;
12+
use Magento\Quote\Api\CartRepositoryInterfaceFactory;
13+
use Magento\Quote\Api\Data\CartInterface;
14+
use Magento\Quote\Model\QuoteMutexInterface;
15+
16+
class AddProductToCart
17+
{
18+
/**
19+
* @param QuoteMutexInterface $quoteMutex
20+
* @param CartRepositoryInterfaceFactory $quoteRepositoryFactory
21+
* @param DateTime $dateTime
22+
*/
23+
public function __construct(
24+
private readonly QuoteMutexInterface $quoteMutex,
25+
private readonly CartRepositoryInterfaceFactory $quoteRepositoryFactory,
26+
private readonly DateTime $dateTime
27+
) {
28+
}
29+
30+
/**
31+
* Add product to cart
32+
*
33+
* @param Cart $cart
34+
* @param Product $product
35+
* @param array $buyRequest Buy request info
36+
* @param array $related Product IDs to add to the cart along with the main product
37+
* @return bool
38+
* @throws \Throwable
39+
*/
40+
public function execute(Cart $cart, Product $product, array $buyRequest = [], array $related = []): bool
41+
{
42+
if (!$cart->getQuote()->getId()) {
43+
return $this->add($cart, $product, $buyRequest, $related);
44+
}
45+
46+
return $this->quoteMutex->execute(
47+
[(int) $cart->getQuote()->getId()],
48+
function (array $quotes = []) use ($cart, $product, $buyRequest, $related) {
49+
$reload = true;
50+
// check if the mutex provided the quote
51+
if (!empty($quotes)) {
52+
// check if the quote was updated since the last load
53+
// if not, we can use the quote in memory to avoid full reload which is expensive and unnecessary
54+
$lastUpdatedAt = $cart->getQuote()->getUpdatedAt()
55+
?: $cart->getQuote()->getOrigData(CartInterface::KEY_UPDATED_AT);
56+
$quote = current($quotes);
57+
$updatedAt = $quote->getUpdatedAt();
58+
$reload = $updatedAt
59+
&& $lastUpdatedAt
60+
&& $this->dateTime->timestamp($updatedAt) > $this->dateTime->timestamp($lastUpdatedAt);
61+
}
62+
if ($reload) {
63+
// bypass repository cache by creating a new repository instead of using the shared repository
64+
$quote = $this->quoteRepositoryFactory->create()->getActive($cart->getQuote()->getId());
65+
$cart->setQuote($quote);
66+
$cart->getCheckoutSession()->replaceQuote($quote);
67+
}
68+
return $this->add($cart, $product, $buyRequest, $related);
69+
},
70+
);
71+
}
72+
73+
/**
74+
* Add product to cart
75+
*
76+
* @param Cart $cart
77+
* @param Product $product
78+
* @param array $buyRequest
79+
* @param array $related
80+
* @return bool
81+
*/
82+
private function add(Cart $cart, Product $product, array $buyRequest, array $related = []): bool
83+
{
84+
$cart->addProduct($product, $buyRequest);
85+
if (!empty($related)) {
86+
$cart->addProductsByIds($related);
87+
}
88+
$cart->save();
89+
return true;
90+
}
91+
}

app/code/Magento/Checkout/Test/Unit/Controller/Cart/AddTest.php

Lines changed: 119 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,31 @@
77

88
namespace Magento\Checkout\Test\Unit\Controller\Cart;
99

10+
use Magento\Catalog\Api\ProductRepositoryInterface;
11+
use Magento\Catalog\Model\Product;
1012
use Magento\Checkout\Controller\Cart\Add;
13+
use Magento\Checkout\Model\AddProductToCart;
14+
use Magento\Checkout\Model\Cart;
15+
use Magento\Checkout\Model\Cart\RequestQuantityProcessor;
16+
use Magento\Framework\App\Request\Http;
1117
use Magento\Framework\App\RequestInterface;
1218
use Magento\Framework\Controller\Result\Redirect;
1319
use Magento\Framework\Controller\Result\RedirectFactory;
1420
use Magento\Framework\Data\Form\FormKey\Validator;
21+
use Magento\Framework\Json\Helper\Data as JsonSerializer;
22+
use Magento\Framework\Locale\ResolverInterface;
1523
use Magento\Framework\Message\ManagerInterface;
24+
use Magento\Framework\ObjectManagerInterface;
1625
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
26+
use Magento\Quote\Model\Quote;
27+
use Magento\Store\Model\Store;
28+
use Magento\Store\Model\StoreManagerInterface;
1729
use PHPUnit\Framework\MockObject\MockObject;
1830
use PHPUnit\Framework\TestCase;
1931

32+
/**
33+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
34+
*/
2035
class AddTest extends TestCase
2136
{
2237
/**
@@ -44,6 +59,36 @@ class AddTest extends TestCase
4459
*/
4560
private $messageManager;
4661

62+
/**
63+
* @var ProductRepositoryInterface&MockObject
64+
*/
65+
private $productRepository;
66+
67+
/**
68+
* @var ObjectManagerInterface&MockObject
69+
*/
70+
private $objectManagerMock;
71+
72+
/**
73+
* @var RequestQuantityProcessor&MockObject
74+
*/
75+
private $quantityProcessor;
76+
77+
/**
78+
* @var AddProductToCart&MockObject
79+
*/
80+
private $addProductToCart;
81+
82+
/**
83+
* @var Cart&MockObject
84+
*/
85+
private $cart;
86+
87+
/**
88+
* @var \Magento\Framework\App\Response\Http&MockObject
89+
*/
90+
private $response;
91+
4792
/**
4893
* @var Add|MockObject
4994
*/
@@ -63,21 +108,34 @@ protected function setUp(): void
63108
$this->getMockBuilder(RedirectFactory::class)
64109
->disableOriginalConstructor()
65110
->getMock();
66-
$this->request = $this->getMockBuilder(RequestInterface::class)
111+
$this->request = $this->getMockBuilder(Http::class)
67112
->disableOriginalConstructor()
68113
->getmock();
69114
$this->messageManager = $this->getMockBuilder(ManagerInterface::class)
70115
->disableOriginalConstructor()
71116
->getMockForAbstractClass();
72117

118+
$this->productRepository = $this->createMock(ProductRepositoryInterface::class);
119+
$this->objectManagerMock = $this->createMock(ObjectManagerInterface::class);
120+
$this->quantityProcessor = $this->createMock(RequestQuantityProcessor::class);
121+
$this->addProductToCart = $this->createMock(AddProductToCart::class);
122+
$this->cart = $this->createMock(Cart::class);
123+
$this->response = $this->createMock(\Magento\Framework\App\Response\Http::class);
124+
73125
$this->objectManagerHelper = new ObjectManagerHelper($this);
74126
$this->cartAdd = $this->objectManagerHelper->getObject(
75127
Add::class,
76128
[
77129
'_formKeyValidator' => $this->formKeyValidator,
78130
'resultRedirectFactory' => $this->resultRedirectFactory,
79131
'_request' => $this->request,
80-
'messageManager' => $this->messageManager
132+
'messageManager' => $this->messageManager,
133+
'productRepository' => $this->productRepository,
134+
'_objectManager' => $this->objectManagerMock,
135+
'quantityProcessor' => $this->quantityProcessor,
136+
'addProductToCart' => $this->addProductToCart,
137+
'cart' => $this->cart,
138+
'_response' => $this->response
81139
]
82140
);
83141
}
@@ -87,7 +145,7 @@ protected function setUp(): void
87145
*
88146
* @return void
89147
*/
90-
public function testExecute()
148+
public function testExecuteWhenFormKeyValidatorFails(): void
91149
{
92150
$redirect = $this->getMockBuilder(Redirect::class)
93151
->disableOriginalConstructor()
@@ -100,4 +158,62 @@ public function testExecute()
100158
$redirect->expects($this->once())->method('setPath')->with($path)->willReturnSelf();
101159
$this->assertEquals($redirect, $this->cartAdd->execute());
102160
}
161+
162+
public function testExecuteWithValidData(): void
163+
{
164+
$productId = 1;
165+
$storeId = 1;
166+
$params = ['qty' => 1];
167+
$product = $this->createMock(Product::class);
168+
$quote = $this->createMock(Quote::class);
169+
$storeManager = $this->createMock(StoreManagerInterface::class);
170+
$store = $this->createMock(Store::class);
171+
$localeResolver = $this->createMock(ResolverInterface::class);
172+
$storeManager->expects($this->once())
173+
->method('getStore')
174+
->willReturn($store);
175+
$store->expects($this->once())
176+
->method('getId')
177+
->willReturn($storeId);
178+
$this->request->method('getParam')
179+
->willReturnMap([
180+
['product', null, $productId],
181+
['related_product', null, '2,3'],
182+
['return_url', null, '/sku.html']
183+
]);
184+
$this->request->expects($this->once())
185+
->method('getParams')
186+
->willReturn($params);
187+
$this->request->expects($this->once())
188+
->method('isAjax')
189+
->willReturn(true);
190+
$this->productRepository->expects($this->once())
191+
->method('getById')
192+
->with($productId, false, $storeId)
193+
->willReturn($product);
194+
$this->objectManagerMock->method('get')
195+
->with()
196+
->willReturnMap([
197+
[StoreManagerInterface::class, $storeManager],
198+
[ResolverInterface::class, $localeResolver],
199+
[JsonSerializer::class, $this->createMock(JsonSerializer::class)],
200+
]);
201+
$this->addProductToCart->expects($this->once())
202+
->method('execute')
203+
->with($this->cart, $product, $params, [2, 3])
204+
->willReturn(true);
205+
$this->quantityProcessor->expects($this->once())
206+
->method('prepareQuantity')
207+
->with($params['qty'])
208+
->willReturn($params['qty']);
209+
$this->cart->expects($this->once())
210+
->method('getQuote')
211+
->willReturn($quote);
212+
$this->formKeyValidator->expects($this->once())
213+
->method('validate')
214+
->with($this->request)
215+
->willReturn(true);
216+
217+
$this->assertEquals($this->response, $this->cartAdd->execute());
218+
}
103219
}

0 commit comments

Comments
 (0)