Commit 450556248bf for woocommerce
commit 450556248bf0abb3e076dc822486de0b60b18665
Author: Chi-Hsuan Huang <chihsuan.tw@gmail.com>
Date: Mon Mar 9 10:33:25 2026 +0800
Exclude test orders from analytics data (#63550)
* feat: exclude test orders from analytics data
Add is_test_order() check in OrdersScheduler::import() and
OrdersStatsDataStore::update() to prevent WCPay test orders
(with _wcpay_mode = 'test') from entering analytics lookup tables.
Extensible via woocommerce_analytics_is_test_order filter.
Refund orders check their parent order's meta.
WOOA7S-1069
* Add changefile(s) from automation for the following project(s): woocommerce
* Update phpstan-baseline.neon
* fix: update PHPDoc param descriptions for get_num_items_sold and get_net_total
* fix: suppress PHPCS MissingShort for inline PHPStan type hint
* fix: add short description to inline @var docblock to fix PHPCS lint
* fix: update phpstan-baseline.neon for resolved DataStore type errors
* fix: add use stdClass import to resolve PHPStan namespace errors
* Regenerate baseline
* fix: address code review feedback for test order exclusion
- Add @since 10.7.0 and refund behavior docs to is_test_order()
- Fix filter @since placement and add use-case context
- Add debug logging when test orders are skipped
- Add defense-in-depth comment in DataStore::update()
- Convert test docblocks to @testdox with void return types
- Add integration tests for DataStore update/sync_order guards
- Add orphan refund and filter parent order contract tests
* fix: add instanceof guard in is_test_order()
Validate input is WC_Abstract_Order before dereferencing,
and use instanceof for wc_get_order() return check.
* fix: resolve PHPCS lint errors in DataStore and OrdersSchedulerTest
* Fix lint
---------
Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>
diff --git a/plugins/woocommerce/changelog/63550-wooa7s-1069-remove-test-orders-from-analytics-data b/plugins/woocommerce/changelog/63550-wooa7s-1069-remove-test-orders-from-analytics-data
new file mode 100644
index 00000000000..c1ae907e318
--- /dev/null
+++ b/plugins/woocommerce/changelog/63550-wooa7s-1069-remove-test-orders-from-analytics-data
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Exclude WCPay test orders from analytics data to prevent test transactions from polluting reports.
\ No newline at end of file
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 1285a474439..d1bb250ae20 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -19146,7 +19146,6 @@ parameters:
count: 1
path: includes/emails/class-wc-email-customer-pos-refunded-order.php
-
-
message: '#^Method WC_Email_Customer_Processing_Order\:\:trigger\(\) has no return type specified\.$#'
identifier: missingType.return
@@ -43732,114 +43731,18 @@ parameters:
count: 4
path: src/Admin/API/Reports/Orders/Stats/DataStore.php
- -
- message: '#^@param WC_Order \$order does not accept actual type of parameter\: Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WC_Order_Refund\|WC_Order\.$#'
- identifier: parameter.phpDocType
- count: 1
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
-
message: '#^Access to property \$intervals on an unknown class Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WP_Error\.$#'
identifier: class.notFound
count: 3
path: src/Admin/API/Reports/Orders/Stats/DataStore.php
- -
- message: '#^Access to property \$intervals on an unknown class Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\stdClass\.$#'
- identifier: class.notFound
- count: 7
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Access to property \$totals on an unknown class Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\stdClass\.$#'
- identifier: class.notFound
- count: 2
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
-
message: '#^Binary operation "\*" between \-1 and string results in an error\.$#'
identifier: binaryOp.invalid
count: 1
path: src/Admin/API/Reports/Orders/Stats/DataStore.php
- -
- message: '#^Call to method get_date_completed\(\) on an unknown class Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WC_Order_Refund\.$#'
- identifier: class.notFound
- count: 2
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Call to method get_date_created\(\) on an unknown class Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WC_Order_Refund\.$#'
- identifier: class.notFound
- count: 3
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Call to method get_date_paid\(\) on an unknown class Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WC_Order_Refund\.$#'
- identifier: class.notFound
- count: 2
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Call to method get_id\(\) on an unknown class Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WC_Order_Refund\.$#'
- identifier: class.notFound
- count: 3
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Call to method get_meta\(\) on an unknown class Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WC_Order_Refund\.$#'
- identifier: class.notFound
- count: 1
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Call to method get_parent_id\(\) on an unknown class Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WC_Order_Refund\.$#'
- identifier: class.notFound
- count: 2
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Call to method get_report_customer_id\(\) on an unknown class Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WC_Order_Refund\.$#'
- identifier: class.notFound
- count: 1
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Call to method get_shipping_total\(\) on an unknown class Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WC_Order_Refund\.$#'
- identifier: class.notFound
- count: 1
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Call to method get_status\(\) on an unknown class Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WC_Order_Refund\.$#'
- identifier: class.notFound
- count: 1
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Call to method get_total\(\) on an unknown class Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WC_Order_Refund\.$#'
- identifier: class.notFound
- count: 1
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Call to method get_total_tax\(\) on an unknown class Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WC_Order_Refund\.$#'
- identifier: class.notFound
- count: 1
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Call to method get_type\(\) on an unknown class Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WC_Order_Refund\.$#'
- identifier: class.notFound
- count: 1
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Call to method is_returning_customer\(\) on an unknown class Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WC_Order_Refund\.$#'
- identifier: class.notFound
- count: 1
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
-
message: '#^Method Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\DataStore\:\:assign_report_columns\(\) has no return type specified\.$#'
identifier: missingType.return
@@ -43865,7 +43768,7 @@ parameters:
path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- message: '#^Method Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\DataStore\:\:get_noncached_data\(\) should return Automattic\\WooCommerce\\Admin\\API\\Reports\\stdClass\|Automattic\\WooCommerce\\Admin\\API\\Reports\\WP_Error but returns Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\stdClass\|Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WP_Error\|\(object\{totals\: null, intervals\: array, total\: int, pages\: int, page_no\: int\}&stdClass\)\.$#'
+ message: '#^Method Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\DataStore\:\:get_noncached_data\(\) should return Automattic\\WooCommerce\\Admin\\API\\Reports\\stdClass\|Automattic\\WooCommerce\\Admin\\API\\Reports\\WP_Error but returns Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WP_Error\|stdClass\.$#'
identifier: return.type
count: 1
path: src/Admin/API/Reports/Orders/Stats/DataStore.php
@@ -43877,19 +43780,13 @@ parameters:
path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- message: '#^Method Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\DataStore\:\:get_noncached_stats_data\(\) has invalid return type Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\stdClass\.$#'
- identifier: class.notFound
- count: 1
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Method Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\DataStore\:\:get_noncached_stats_data\(\) should return Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\stdClass\|Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WP_Error but returns Automattic\\WooCommerce\\Admin\\API\\Reports\\stdClass\.$#'
+ message: '#^Method Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\DataStore\:\:get_noncached_stats_data\(\) should return Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WP_Error\|stdClass but returns Automattic\\WooCommerce\\Admin\\API\\Reports\\stdClass\.$#'
identifier: return.type
count: 1
path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- message: '#^Method Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\DataStore\:\:get_noncached_stats_data\(\) should return Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\stdClass\|Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WP_Error but returns WP_Error\.$#'
+ message: '#^Method Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\DataStore\:\:get_noncached_stats_data\(\) should return Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WP_Error\|stdClass but returns WP_Error\.$#'
identifier: return.type
count: 2
path: src/Admin/API/Reports/Orders/Stats/DataStore.php
@@ -43937,7 +43834,7 @@ parameters:
path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- message: '#^Parameter \#1 \$data of method Automattic\\WooCommerce\\Admin\\API\\Reports\\Segmenter\:\:add_intervals_segments\(\) expects Automattic\\WooCommerce\\Admin\\API\\Reports\\stdClass, Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\stdClass\|Automattic\\WooCommerce\\Admin\\API\\Reports\\stdClass given\.$#'
+ message: '#^Parameter \#1 \$data of method Automattic\\WooCommerce\\Admin\\API\\Reports\\Segmenter\:\:add_intervals_segments\(\) expects Automattic\\WooCommerce\\Admin\\API\\Reports\\stdClass, Automattic\\WooCommerce\\Admin\\API\\Reports\\stdClass\|stdClass given\.$#'
identifier: argument.type
count: 1
path: src/Admin/API/Reports/Orders/Stats/DataStore.php
@@ -43949,59 +43846,11 @@ parameters:
path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- message: '#^Parameter \#1 \$order of static method Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\DataStore\:\:get_net_total\(\) expects WC_Order, Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WC_Order_Refund\|WC_Order given\.$#'
- identifier: argument.type
- count: 1
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Parameter \#1 \$order of static method Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\DataStore\:\:get_net_total\(\) expects WC_Order, WC_Order\|WC_Order_Refund given\.$#'
+ message: '#^Parameter \#5 \$data of method Automattic\\WooCommerce\\Admin\\API\\Reports\\DataStore\:\:fill_in_missing_intervals\(\) expects Automattic\\WooCommerce\\Admin\\API\\Reports\\stdClass, stdClass given\.$#'
identifier: argument.type
count: 1
path: src/Admin/API/Reports/Orders/Stats/DataStore.php
- -
- message: '#^Parameter \#1 \$order of static method Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\DataStore\:\:get_num_items_sold\(\) expects WC_Order, Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WC_Order_Refund\|WC_Order given\.$#'
- identifier: argument.type
- count: 1
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Parameter \#1 \$order of static method Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\DataStore\:\:get_num_items_sold\(\) expects WC_Order, WC_Order\|WC_Order_Refund given\.$#'
- identifier: argument.type
- count: 1
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Parameter \#1 \$order of static method Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\DataStore\:\:update\(\) expects Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WC_Order_Refund\|WC_Order, WC_Order\|WC_Order_Refund given\.$#'
- identifier: argument.type
- count: 1
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Parameter \#3 \$data of method Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\DataStore\:\:get_noncached_stats_data\(\) expects Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\stdClass, object\{totals\: null, intervals\: array, total\: int, pages\: int, page_no\: int\}&stdClass given\.$#'
- identifier: argument.type
- count: 1
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Parameter \#5 \$data of method Automattic\\WooCommerce\\Admin\\API\\Reports\\DataStore\:\:fill_in_missing_intervals\(\) expects Automattic\\WooCommerce\\Admin\\API\\Reports\\stdClass, Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\stdClass given\.$#'
- identifier: argument.type
- count: 1
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Parameter \$data of method Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\DataStore\:\:get_noncached_stats_data\(\) has invalid type Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\stdClass\.$#'
- identifier: class.notFound
- count: 1
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
- -
- message: '#^Parameter \$order of method Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\DataStore\:\:update\(\) has invalid type Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\Stats\\WC_Order_Refund\.$#'
- identifier: class.notFound
- count: 1
- path: src/Admin/API/Reports/Orders/Stats/DataStore.php
-
-
message: '#^Variable \$segments might not be defined\.$#'
identifier: variable.undefined
diff --git a/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php b/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php
index ab450f144ec..93e6cadadc6 100644
--- a/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php
+++ b/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php
@@ -14,10 +14,15 @@ use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
use Automattic\WooCommerce\Admin\API\Reports\Cache as ReportsCache;
use Automattic\WooCommerce\Admin\API\Reports\Customers\DataStore as CustomersDataStore;
+use Automattic\WooCommerce\Internal\Admin\Schedulers\OrdersScheduler;
use Automattic\WooCommerce\Utilities\OrderUtil;
use Automattic\WooCommerce\Admin\API\Reports\StatsDataStoreTrait;
use Automattic\WooCommerce\Utilities\FeaturesUtil;
+use stdClass;
use WC_Order;
+use WC_Order_Refund;
+use Automattic\WooCommerce\Admin\Overrides\Order;
+use Automattic\WooCommerce\Admin\Overrides\OrderRefund;
/**
* API\Reports\Orders\Stats\DataStore.
@@ -501,6 +506,11 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
return -1;
}
+ /**
+ * The order instance to be synchronized.
+ *
+ * @var false|Order|OrderRefund $order Order instance (Override classes registered via OrdersScheduler::init()).
+ */
$order = wc_get_order( $post_id );
if ( ! $order ) {
return -1;
@@ -512,7 +522,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
/**
* Update the database with stats data.
*
- * @param WC_Order|WC_Order_Refund $order Order or refund to update row for.
+ * @param Order|OrderRefund $order Order or refund to update row for.
* @return int|bool Returns -1 if order won't be processed, or a boolean indicating processing success.
*/
public static function update( $order ) {
@@ -523,6 +533,13 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
return -1;
}
+ // Exclude test orders (e.g., WCPay test mode) from analytics stats.
+ // Defense-in-depth: also checked in OrdersScheduler::import(), but
+ // update() can be called directly outside of the import flow.
+ if ( OrdersScheduler::is_test_order( $order ) ) {
+ return -1;
+ }
+
$format = array(
'%d',
'%d',
@@ -568,7 +585,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
* Filters order stats data.
*
* @param array $data Data written to order stats lookup table.
- * @param WC_Order $order Order object.
+ * @param Order|OrderRefund $order Order object.
*
* @since 4.0.0
*/
@@ -653,7 +670,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
/**
* Get number of items sold among all orders.
*
- * @param WC_Order $order WC_Order object.
+ * @param WC_Order|WC_Order_Refund $order WC_Order or WC_Order_Refund object.
* @return int
*/
protected static function get_num_items_sold( $order ) {
@@ -670,7 +687,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
/**
* Get the net amount from an order without shipping, tax, or refunds.
*
- * @param WC_Order $order WC_Order object.
+ * @param WC_Order|WC_Order_Refund $order WC_Order or WC_Order_Refund object.
* @return float
*/
protected static function get_net_total( $order ) {
diff --git a/plugins/woocommerce/src/Internal/Admin/Schedulers/OrdersScheduler.php b/plugins/woocommerce/src/Internal/Admin/Schedulers/OrdersScheduler.php
index c6fa2384b2d..577f57c7746 100644
--- a/plugins/woocommerce/src/Internal/Admin/Schedulers/OrdersScheduler.php
+++ b/plugins/woocommerce/src/Internal/Admin/Schedulers/OrdersScheduler.php
@@ -346,6 +346,15 @@ AND status NOT IN ( 'wc-auto-draft', 'trash', 'auto-draft' )
return;
}
+ // Skip test orders (e.g., WCPay test mode) from analytics.
+ if ( self::is_test_order( $order ) ) {
+ wc_get_logger()->debug(
+ sprintf( 'Skipping test order #%d from analytics import.', $order_id ),
+ array( 'source' => 'wc-analytics-order-import' )
+ );
+ return;
+ }
+
$results = array(
OrdersStatsDataStore::sync_order( $order_id ),
ProductsDataStore::sync_order_products( $order_id ),
@@ -684,6 +693,48 @@ AND status NOT IN ( 'wc-auto-draft', 'trash', 'auto-draft' )
);
}
+ /**
+ * Check if an order is a test order that should be excluded from analytics.
+ *
+ * For refunds, the parent order is checked instead, since refunds do not
+ * carry the test mode metadata directly.
+ *
+ * @param \WC_Abstract_Order $order Order object.
+ * @return bool
+ *
+ * @since 10.7.0
+ */
+ public static function is_test_order( $order ) {
+ if ( ! $order instanceof \WC_Abstract_Order ) {
+ return false;
+ }
+
+ // For refunds, check the parent order.
+ $check_order = $order;
+ if ( 'shop_order_refund' === $order->get_type() ) {
+ $check_order = wc_get_order( $order->get_parent_id() );
+ if ( ! $check_order instanceof \WC_Abstract_Order ) {
+ return false;
+ }
+ }
+
+ $is_test = 'test' === $check_order->get_meta( '_wcpay_mode' );
+
+ /**
+ * Filter whether an order is a test order excluded from analytics.
+ *
+ * Use this filter to customize test order detection beyond the default
+ * WCPay test mode check, e.g., to exclude orders from other payment
+ * gateways' test/sandbox modes.
+ *
+ * @param bool $is_test Whether the order is a test order.
+ * @param \WC_Abstract_Order $order The order being checked (for refunds, this is the parent order).
+ *
+ * @since 10.7.0
+ */
+ return apply_filters( 'woocommerce_analytics_is_test_order', $is_test, $check_order );
+ }
+
/**
* Delete a batch of orders.
*
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Schedulers/OrdersSchedulerTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Schedulers/OrdersSchedulerTest.php
index a92b32ca4a9..f9191930378 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/Schedulers/OrdersSchedulerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Schedulers/OrdersSchedulerTest.php
@@ -4,6 +4,7 @@ declare( strict_types=1 );
namespace Automattic\WooCommerce\Tests\Internal\Admin\Schedulers;
use Automattic\WooCommerce\Internal\Admin\Schedulers\OrdersScheduler;
+use Automattic\WooCommerce\Admin\API\Reports\Orders\Stats\DataStore as OrdersStatsDataStore;
use WC_Unit_Test_Case;
use Automattic\WooCommerce\Admin\Features\Features;
@@ -205,6 +206,187 @@ class OrdersSchedulerTest extends WC_Unit_Test_Case {
);
}
+ /**
+ * @testdox Should identify order with _wcpay_mode test as a test order.
+ */
+ public function test_is_test_order_with_wcpay_test_mode(): void {
+ $order = \WC_Helper_Order::create_order();
+ $order->update_meta_data( '_wcpay_mode', 'test' );
+ $order->save();
+
+ $this->assertTrue( OrdersScheduler::is_test_order( $order ) );
+ }
+
+ /**
+ * @testdox Should not identify a normal order as a test order.
+ */
+ public function test_is_test_order_with_normal_order(): void {
+ $order = \WC_Helper_Order::create_order();
+
+ $this->assertFalse( OrdersScheduler::is_test_order( $order ) );
+ }
+
+ /**
+ * @testdox Should not identify an order with _wcpay_mode live as a test order.
+ */
+ public function test_is_test_order_with_wcpay_live_mode(): void {
+ $order = \WC_Helper_Order::create_order();
+ $order->update_meta_data( '_wcpay_mode', 'live' );
+ $order->save();
+
+ $this->assertFalse( OrdersScheduler::is_test_order( $order ) );
+ }
+
+ /**
+ * @testdox Should identify a refund of a test order as a test order.
+ */
+ public function test_is_test_order_with_refund_of_test_order(): void {
+ $order = \WC_Helper_Order::create_order();
+ $order->update_meta_data( '_wcpay_mode', 'test' );
+ $order->save();
+
+ $refund = wc_create_refund(
+ array(
+ 'order_id' => $order->get_id(),
+ 'amount' => 10,
+ 'reason' => 'Test refund',
+ )
+ );
+
+ $this->assertTrue( OrdersScheduler::is_test_order( $refund ) );
+ }
+
+ /**
+ * @testdox Should not identify a refund of a normal order as a test order.
+ */
+ public function test_is_test_order_with_refund_of_normal_order(): void {
+ $order = \WC_Helper_Order::create_order();
+
+ $refund = wc_create_refund(
+ array(
+ 'order_id' => $order->get_id(),
+ 'amount' => 10,
+ 'reason' => 'Test refund',
+ )
+ );
+
+ $this->assertFalse( OrdersScheduler::is_test_order( $refund ) );
+ }
+
+ /**
+ * @testdox Should allow the woocommerce_analytics_is_test_order filter to mark a normal order as a test order.
+ */
+ public function test_is_test_order_filter_can_override(): void {
+ $order = \WC_Helper_Order::create_order();
+
+ // Order has no _wcpay_mode meta, so default is false.
+ $this->assertFalse( OrdersScheduler::is_test_order( $order ) );
+
+ // Override via filter to mark it as test.
+ add_filter( 'woocommerce_analytics_is_test_order', '__return_true' );
+
+ $this->assertTrue( OrdersScheduler::is_test_order( $order ) );
+
+ remove_filter( 'woocommerce_analytics_is_test_order', '__return_true' );
+ }
+
+ /**
+ * @testdox Should allow the woocommerce_analytics_is_test_order filter to include a test order in analytics.
+ */
+ public function test_is_test_order_filter_can_allow_test_order(): void {
+ $order = \WC_Helper_Order::create_order();
+ $order->update_meta_data( '_wcpay_mode', 'test' );
+ $order->save();
+
+ // Default is true because _wcpay_mode is 'test'.
+ $this->assertTrue( OrdersScheduler::is_test_order( $order ) );
+
+ // Override via filter to allow it.
+ add_filter( 'woocommerce_analytics_is_test_order', '__return_false' );
+
+ $this->assertFalse( OrdersScheduler::is_test_order( $order ) );
+
+ remove_filter( 'woocommerce_analytics_is_test_order', '__return_false' );
+ }
+
+ /**
+ * @testdox Should return false for a refund whose parent order has been deleted.
+ */
+ public function test_is_test_order_with_orphaned_refund(): void {
+ $order = \WC_Helper_Order::create_order();
+ $order->update_meta_data( '_wcpay_mode', 'test' );
+ $order->save();
+
+ $refund = wc_create_refund(
+ array(
+ 'order_id' => $order->get_id(),
+ 'amount' => 10,
+ 'reason' => 'Test refund',
+ )
+ );
+
+ // Delete the parent order to create an orphaned refund.
+ $order->delete( true );
+
+ $this->assertFalse( OrdersScheduler::is_test_order( $refund ) );
+ }
+
+ /**
+ * @testdox Should pass the parent order to the filter when checking a refund.
+ */
+ public function test_is_test_order_filter_receives_parent_order_for_refund(): void {
+ $order = \WC_Helper_Order::create_order();
+ $order->save();
+
+ $refund = wc_create_refund(
+ array(
+ 'order_id' => $order->get_id(),
+ 'amount' => 10,
+ 'reason' => 'Test refund',
+ )
+ );
+
+ $received_order = null;
+ $filter_callback = function ( $is_test, $filter_order ) use ( &$received_order ) {
+ $received_order = $filter_order;
+ return $is_test;
+ };
+ add_filter( 'woocommerce_analytics_is_test_order', $filter_callback, 10, 2 );
+
+ OrdersScheduler::is_test_order( $refund );
+
+ $this->assertNotNull( $received_order );
+ $this->assertEquals( $order->get_id(), $received_order->get_id() );
+
+ remove_filter( 'woocommerce_analytics_is_test_order', $filter_callback );
+ }
+
+ /**
+ * @testdox Should return -1 from DataStore update when given a test order.
+ */
+ public function test_datastore_update_skips_test_order(): void {
+ $order = \WC_Helper_Order::create_order();
+ $order->update_meta_data( '_wcpay_mode', 'test' );
+ $order->save();
+
+ $result = OrdersStatsDataStore::update( $order );
+
+ $this->assertSame( -1, $result );
+ }
+
+ /**
+ * @testdox Should return -1 from DataStore sync_order when given a test order ID.
+ */
+ public function test_datastore_sync_order_skips_test_order(): void {
+ $order = \WC_Helper_Order::create_order();
+ $order->update_meta_data( '_wcpay_mode', 'test' );
+ $order->save();
+
+ $result = OrdersStatsDataStore::sync_order( $order->get_id() );
+
+ $this->assertSame( -1, $result );
+ }
+
/**
* Clear any scheduled batch processor actions.
*