Skip to content

Commit 91348c1

Browse files
committed
Tests for forgetCart cookie-restore regression
1 parent b7ec605 commit 91348c1

1 file changed

Lines changed: 88 additions & 0 deletions

File tree

tests/unit/services/CartsTest.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use craft\commerce\Plugin;
1414
use craft\commerce\services\Carts;
1515
use craft\commerce\services\Stores;
16+
use craft\web\Request;
1617
use craftcommercetests\fixtures\CustomerAddressFixture;
1718
use craftcommercetests\fixtures\CustomerFixture;
1819
use UnitTester;
@@ -108,6 +109,93 @@ public function getCartDataProvider(): array
108109
];
109110
}
110111

112+
/**
113+
* Tests that calling forgetCart() followed by getCart() in the same request returns a new
114+
* cart with a different number — verifying the fix in loadCookie() that respects the `false`
115+
* set by forgetCart() and prevents the cookie from restoring the forgotten cart.
116+
*
117+
* @see https://github.com/craftcms/commerce/issues/4279
118+
*/
119+
public function testForgetCartPreventsCartRestoration(): void
120+
{
121+
$cartsService = Plugin::getInstance()->getCarts();
122+
123+
// First call generates an in-memory cart number and returns a new cart.
124+
$initialCart = $cartsService->getCart();
125+
$originalNumber = $initialCart->number;
126+
127+
// forgetCart() sets the private $_cartNumber sentinel to `false`.
128+
$cartsService->forgetCart();
129+
130+
// A subsequent getCart() must generate a completely new number and return a fresh cart.
131+
$newCart = $cartsService->getCart();
132+
133+
self::assertNotEquals(
134+
$originalNumber,
135+
$newCart->number,
136+
'After forgetCart(), getCart() should return a cart with a new number.',
137+
);
138+
}
139+
140+
/**
141+
* Demonstrates that without the fix, a web request whose cookie still carries the forgotten
142+
* cart number causes getCart() to reuse that number — exactly as the old loadCookie() would
143+
* have behaved before the `$this->_cartNumber === false` guard was introduced.
144+
*
145+
* @see https://github.com/craftcms/commerce/issues/4279
146+
*/
147+
public function testForgetCartWithRestoredCartNumberReturnsSameNumber(): void
148+
{
149+
$carts = Plugin::getInstance()->getCarts();
150+
151+
// Get an initial cart and number.
152+
$initialCart = $carts->getCart();
153+
$originalNumber = $initialCart->number;
154+
155+
// Forget the cart — $_cartNumber is now `false`.
156+
$carts->forgetCart();
157+
158+
$cookieName = 'test_commerce_cart';
159+
$carts->cartCookie = ['name' => $cookieName];
160+
161+
// Simulate the pre-fix state: set $_cartNumber to `null`.
162+
// In old code the `$this->_cartNumber === false` guard didn't exist, so even after
163+
// forgetCart() wrote `false`, loadCookie() would proceed and silently overwrite it
164+
// with whatever value was in the request cookie.
165+
$reflection = new \ReflectionClass($carts);
166+
$cartNumberProp = $reflection->getProperty('_cartNumber');
167+
$cartNumberProp->setAccessible(true);
168+
$cartNumberProp->setValue($carts, null);
169+
170+
$requestCookies = new \yii\web\CookieCollection();
171+
$requestCookies->add(new \yii\web\Cookie([
172+
'name' => $cookieName,
173+
'value' => $originalNumber,
174+
]));
175+
176+
$originalRequest = \Craft::$app->getRequest();
177+
178+
// Create a mock request class to return test data
179+
$requestMock = $this->make(Request::class, [
180+
'getIsConsoleRequest' => false,
181+
'getCookies' => $requestCookies,
182+
]);
183+
184+
Craft::$app->set('request', $requestMock);
185+
186+
try {
187+
$restoredCart = $carts->getCart();
188+
189+
self::assertEquals(
190+
$originalNumber,
191+
$restoredCart->number,
192+
'Without the false guard in loadCookie(), the request cookie restores the forgotten cart number.',
193+
);
194+
} finally {
195+
\Craft::$app->set('request', $originalRequest);
196+
}
197+
}
198+
111199
public function testGetCartSwitchCustomer(): void
112200
{
113201
$cartNumber = Plugin::getInstance()->getCarts()->generateCartNumber();

0 commit comments

Comments
 (0)