Commit 028397d2398 for woocommerce
commit 028397d239893519ea94142ba0bd8e7228de7d72
Author: Vlad Olaru <vlad.olaru@automattic.com>
Date: Sat Sep 12 23:13:29 2026 +0300
[tests] Fix the hour-boundary flake in the Orders Stats report tests (#68665)
test(admin): Pin date_paid on Orders Stats report test fixtures
The Orders Stats report queries the `date_paid` column by default:
`Admin\API\Reports\Orders\Stats\DataStore::__construct` resolves
`date_column_name` from the `woocommerce_date_type` option and falls
back to `date_paid`, and no test sets that option.
These fixtures pinned `date_created` to a deterministic timestamp but
left `date_paid` alone. Reaching a paid status stamps it with the wall
clock instead: `WC_Order::status_transition()` calls
`maybe_set_date_paid()`, which sets `time()` when the prop is empty.
The assertions then query a window derived from `date_created`, so a
run whose fixture creation crosses the top of the hour leaves some
orders paid in the next hour and outside that window. In
`DataStoreFilterQueriesTest` that dropped 18 of 36 orders and failed 27
of the 28 scenarios at once.
Set `date_paid` alongside `date_created` wherever a fixture pins one,
which is what the sibling `DataStoreZeroFillTest` already does. The
fixtures keep exercising the default `date_paid` path; they just no
longer depend on how long they take to build.
Only `DataStoreFilterQueriesTest` has been observed failing. The two
sites in `DataStoreBasicsTest` have the same cause and a narrower
window, and are fixed here rather than left to surface later.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/fix-orders-stats-report-test-hour-boundary b/plugins/woocommerce/changelog/fix-orders-stats-report-test-hour-boundary
new file mode 100644
index 00000000000..430ac44b245
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-orders-stats-report-test-hour-boundary
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Pin date_paid on the Orders Stats report test fixtures so a run that crosses the top of the hour no longer drops orders out of the queried window.
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 837b8bfaec7..4e554a7b9fa 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
@@ -38,6 +38,9 @@ class DataStoreBasicsTest extends OrdersStatsTestCase {
$coupon->save();
$order = WC_Helper_Order::create_order( 1, $product );
+ // Pin date_paid to date_created, which the window below is derived from; otherwise
+ // the two can land in different hours when the save crosses the top of the hour.
+ $order->set_date_paid( $order->get_date_created() );
$order->set_status( OrderStatus::COMPLETED );
$order->set_shipping_total( 10 );
$order->apply_coupon( $coupon );
@@ -319,14 +322,18 @@ class DataStoreBasicsTest extends OrdersStatsTestCase {
$order_datetime->setTime( (int) $order_datetime->format( 'H' ), 10, 0 );
$order_time = (int) $order_datetime->format( 'U' );
+ // Both orders pin date_paid alongside date_created, because the report queries
+ // date_paid and the window below comes from date_created.
$customer_1_order = WC_Helper_Order::create_order( $customer_1->get_id() );
$customer_1_order->set_date_created( $order_time );
+ $customer_1_order->set_date_paid( $order_time );
$customer_1_order->set_status( OrderStatus::COMPLETED );
$customer_1_order->save();
// Offset by 1 second to keep both orders in the same hour but distinct.
$customer_2_order = WC_Helper_Order::create_order( $customer_2->get_id() );
$customer_2_order->set_date_created( $order_time + 1 );
+ $customer_2_order->set_date_paid( $order_time + 1 );
$customer_2_order->set_status( OrderStatus::COMPLETED );
$customer_2_order->save();
diff --git a/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/Stats/DataStoreFilterQueriesTest.php b/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/Stats/DataStoreFilterQueriesTest.php
index e3f4b2b94eb..f864244e06a 100644
--- a/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/Stats/DataStoreFilterQueriesTest.php
+++ b/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/Stats/DataStoreFilterQueriesTest.php
@@ -133,12 +133,19 @@ class DataStoreFilterQueriesTest extends OrdersStatsTestCase {
$order_time = (int) $order_datetime->format( 'U' );
$iterations = 1;
+ // The Orders Stats report queries date_paid by default, so each order pins it
+ // alongside date_created. Left unset, reaching a paid status stamps date_paid with
+ // the wall clock, and a run that crosses the hour puts orders outside the window
+ // the assertions query.
foreach ( $primary_products as $product ) {
foreach ( array( null, $small_coupon, $large_coupon ) as $coupon ) {
foreach ( array( OrderStatus::COMPLETED, OrderStatus::PROCESSING ) as $order_status ) {
- $single_product_order = WC_Helper_Order::create_order( $customer->get_id(), $product );
// Offset each order by 1 second.
- $single_product_order->set_date_created( $order_time + $iterations++ );
+ $single_order_time = $order_time + $iterations++;
+
+ $single_product_order = WC_Helper_Order::create_order( $customer->get_id(), $product );
+ $single_product_order->set_date_created( $single_order_time );
+ $single_product_order->set_date_paid( $single_order_time );
$single_product_order->set_status( $order_status );
if ( $coupon ) {
@@ -161,7 +168,9 @@ class DataStoreFilterQueriesTest extends OrdersStatsTestCase {
$item->save();
$two_product_order->add_item( $item );
// Offset each order by 1 second.
- $two_product_order->set_date_created( $order_time + $iterations++ );
+ $two_order_time = $order_time + $iterations++;
+ $two_product_order->set_date_created( $two_order_time );
+ $two_product_order->set_date_paid( $two_order_time );
$two_product_order->set_status( $order_status );
if ( $coupon ) {