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.' );