Commit 3eeb64f25f2 for woocommerce

commit 3eeb64f25f242178a415e522f7f76f6c9cc012cc
Author: Vlad Olaru <vlad.olaru@automattic.com>
Date:   Sun Sep 13 16:00:08 2026 +0300

    [tests] Pin the refund fixtures in the Orders Stats report tests (#68675)

    test(admin): Pin the refund fixtures in the Orders Stats report tests

    PR #68665 pinned `date_paid` on the order fixtures so a run crossing the
    top of the hour could not drop them out of the queried window. It missed
    the refunds, which reach the same window by a different route.

    `Admin\API\Reports\Orders\Stats\DataStore::update()` backfills a refund
    row's `date_paid` from the refund's own `date_created`, whenever the
    parent order has a `date_paid` of its own -- which, since #68665, it
    always does here. No refund here carries a date: `wc_create_refund()`
    accepts none, so an explicit refund takes the wall clock, and an order
    moved to `refunded` gets a second, implicit refund the same way, because
    `wc_order_fully_refunded()` runs on that status change and calls
    `wc_create_refund()` itself. The window still comes from the order's
    `date_created`, so any refund created after the hour turns over lands
    outside it and its amount goes missing from `refunds`, `total_sales`
    and `net_revenue`.

    Give an order's refunds the order's own date, through a
    `pin_refund_dates()` helper on the shared test case. Applying it per
    order rather than per `wc_create_refund()` call is what covers the
    implicit refunds, which no call site in these tests names.

    Three fixtures were exposed: `test_populate_and_query` and
    `test_populate_and_query_statuses` in `DataStoreBasicsTest`, and
    `test_populate_and_query_refunds` in `DataStoreRefundFiltersTest`. The
    two full-refund tests in that same file query a window a day wide, so
    they have slack the others do not. `DataStoreTest` holds eight more
    `wc_create_refund()` calls and needs nothing: it asserts on
    `wc_order_stats` rows by order id and queries no time window at all.

    Refs WOOAIRR-307

    Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

diff --git a/plugins/woocommerce/changelog/fix-orders-stats-refund-fixture-hour-boundary b/plugins/woocommerce/changelog/fix-orders-stats-refund-fixture-hour-boundary
new file mode 100644
index 00000000000..1a5ba5083ff
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-orders-stats-refund-fixture-hour-boundary
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Pin the refund fixtures' dates in the Orders Stats report tests, closing the half of the hour-boundary flake that pinning the orders alone left open.
diff --git a/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/Stats/DataStoreBasicsTest.php b/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/Stats/DataStoreBasicsTest.php
index 4e554a7b9fa..fe6b050111c 100644
--- a/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/Stats/DataStoreBasicsTest.php
+++ b/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/Stats/DataStoreBasicsTest.php
@@ -57,6 +57,8 @@ class DataStoreBasicsTest extends OrdersStatsTestCase {
 			)
 		);

+		$this->pin_refund_dates( $order, $order->get_date_created() );
+
 		WC_Helper_Queue::run_all_pending( 'wc-admin-data' );

 		$start_time = gmdate( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() );
@@ -150,6 +152,10 @@ class DataStoreBasicsTest extends OrdersStatsTestCase {
 			$order->set_shipping_total( 0 );
 			$order->set_cart_tax( 0 );
 			$order->save();
+
+			// The refunded order's save creates a refund of its own, via
+			// wc_order_fully_refunded(); it needs the fixture's date like any other.
+			$this->pin_refund_dates( $order, $time );
 		}

 		WC_Helper_Queue::run_all_pending( 'wc-admin-data' );
diff --git a/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/Stats/DataStoreRefundFiltersTest.php b/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/Stats/DataStoreRefundFiltersTest.php
index 00218f0d002..f1f927a8e02 100644
--- a/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/Stats/DataStoreRefundFiltersTest.php
+++ b/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/Stats/DataStoreRefundFiltersTest.php
@@ -60,6 +60,10 @@ class DataStoreRefundFiltersTest extends OrdersStatsTestCase {
 			$order->set_shipping_total( 0 );
 			$order->set_cart_tax( 0 );
 			$order->save();
+
+			// The refunded order's save creates a refund of its own, via
+			// wc_order_fully_refunded(); it needs the fixture's date like any other.
+			$this->pin_refund_dates( $order, $time );
 		}

 		// Add a partial refund on the first item of the last order.
@@ -77,6 +81,8 @@ class DataStoreRefundFiltersTest extends OrdersStatsTestCase {
 			)
 		);

+		$this->pin_refund_dates( $order, $time );
+
 		WC_Helper_Queue::run_all_pending( 'wc-admin-data' );

 		$data_store = new OrdersStatsDataStore();
diff --git a/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/Stats/OrdersStatsTestCase.php b/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/Stats/OrdersStatsTestCase.php
index c30666a8f97..f06280fda58 100644
--- a/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/Stats/OrdersStatsTestCase.php
+++ b/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/Stats/OrdersStatsTestCase.php
@@ -111,4 +111,24 @@ abstract class OrdersStatsTestCase extends WC_Unit_Test_Case {
 			'Query args: ' . print_r( $query_args, true ) . "; query: {$wpdb->last_query}" // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
 		);
 	}
+
+	/**
+	 * Give an order's refunds the same date as the order itself.
+	 *
+	 * A refund reaches the report through its own date: the lookup table backfills a refund
+	 * row's date_paid from the refund's date_created whenever the parent order has a date_paid.
+	 * Neither `wc_create_refund()` nor the refund `wc_order_fully_refunded()` creates on a
+	 * `refunded` status change takes a date, so both use the wall clock. A fixture that pins its
+	 * order to a fixed hour and then queries that hour loses those refunds when the clock has
+	 * moved on, which for a plain `time()` fixture means whenever the run crosses the hour.
+	 *
+	 * @param \WC_Order  $order The order whose refunds should be pinned.
+	 * @param int|string $date  Date to apply, in any form set_date_created() accepts.
+	 */
+	protected function pin_refund_dates( \WC_Order $order, $date ): void {
+		foreach ( $order->get_refunds() as $refund ) {
+			$refund->set_date_created( $date );
+			$refund->save();
+		}
+	}
 }