Commit c3a7a1c6e0a for woocommerce

commit c3a7a1c6e0aec2e64974b628be9afdb537577e99
Author: daledupreez <dale@automattic.com>
Date:   Tue Jul 28 20:20:41 2026 +0200

    Add unit tests to better validate use of orders in hooks (#67045)

    * Ensure same order instances are used for AbandonedCartRecovery tests
    * Changelog
    * Add similar tests for handle_new_order()
    * Ensure order cache is clear for new handle_new_order test assertions
    * Fix linting - avoid short array syntax

diff --git a/plugins/woocommerce/changelog/add-unit-tests-to-better-validate-use-of-orders-in-hooks b/plugins/woocommerce/changelog/add-unit-tests-to-better-validate-use-of-orders-in-hooks
new file mode 100644
index 00000000000..37cbcd7a42a
--- /dev/null
+++ b/plugins/woocommerce/changelog/add-unit-tests-to-better-validate-use-of-orders-in-hooks
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Add stricter checks for order re-use in AbandonedCartRecovery unit tests
diff --git a/plugins/woocommerce/tests/php/src/Internal/AbandonedCartRecovery/SchedulerTest.php b/plugins/woocommerce/tests/php/src/Internal/AbandonedCartRecovery/SchedulerTest.php
index 42052a0ff6c..91c9cb1c08f 100644
--- a/plugins/woocommerce/tests/php/src/Internal/AbandonedCartRecovery/SchedulerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/AbandonedCartRecovery/SchedulerTest.php
@@ -3,6 +3,7 @@ declare( strict_types = 1 );

 namespace Automattic\WooCommerce\Tests\Internal\AbandonedCartRecovery;

+use Automattic\WooCommerce\Caches\OrderCache;
 use Automattic\WooCommerce\Enums\OrderStatus;
 use Automattic\WooCommerce\Internal\AbandonedCartRecovery\Scheduler;
 use Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper;
@@ -216,14 +217,30 @@ class SchedulerTest extends WC_Unit_Test_Case {

 	/**
 	 * @testdox handle_new_order() schedules the AS action and records the scheduled-at meta for a pending order when automated + enabled.
+	 * @dataProvider provide_handle_new_order_test_cases
+	 *
+	 * @param bool $supply_order_argument Whether to supply the order object as the second argument to handle_new_order().
 	 */
-	public function test_handle_new_order_schedules_for_pending_order(): void {
+	public function test_handle_new_order_schedules_for_pending_order( bool $supply_order_argument ): void {
 		$order = OrderHelper::create_order();
 		$order->set_created_via( 'checkout' );
 		$order->set_status( OrderStatus::PENDING );
 		$order->save();

-		$this->sut->handle_new_order( $order->get_id() );
+		$order_argument = $supply_order_argument ? $order : null;
+
+		if ( $supply_order_argument ) {
+			wc_get_container()->get( OrderCache::class )->remove( $order->get_id() );
+		}
+
+		$this->sut->handle_new_order( $order->get_id(), $order_argument );
+
+		if ( $order_argument ) {
+			$this->assertNotEmpty(
+				$order_argument->get_meta( Scheduler::SCHEDULED_META_KEY ),
+				'Scheduled-at meta must be populated on the supplied order object.'
+			);
+		}

 		$fresh = wc_get_order( $order->get_id() );
 		$this->assertNotEmpty(
@@ -236,6 +253,16 @@ class SchedulerTest extends WC_Unit_Test_Case {
 		);
 	}

+	/**
+	 * Provide test cases for {@see test_handle_new_order_schedules_for_pending_order()}.
+	 */
+	public function provide_handle_new_order_test_cases(): array {
+		return array(
+			'New order with no order object' => array( false ),
+			'New order with order object'    => array( true ),
+		);
+	}
+
 	/**
 	 * @testdox handle_new_order() is a no-op when the order is created in a non-abandoned status (e.g. processing).
 	 */
@@ -533,13 +560,28 @@ class SchedulerTest extends WC_Unit_Test_Case {
 	}

 	/**
-	 * @testdox handle_status_changed() cancels the pending send when the hook supplies the order object, matching the id-only path that falls back to a lookup.
+	 * @testdox handle_status_changed() reuses the hook-supplied order while cancelling its pending send.
 	 */
 	public function test_handle_status_changed_cancels_scheduled_send_when_order_object_passed(): void {
 		$order = $this->schedule_for_pending_order();
+		$this->assertNotEmpty( $order->get_meta( Scheduler::SCHEDULED_META_KEY ), 'Fixture must prime the supplied order instance with the scheduled-at meta.' );
+		wc_get_container()->get( OrderCache::class )->remove( $order->get_id() );

-		$this->sut->handle_status_changed( $order->get_id(), OrderStatus::PENDING, OrderStatus::PROCESSING, $order );
+		$filtered_order = null;
+		$capture_order  = static function ( array $statuses, ?WC_Order $candidate_order ) use ( &$filtered_order ): array {
+			$filtered_order = $candidate_order;
+			return $statuses;
+		};
+
+		add_filter( 'woocommerce_abandoned_cart_recovery_eligible_statuses', $capture_order, 10, 2 );
+		try {
+			$this->sut->handle_status_changed( $order->get_id(), OrderStatus::PENDING, OrderStatus::PROCESSING, $order );
+		} finally {
+			remove_filter( 'woocommerce_abandoned_cart_recovery_eligible_statuses', $capture_order );
+		}

+		$this->assertSame( $order, $filtered_order, 'Eligibility checks must receive the order instance supplied by the hook.' );
+		$this->assertSame( '', $order->get_meta( Scheduler::SCHEDULED_META_KEY ), 'Scheduled-at meta must be cleared on the supplied order instance.' );
 		$fresh = wc_get_order( $order->get_id() );
 		$this->assertSame( '', $fresh->get_meta( Scheduler::SCHEDULED_META_KEY ), 'Scheduled-at meta must be cleared when the hook supplies the order object.' );
 		$this->assertFalse( as_next_scheduled_action( Scheduler::ACTION_HOOK, array( $order->get_id() ) ), 'Queued send must be unscheduled when the hook supplies the order object.' );
@@ -588,13 +630,16 @@ class SchedulerTest extends WC_Unit_Test_Case {
 	}

 	/**
-	 * @testdox handle_cancellation() clears the same state when the caller supplies the order object, so the status-change and delete entry points match the id-only trash path.
+	 * @testdox handle_cancellation() reuses the caller-supplied order while clearing its scheduled state.
 	 */
 	public function test_handle_cancellation_clears_state_when_order_object_passed(): void {
 		$order = $this->schedule_for_pending_order();
+		$this->assertNotEmpty( $order->get_meta( Scheduler::SCHEDULED_META_KEY ), 'Fixture must prime the supplied order instance with the scheduled-at meta.' );
+		wc_get_container()->get( OrderCache::class )->remove( $order->get_id() );

 		$this->sut->handle_cancellation( $order->get_id(), $order );

+		$this->assertSame( '', $order->get_meta( Scheduler::SCHEDULED_META_KEY ), 'Scheduled-at meta must be cleared on the supplied order instance.' );
 		$fresh = wc_get_order( $order->get_id() );
 		$this->assertSame( '', $fresh->get_meta( Scheduler::SCHEDULED_META_KEY ), 'Scheduled-at meta must be cleared when the caller supplies the order object.' );
 		$this->assertFalse( as_next_scheduled_action( Scheduler::ACTION_HOOK, array( $order->get_id() ) ), 'Queued send must be unscheduled when the caller supplies the order object.' );