Commit 52b6e139a7c for woocommerce

commit 52b6e139a7c87fa7c6e1e56b390255c814aef93f
Author: Adrian Moldovan <3854374+adimoldovan@users.noreply.github.com>
Date:   Wed Jul 8 19:17:45 2026 +0300

    test: remove real sleep() from PHP unit tests (#66413)

diff --git a/plugins/woocommerce/changelog/testops-212-remove-real-sleep-from-php-unit-tests b/plugins/woocommerce/changelog/testops-212-remove-real-sleep-from-php-unit-tests
new file mode 100644
index 00000000000..09e16a518c5
--- /dev/null
+++ b/plugins/woocommerce/changelog/testops-212-remove-real-sleep-from-php-unit-tests
@@ -0,0 +1,3 @@
+Significance: patch
+Type: dev
+Comment: Remove real sleep() calls from PHP unit tests by controlling timestamps explicitly; no production change.
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php b/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php
index cae702a1673..7d1775325b9 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php
@@ -344,29 +344,32 @@ class WC_Tracker_Test extends \WC_Unit_Test_Case {
 		$this->assertEquals( $tracking_data['woocommerce_allow_tracking_last_modified'], 'unknown' );
 		$this->assertEquals( $tracking_data['woocommerce_allow_tracking_first_optin'], 'unknown' );

-		$time_one = time();
+		$before = time();
 		update_option( 'woocommerce_allow_tracking', 'yes' );
 		$tracking_data = WC_Tracker::get_tracking_data();
 		$this->assertEquals( $tracking_data['woocommerce_allow_tracking'], 'yes' );
-		$this->assertTrue( $tracking_data['woocommerce_allow_tracking_last_modified'] >= $time_one );
-		$this->assertTrue( $tracking_data['woocommerce_allow_tracking_first_optin'] >= $time_one );
+		$this->assertGreaterThanOrEqual( $before, (int) $tracking_data['woocommerce_allow_tracking_last_modified'] );
+		$this->assertGreaterThanOrEqual( $before, (int) $tracking_data['woocommerce_allow_tracking_first_optin'] );

-		sleep( 1 ); // be sure $time_two is at least one second after $time_one.
-		$time_two = time();
+		// first_optin is recorded once on the first opt-in and must never change afterwards.
+		$first_optin = (int) get_option( 'woocommerce_allow_tracking_first_optin' );
+
+		// last_modified must be refreshed to the current time on every tracking change. Capturing the
+		// time immediately before each update keeps this deterministic without waiting on the clock.
+		$before = time();
 		update_option( 'woocommerce_allow_tracking', 'no' );
 		$tracking_data = WC_Tracker::get_tracking_data();

 		$this->assertEquals( $tracking_data['woocommerce_allow_tracking'], 'no' );
-		$this->assertTrue( $tracking_data['woocommerce_allow_tracking_last_modified'] >= $time_two );
-		$this->assertTrue( $tracking_data['woocommerce_allow_tracking_first_optin'] >= $time_one && $tracking_data['woocommerce_allow_tracking_first_optin'] < $time_two );
+		$this->assertGreaterThanOrEqual( $before, (int) $tracking_data['woocommerce_allow_tracking_last_modified'] );
+		$this->assertEquals( $first_optin, (int) $tracking_data['woocommerce_allow_tracking_first_optin'] );

-		sleep( 1 ); // be sure $time_three is at least one second after $time_two.
-		$time_three = time();
+		$before = time();
 		update_option( 'woocommerce_allow_tracking', 'yes' );
 		$tracking_data = WC_Tracker::get_tracking_data();
 		$this->assertEquals( $tracking_data['woocommerce_allow_tracking'], 'yes' );
-		$this->assertTrue( $tracking_data['woocommerce_allow_tracking_last_modified'] >= $time_three );
-		$this->assertTrue( $tracking_data['woocommerce_allow_tracking_first_optin'] >= $time_one && $tracking_data['woocommerce_allow_tracking_first_optin'] < $time_two );
+		$this->assertGreaterThanOrEqual( $before, (int) $tracking_data['woocommerce_allow_tracking_last_modified'] );
+		$this->assertEquals( $first_optin, (int) $tracking_data['woocommerce_allow_tracking_first_optin'] );

 		// Restore everything as it was.
 		update_option( 'woocommerce_allow_tracking', $current_woocommerce_allow_tracking );
diff --git a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version4/Customers/class-wc-rest-customers-v4-controller-tests.php b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version4/Customers/class-wc-rest-customers-v4-controller-tests.php
index 94f3be57999..725702fe080 100644
--- a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version4/Customers/class-wc-rest-customers-v4-controller-tests.php
+++ b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version4/Customers/class-wc-rest-customers-v4-controller-tests.php
@@ -165,6 +165,22 @@ class WC_REST_Customers_V4_Controller_Tests extends WC_REST_Unit_Test_Case {
 		return $customer;
 	}

+	/**
+	 * Backdate a customer's registration timestamp so ordering-by-registration-date tests are
+	 * deterministic without waiting on the real clock.
+	 *
+	 * @param int $user_id     User ID.
+	 * @param int $seconds_ago How many seconds before "now" the user should appear to have registered.
+	 */
+	private function set_user_registered( int $user_id, int $seconds_ago ): void {
+		wp_update_user(
+			array(
+				'ID'              => $user_id,
+				'user_registered' => gmdate( 'Y-m-d H:i:s', time() - $seconds_ago ),
+			)
+		);
+	}
+
 	/**
 	 * Helper method to validate response against schema.
 	 *
@@ -1044,9 +1060,6 @@ class WC_REST_Customers_V4_Controller_Tests extends WC_REST_Unit_Test_Case {
 			)
 		);

-		// Wait a moment to ensure different registration times.
-		sleep( 1 );
-
 		$customer2 = $this->create_test_customer(
 			array(
 				'email'    => 'orderby2@example.com',
@@ -1054,6 +1067,9 @@ class WC_REST_Customers_V4_Controller_Tests extends WC_REST_Unit_Test_Case {
 			)
 		);

+		$this->set_user_registered( $customer1->get_id(), 200 );
+		$this->set_user_registered( $customer2->get_id(), 100 );
+
 		// Test ordering by ID ascending.
 		$request = new WP_REST_Request( 'GET', '/wc/v4/customers' );
 		$request->set_param( 'orderby', 'id' );
@@ -1346,9 +1362,6 @@ class WC_REST_Customers_V4_Controller_Tests extends WC_REST_Unit_Test_Case {
 			)
 		);

-		// Wait to ensure different registration times.
-		sleep( 1 );
-
 		$customer2 = $this->create_test_customer(
 			array(
 				'email'    => 'orderbydefault2@example.com',
@@ -1356,8 +1369,6 @@ class WC_REST_Customers_V4_Controller_Tests extends WC_REST_Unit_Test_Case {
 			)
 		);

-		sleep( 1 );
-
 		$customer3 = $this->create_test_customer(
 			array(
 				'email'    => 'orderbydefault3@example.com',
@@ -1365,6 +1376,10 @@ class WC_REST_Customers_V4_Controller_Tests extends WC_REST_Unit_Test_Case {
 			)
 		);

+		$this->set_user_registered( $customer1->get_id(), 300 );
+		$this->set_user_registered( $customer2->get_id(), 200 );
+		$this->set_user_registered( $customer3->get_id(), 100 );
+
 		// Make request without orderby parameter.
 		$request  = new WP_REST_Request( 'GET', '/wc/v4/customers' );
 		$response = $this->server->dispatch( $request );
@@ -1416,8 +1431,6 @@ class WC_REST_Customers_V4_Controller_Tests extends WC_REST_Unit_Test_Case {
 		);
 		update_user_meta( $customer1->get_id(), 'wc_order_count_' . $site_specific_key, 10 );

-		sleep( 1 );
-
 		$customer2 = $this->create_test_customer(
 			array(
 				'email'    => 'orderbypresent2@example.com',
@@ -1426,8 +1439,6 @@ class WC_REST_Customers_V4_Controller_Tests extends WC_REST_Unit_Test_Case {
 		);
 		update_user_meta( $customer2->get_id(), 'wc_order_count_' . $site_specific_key, 25 );

-		sleep( 1 );
-
 		$customer3 = $this->create_test_customer(
 			array(
 				'email'    => 'orderbypresent3@example.com',
@@ -1530,8 +1541,6 @@ class WC_REST_Customers_V4_Controller_Tests extends WC_REST_Unit_Test_Case {
 			)
 		);

-		sleep( 1 );
-
 		$customer2 = $this->create_test_customer(
 			array(
 				'email'    => 'registered2@example.com',
@@ -1539,8 +1548,6 @@ class WC_REST_Customers_V4_Controller_Tests extends WC_REST_Unit_Test_Case {
 			)
 		);

-		sleep( 1 );
-
 		$customer3 = $this->create_test_customer(
 			array(
 				'email'    => 'registered3@example.com',
@@ -1548,6 +1555,10 @@ class WC_REST_Customers_V4_Controller_Tests extends WC_REST_Unit_Test_Case {
 			)
 		);

+		$this->set_user_registered( $customer1->get_id(), 300 );
+		$this->set_user_registered( $customer2->get_id(), 200 );
+		$this->set_user_registered( $customer3->get_id(), 100 );
+
 		// Test with orderby=registered_date.
 		$request = new WP_REST_Request( 'GET', '/wc/v4/customers' );
 		$request->set_param( 'orderby', 'registered_date' );
diff --git a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php
index f585d46763d..659ef9bbef1 100644
--- a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php
+++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php
@@ -3224,7 +3224,12 @@ class OrdersTableDataStoreTests extends \HposTestCase {
 		$this->assertEquals( 'test_value', $r_order->get_meta( 'test_key', true ) );

 		$different_request && $this->reset_order_data_store_state( $cot_store );
-		sleep( 2 );
+
+		// Backdate the modified date on both records (save() backfills the post while sync is on) so the
+		// upcoming sync-off meta update bumps the order's modified date past the post's, without waiting
+		// on the real clock.
+		$r_order->set_date_modified( gmdate( 'Y-m-d H:i:s', strtotime( '-2 day' ) ) );
+		$r_order->save();

 		$this->disable_cot_sync();
 		$r_order->update_meta_data( 'test_key', 'test_value_updated' );
diff --git a/plugins/woocommerce/tests/php/src/Internal/OrderReviews/SchedulerTest.php b/plugins/woocommerce/tests/php/src/Internal/OrderReviews/SchedulerTest.php
index d88bab23924..30f86987020 100644
--- a/plugins/woocommerce/tests/php/src/Internal/OrderReviews/SchedulerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/OrderReviews/SchedulerTest.php
@@ -99,12 +99,23 @@ class SchedulerTest extends WC_Unit_Test_Case {
 		$order->update_status( 'completed' );
 		$first = (int) wc_get_order( $order->get_id() )->get_meta( Scheduler::SCHEDULED_META_KEY );

-		// Simulate a second completed-notification firing (e.g. status toggled back and forth).
-		sleep( 1 );
-		do_action( 'woocommerce_order_status_completed', $order->get_id() ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment -- existing core hook, fired here only to simulate a duplicate transition in the test.
+		// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment -- Existing core hook, fired here only to simulate a second completed-notification (e.g. status toggled back and forth).
+		do_action( 'woocommerce_order_status_completed', $order->get_id() );
 		$second = (int) wc_get_order( $order->get_id() )->get_meta( Scheduler::SCHEDULED_META_KEY );

 		$this->assertSame( $first, $second, 'Scheduled-at meta should not change on re-completion.' );
+		$this->assertCount(
+			1,
+			as_get_scheduled_actions(
+				array(
+					'hook'   => Scheduler::ACTION_HOOK,
+					'args'   => array( $order->get_id() ),
+					'status' => \ActionScheduler_Store::STATUS_PENDING,
+				),
+				'ids'
+			),
+			'A duplicate completion must not schedule a second review-request action.'
+		);
 	}

 	/**