Commit 693740ba210 for woocommerce
commit 693740ba210288c95fea4911554f41ca4cde9de2
Author: Thomas Roberts <5656702+opr@users.noreply.github.com>
Date: Wed Sep 16 10:00:13 2026 +0100
Prevent cart loss after Store API batch load failures (#67920)
* Fix cart persistence after Store API batch load failures
* Add changelog entry for Store API cart session fix
* Fix PHPStan cart type regression
* Prevent failed Store API carts from updating sessions
* Track failed carts explicitly for session updates
* Cover failed cart session callbacks
* Expand Store API cart failure regression coverage
* Guard all failed cart session writes
* Fix Batch regression test deadlock
* Keep failed Store API carts available to extensions
* Limit cart invalidation to session load failures
* Document Store API batch cart failure responses
diff --git a/docs/apis/store-api/cart-tokens.md b/docs/apis/store-api/cart-tokens.md
index f9ac5d6499f..36759fb6c4a 100644
--- a/docs/apis/store-api/cart-tokens.md
+++ b/docs/apis/store-api/cart-tokens.md
@@ -9,7 +9,7 @@ Cart tokens can be used instead of cookies based sessions for headless interacti
## Obtaining a Cart Token
-Requests to `/cart` endpoints return a `Cart-Token` header alongside the response. This contains a token which can later be sent as a request header to the Store API Cart and Checkout endpoints to identify the cart.
+Successful requests to `/cart` endpoints return a `Cart-Token` header alongside the response. This contains a token which can later be sent as a request header to the Store API Cart and Checkout endpoints to identify the cart. Error responses caused by a cart session loading failure do not include `Cart-Token` or `Cart-Hash` headers.
The quickest method of obtaining a Cart Token is to make a GET request `/wp-json/wc/store/v1/cart` and observe the response headers. You should see a `Cart-Token` header there.
diff --git a/docs/apis/store-api/resources-endpoints/cart.md b/docs/apis/store-api/resources-endpoints/cart.md
index dce25c3863d..f3ec22e67ca 100644
--- a/docs/apis/store-api/resources-endpoints/cart.md
+++ b/docs/apis/store-api/resources-endpoints/cart.md
@@ -483,6 +483,8 @@ The JSON payload for adding multiple items to the cart would look like this:
}
```
+If a cart subrequest fails while loading its session, it returns a `500` response with the `woocommerce_rest_unknown_server_error` error code. Later cart subrequests in the same batch also return `500` instead of using the incomplete cart, while non-cart subrequests continue normally. These failed cart responses do not include `Cart-Token` or `Cart-Hash` headers.
+
## Remove Item
Remove an item from the cart and return the full cart response, or an error.
diff --git a/plugins/woocommerce/changelog/fix-store-api-batch-cart-session-failure b/plugins/woocommerce/changelog/fix-store-api-batch-cart-session-failure
new file mode 100644
index 00000000000..8677b5752c2
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-store-api-batch-cart-session-failure
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Prevent failed Store API batch requests from clearing the shopper's saved cart.
diff --git a/plugins/woocommerce/includes/class-wc-cart-session.php b/plugins/woocommerce/includes/class-wc-cart-session.php
index a98a69a28a4..a5f17d40650 100644
--- a/plugins/woocommerce/includes/class-wc-cart-session.php
+++ b/plugins/woocommerce/includes/class-wc-cart-session.php
@@ -20,6 +20,13 @@ if ( ! defined( 'ABSPATH' ) ) {
*/
final class WC_Cart_Session {
+ /**
+ * Carts whose session updates have been disabled.
+ *
+ * @var WeakReference<WC_Cart>[]
+ */
+ private static $carts_with_disabled_updates = array();
+
/**
* Reference to cart object.
*
@@ -96,6 +103,10 @@ final class WC_Cart_Session {
* @since 3.2.0
*/
public function get_cart_from_session() {
+ if ( $this->should_skip_session_updates() ) {
+ return;
+ }
+
/**
* Fires when cart is loaded from session.
*
@@ -305,12 +316,60 @@ final class WC_Cart_Session {
}
}
+ /**
+ * Enables or disables session updates for a cart.
+ *
+ * @internal
+ * @since 11.2.0
+ *
+ * @param WC_Cart $cart Cart object.
+ * @param bool $enabled Whether session updates should be enabled.
+ */
+ public static function set_updates_enabled_for_cart( WC_Cart $cart, $enabled ): void {
+ $cart_id = spl_object_id( $cart );
+
+ if ( $enabled ) {
+ unset( self::$carts_with_disabled_updates[ $cart_id ] );
+ } else {
+ self::$carts_with_disabled_updates[ $cart_id ] = WeakReference::create( $cart );
+ }
+ }
+
+ /**
+ * Checks whether session updates are enabled for a cart.
+ *
+ * @internal
+ * @since 11.2.0
+ *
+ * @param WC_Cart $cart Cart object.
+ * @return bool
+ */
+ public static function are_updates_enabled_for_cart( WC_Cart $cart ): bool {
+ $cart_id = spl_object_id( $cart );
+
+ if ( ! isset( self::$carts_with_disabled_updates[ $cart_id ] ) ) {
+ return true;
+ }
+
+ $disabled_cart = self::$carts_with_disabled_updates[ $cart_id ]->get();
+ if ( null === $disabled_cart ) {
+ unset( self::$carts_with_disabled_updates[ $cart_id ] );
+ return true;
+ }
+
+ return $disabled_cart !== $cart;
+ }
+
/**
* Destroy cart session data.
*
* @since 3.2.0
*/
public function destroy_cart_session() {
+ if ( $this->should_skip_session_updates() ) {
+ return;
+ }
+
$wc_session = WC()->session;
$wc_session->set( 'cart', null );
@@ -335,7 +394,7 @@ final class WC_Cart_Session {
* @since 3.2.0
*/
public function maybe_set_cart_cookies() {
- if ( headers_sent() || ! did_action( 'wp_loaded' ) ) {
+ if ( $this->should_skip_session_updates() || headers_sent() || ! did_action( 'wp_loaded' ) ) {
return;
}
if ( ! $this->cart->is_empty() ) {
@@ -402,6 +461,10 @@ final class WC_Cart_Session {
* Sets the php session data for the cart and coupons.
*/
public function set_session() {
+ if ( $this->should_skip_session_updates() ) {
+ return;
+ }
+
$wc_session = WC()->session;
$cart = $this->get_cart_for_session();
@@ -458,6 +521,10 @@ final class WC_Cart_Session {
* Save the persistent cart when the cart is updated.
*/
public function persistent_cart_update() {
+ if ( $this->should_skip_session_updates() ) {
+ return;
+ }
+
/**
* Filters whether the persistent cart is enabled.
*
@@ -479,6 +546,10 @@ final class WC_Cart_Session {
* Delete the persistent cart permanently.
*/
public function persistent_cart_destroy() {
+ if ( $this->should_skip_session_updates() ) {
+ return;
+ }
+
/**
* Filters whether the persistent cart is enabled.
*
@@ -721,12 +792,25 @@ final class WC_Cart_Session {
return false;
}
+ /**
+ * Checks whether session updates have been disabled for this cart.
+ *
+ * @return bool
+ */
+ private function should_skip_session_updates() {
+ return ! self::are_updates_enabled_for_cart( $this->cart );
+ }
+
/**
* Removes items from the removed cart contents on next user initiated request.
*
* @return void
*/
public function clean_up_removed_cart_contents() {
+ if ( $this->should_skip_session_updates() ) {
+ return;
+ }
+
// Limit to page requests initiated by the user.
$is_page = is_singular() || is_archive() || is_search();
diff --git a/plugins/woocommerce/src/StoreApi/Routes/V1/AbstractCartRoute.php b/plugins/woocommerce/src/StoreApi/Routes/V1/AbstractCartRoute.php
index 06a688097a5..610563d1edb 100644
--- a/plugins/woocommerce/src/StoreApi/Routes/V1/AbstractCartRoute.php
+++ b/plugins/woocommerce/src/StoreApi/Routes/V1/AbstractCartRoute.php
@@ -193,7 +193,7 @@ abstract class AbstractCartRoute extends AbstractRoute {
$response->header( 'User-ID', get_current_user_id() );
$response->header( 'Cache-Control', 'no-store' );
- if ( WC()->cart instanceof \WC_Cart ) {
+ if ( WC()->cart instanceof \WC_Cart && \WC_Cart_Session::are_updates_enabled_for_cart( WC()->cart ) ) {
$response->header( 'Cart-Token', $this->get_cart_token() );
$response->header( 'Cart-Hash', WC()->cart->get_cart_hash() );
}
@@ -204,19 +204,34 @@ abstract class AbstractCartRoute extends AbstractRoute {
/**
* Load the cart session before handling responses.
*
+ * @throws \RuntimeException When a previous cart session load failed.
+ * @throws \Throwable When the cart cannot be loaded or normalized.
* @param \WP_REST_Request $request Request object.
*/
protected function load_cart_session( \WP_REST_Request $request ) {
- if ( $this->has_cart_token( $request ) ) {
- // Overrides the core session class.
- add_filter(
- 'woocommerce_session_handler',
- function () {
- return SessionHandler::class;
- }
- );
+ if ( WC()->cart instanceof \WC_Cart && ! \WC_Cart_Session::are_updates_enabled_for_cart( WC()->cart ) ) {
+ throw new \RuntimeException( 'The cart is unavailable after its session failed to load.' );
}
- $this->cart_controller->load_cart();
+
+ try {
+ if ( $this->has_cart_token( $request ) ) {
+ // Overrides the core session class.
+ add_filter(
+ 'woocommerce_session_handler',
+ function () {
+ return SessionHandler::class;
+ }
+ );
+ }
+ $this->cart_controller->load_cart();
+ } catch ( \Throwable $error ) {
+ if ( WC()->cart instanceof \WC_Cart ) {
+ \WC_Cart_Session::set_updates_enabled_for_cart( WC()->cart, false );
+ }
+
+ throw $error;
+ }
+
$this->cart_controller->normalize_cart();
}
diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Batch.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Batch.php
index 0882cf1fdc1..670711f0c34 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Batch.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Batch.php
@@ -130,6 +130,147 @@ class Batch extends ControllerTestCase {
}
+ /**
+ * @testdox Should preserve the session cart when loading it fails in a batch sub-request.
+ */
+ public function test_cart_session_failure_does_not_clear_session_cart(): void {
+ WC()->cart->add_to_cart( $this->products[0]->get_id() );
+ WC()->cart->add_to_cart( $this->products[1]->get_id() );
+
+ global $wp_query;
+
+ $stored_cart = WC()->cart->get_cart_for_session();
+ $cart_contents_backup = WC()->cart->get_cart_contents();
+ $cart_backup = WC()->cart;
+ $load_action_count = $GLOBALS['wp_actions']['woocommerce_load_cart_from_session'] ?? null;
+ $current_user_id = get_current_user_id();
+ $is_singular_backup = $wp_query->is_singular;
+ $is_archive_backup = $wp_query->is_archive;
+ $is_search_backup = $wp_query->is_search;
+ $restored_item_count = 0;
+ $session_failure_callback = static function ( $session_data ) use ( &$restored_item_count ) {
+ ++$restored_item_count;
+ if ( 2 === $restored_item_count ) {
+ throw new \RuntimeException( 'Synthetic Store API cart-session failure.' );
+ }
+ return $session_data;
+ };
+ WC()->session->set( 'cart', $stored_cart );
+ WC()->cart->set_cart_contents( array() );
+ unset( $GLOBALS['wp_actions']['woocommerce_load_cart_from_session'] );
+ add_filter( 'woocommerce_get_cart_item_from_session', $session_failure_callback );
+
+ $request = new \WP_REST_Request( 'POST', '/wc/store/v1/batch' );
+ $request->set_body_params(
+ array(
+ 'requests' => array(
+ array(
+ 'method' => 'POST',
+ 'path' => '/wc/store/v1/cart/update-customer',
+ 'body' => array(),
+ 'headers' => array( 'Nonce' => wp_create_nonce( 'wc_store_api' ) ),
+ ),
+ array(
+ 'method' => 'POST',
+ 'path' => '/wc/store/v1/cart/update-customer',
+ 'body' => array(),
+ 'headers' => array( 'Nonce' => wp_create_nonce( 'wc_store_api' ) ),
+ ),
+ array(
+ 'method' => 'GET',
+ 'path' => '/wc/store/v1/products',
+ ),
+ ),
+ )
+ );
+
+ try {
+ $response = rest_get_server()->dispatch( $request );
+ $response_data = $response->get_data();
+
+ $this->assertSame( 500, $response_data['responses'][0]['status'], 'The request that fails to load the cart should return an error.' );
+ $this->assertSame( 500, $response_data['responses'][1]['status'], 'Later cart requests should not use a partially loaded cart.' );
+ $this->assertSame( 200, $response_data['responses'][2]['status'], 'Later non-cart requests should remain available.' );
+ $this->assertCount( 1, $cart_backup->get_cart_contents(), 'The synthetic failure should occur after partially restoring the cart.' );
+ $this->assertArrayNotHasKey( 'Cart-Token', $response_data['responses'][0]['headers'], 'A failed cart response should not include a cart token.' );
+ $this->assertArrayNotHasKey( 'Cart-Hash', $response_data['responses'][0]['headers'], 'A failed cart response should not include a cart hash.' );
+
+ $failed_cart_session = new \WC_Cart_Session( $cart_backup );
+ $failed_cart_session->get_cart_from_session();
+ $this->assertCount( 1, $cart_backup->get_cart_contents(), 'The failed cart should not resume loading from the session.' );
+ $this->assertSame( $cart_backup, WC()->cart, 'The failed cart should remain available to extension callbacks.' );
+
+ WC()->cart->empty_cart();
+ $this->assertSame( $stored_cart, WC()->session->get( 'cart' ), 'Emptying the failed cart should not destroy the session.' );
+
+ do_action( 'woocommerce_removed_coupon', 'synthetic-coupon' );
+ $this->assertSame( $stored_cart, WC()->session->get( 'cart' ), 'The failed cart should not update the session.' );
+
+ $user_id = self::factory()->user->create();
+ $persistent_cart_key = '_woocommerce_persistent_cart_' . get_current_blog_id();
+ $stored_persistent_cart = array( 'cart' => $stored_cart );
+ wp_set_current_user( $user_id );
+ update_user_meta( $user_id, $persistent_cart_key, $stored_persistent_cart );
+ do_action( 'woocommerce_cart_item_set_quantity', 'synthetic-item', 2, $cart_backup );
+ $this->assertSame( $stored_persistent_cart, get_user_meta( $user_id, $persistent_cart_key, true ), 'The failed cart should not update the persistent cart.' );
+ $failed_cart_session->persistent_cart_destroy();
+ $this->assertSame( $stored_persistent_cart, get_user_meta( $user_id, $persistent_cart_key, true ), 'The failed cart should not destroy the persistent cart.' );
+
+ $stored_removed_cart_contents = array( 'synthetic-item' => array( 'quantity' => 1 ) );
+ WC()->session->set( 'removed_cart_contents', $stored_removed_cart_contents );
+ $wp_query->is_singular = true;
+ $wp_query->is_archive = false;
+ $wp_query->is_search = false;
+ $failed_cart_session->clean_up_removed_cart_contents();
+ $this->assertSame( $stored_removed_cart_contents, WC()->session->get( 'removed_cart_contents' ), 'The failed cart should not clean up removed cart contents.' );
+ } finally {
+ remove_filter( 'woocommerce_get_cart_item_from_session', $session_failure_callback );
+ \WC_Cart_Session::set_updates_enabled_for_cart( $cart_backup, true );
+ WC()->cart = $cart_backup;
+ WC()->cart->set_cart_contents( $cart_contents_backup );
+ wp_set_current_user( $current_user_id );
+ $wp_query->is_singular = $is_singular_backup;
+ $wp_query->is_archive = $is_archive_backup;
+ $wp_query->is_search = $is_search_backup;
+ if ( null === $load_action_count ) {
+ unset( $GLOBALS['wp_actions']['woocommerce_load_cart_from_session'] );
+ } else {
+ // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Restore the action count changed by the test.
+ $GLOBALS['wp_actions']['woocommerce_load_cart_from_session'] = $load_action_count;
+ }
+ }
+ }
+
+ /**
+ * @testdox Disabling cart-session updates only affects the exact marked cart.
+ */
+ public function test_cart_session_update_registry_is_scoped_to_the_marked_cart(): void {
+ $marked_cart = WC()->cart;
+ $unmarked_clone = clone $marked_cart;
+ $marked_session = new \WC_Cart_Session( $marked_cart );
+ $unmarked_session = new \WC_Cart_Session( $unmarked_clone );
+ $stored_session_cart = $marked_cart->get_cart_for_session();
+
+ try {
+ \WC_Cart_Session::set_updates_enabled_for_cart( $marked_cart, false );
+
+ WC()->session->set( 'cart', $stored_session_cart );
+ $marked_session->destroy_cart_session();
+ $this->assertSame( $stored_session_cart, WC()->session->get( 'cart' ), 'The marked cart should not destroy session data.' );
+
+ $unmarked_session->destroy_cart_session();
+ $this->assertNull( WC()->session->get( 'cart' ), 'An unmarked clone should continue updating session data.' );
+
+ WC()->session->set( 'cart', $stored_session_cart );
+ \WC_Cart_Session::set_updates_enabled_for_cart( $marked_cart, true );
+ $marked_session->destroy_cart_session();
+ $this->assertNull( WC()->session->get( 'cart' ), 'Re-enabled carts should resume updating session data.' );
+ } finally {
+ \WC_Cart_Session::set_updates_enabled_for_cart( $marked_cart, true );
+ WC()->session->set( 'cart', $stored_session_cart );
+ }
+ }
+
/**
* Do a batch request with a get request.
*/
diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Cart.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Cart.php
index 1f8ee4666ab..ea631492200 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Cart.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Cart.php
@@ -33,13 +33,6 @@ class Cart extends ControllerTestCase {
*/
private static $coupon_id;
- /**
- * Cart instance removed to mimic a REST request, restored on teardown.
- *
- * @var \WC_Cart|null
- */
- private $cart_backup = null;
-
/**
* Create immutable catalog rows shared by all test methods.
*/
@@ -2301,19 +2294,33 @@ class Cart extends ControllerTestCase {
// The route restores the cart only when this action has not run yet, so reset
// the counter to put the process back into the state a REST request starts in.
+ $load_action_count = $GLOBALS['wp_actions']['woocommerce_load_cart_from_session'] ?? null;
unset( $GLOBALS['wp_actions']['woocommerce_load_cart_from_session'] );
- add_filter(
- 'woocommerce_get_cart_item_from_session',
- static function () {
- throw new \RuntimeException( 'Synthetic Store API cart-session failure.' );
- }
- );
+ $cart_backup = WC()->cart;
+ $callback = static function () {
+ throw new \RuntimeException( 'Synthetic Store API cart-session failure.' );
+ };
+ add_filter( 'woocommerce_get_cart_item_from_session', $callback );
- $response = rest_get_server()->dispatch( new \WP_REST_Request( 'GET', '/wc/store/v1/cart' ) );
+ try {
+ $response = rest_get_server()->dispatch( new \WP_REST_Request( 'GET', '/wc/store/v1/cart' ) );
+ } finally {
+ remove_filter( 'woocommerce_get_cart_item_from_session', $callback );
+ \WC_Cart_Session::set_updates_enabled_for_cart( $cart_backup, true );
+ WC()->cart = $cart_backup;
+ if ( null === $load_action_count ) {
+ unset( $GLOBALS['wp_actions']['woocommerce_load_cart_from_session'] );
+ } else {
+ // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Restore the action count changed by the test.
+ $GLOBALS['wp_actions']['woocommerce_load_cart_from_session'] = $load_action_count;
+ }
+ }
$this->assertSame( 500, $response->get_status(), 'A cart session failure should return a Store API error response.' );
$this->assertSame( 'woocommerce_rest_unknown_server_error', $response->get_data()['code'] );
+ $this->assertArrayNotHasKey( 'Cart-Token', $response->get_headers(), 'A failed cart response should not include a cart token.' );
+ $this->assertArrayNotHasKey( 'Cart-Hash', $response->get_headers(), 'A failed cart response should not include a cart hash.' );
}
/**
@@ -2322,34 +2329,33 @@ class Cart extends ControllerTestCase {
public function test_cart_session_failure_before_restore_returns_error_response() {
// This filter runs before `get_cart_from_session()` fires its action, so nothing
// stops the response headers attempting a second load of the failed cart.
+ $load_action_count = $GLOBALS['wp_actions']['woocommerce_load_cart_from_session'] ?? null;
unset( $GLOBALS['wp_actions']['woocommerce_load_cart_from_session'] );
// A REST request never runs `initialize_cart()`, so the route starts with no
// cart at all. The test bootstrap leaves one behind.
- $this->cart_backup = WC()->cart;
- WC()->cart = null;
-
- add_filter(
- 'woocommerce_session_handler',
- static function () {
- throw new \RuntimeException( 'Synthetic session handler failure.' );
- }
- );
+ $cart_backup = WC()->cart;
+ WC()->cart = null;
- $response = rest_get_server()->dispatch( new \WP_REST_Request( 'GET', '/wc/store/v1/cart' ) );
-
- $this->assertSame( 500, $response->get_status(), 'A cart session failure should return a Store API error response.' );
- }
+ $callback = static function () {
+ throw new \RuntimeException( 'Synthetic session handler failure.' );
+ };
+ add_filter( 'woocommerce_session_handler', $callback );
- /**
- * Restore the cart instance removed by the cart session failure tests.
- */
- public function tearDown(): void {
- if ( $this->cart_backup instanceof \WC_Cart ) {
- WC()->cart = $this->cart_backup;
- $this->cart_backup = null;
+ try {
+ $response = rest_get_server()->dispatch( new \WP_REST_Request( 'GET', '/wc/store/v1/cart' ) );
+ } finally {
+ remove_filter( 'woocommerce_session_handler', $callback );
+ \WC_Cart_Session::set_updates_enabled_for_cart( $cart_backup, true );
+ WC()->cart = $cart_backup;
+ if ( null === $load_action_count ) {
+ unset( $GLOBALS['wp_actions']['woocommerce_load_cart_from_session'] );
+ } else {
+ // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Restore the action count changed by the test.
+ $GLOBALS['wp_actions']['woocommerce_load_cart_from_session'] = $load_action_count;
+ }
}
- parent::tearDown();
+ $this->assertSame( 500, $response->get_status(), 'A cart session failure should return a Store API error response.' );
}
}
diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Checkout.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Checkout.php
index 7a3d7053c35..ff1c42db37d 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Checkout.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Checkout.php
@@ -3381,16 +3381,28 @@ class Checkout extends \WP_Test_REST_TestCase {
// The route restores the cart only when this action has not run yet, so reset
// the counter to put the process back into the state a REST request starts in.
+ $load_action_count = $GLOBALS['wp_actions']['woocommerce_load_cart_from_session'] ?? null;
unset( $GLOBALS['wp_actions']['woocommerce_load_cart_from_session'] );
- add_filter(
- 'woocommerce_get_cart_item_from_session',
- static function () {
- throw new \RuntimeException( 'Synthetic Store API cart-session failure.' );
- }
- );
+ $cart_backup = WC()->cart;
+ $callback = static function () {
+ throw new \RuntimeException( 'Synthetic Store API cart-session failure.' );
+ };
+ add_filter( 'woocommerce_get_cart_item_from_session', $callback );
- $response = rest_get_server()->dispatch( new \WP_REST_Request( 'GET', '/wc/store/v1/checkout' ) );
+ try {
+ $response = rest_get_server()->dispatch( new \WP_REST_Request( 'GET', '/wc/store/v1/checkout' ) );
+ } finally {
+ remove_filter( 'woocommerce_get_cart_item_from_session', $callback );
+ \WC_Cart_Session::set_updates_enabled_for_cart( $cart_backup, true );
+ WC()->cart = $cart_backup;
+ if ( null === $load_action_count ) {
+ unset( $GLOBALS['wp_actions']['woocommerce_load_cart_from_session'] );
+ } else {
+ // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Restore the action count changed by the test.
+ $GLOBALS['wp_actions']['woocommerce_load_cart_from_session'] = $load_action_count;
+ }
+ }
$this->assertSame( 500, $response->get_status(), 'A cart session failure should return a Store API error response.' );
$this->assertSame( 'woocommerce_rest_unknown_server_error', $response->get_data()['code'] );