diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php index 4ce7f57837a..af4929c2ac3 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php @@ -37,6 +37,11 @@ class WC_Admin_Api_Init { */ const ORDERS_LOOKUP_BATCH_INIT = 'wc-admin_orders_lookup_batch_init'; + /** + * Action hook for processing a batch of orders. + */ + const SINGLE_ORDER_ACTION = 'wc-admin_process_order'; + /** * Queue instance. * @@ -59,19 +64,16 @@ class WC_Admin_Api_Init { add_filter( 'rest_endpoints', array( 'WC_Admin_Api_Init', 'filter_rest_endpoints' ), 10, 1 ); add_filter( 'woocommerce_debug_tools', array( 'WC_Admin_Api_Init', 'add_regenerate_tool' ) ); - // Initialize Orders data store class's static vars. - add_action( 'woocommerce_after_register_post_type', array( 'WC_Admin_Api_Init', 'orders_data_store_init' ), 20 ); - // Initialize Customers Report data store sync hooks. - // Note: we need to hook in before `wc_current_user_is_active`. - // See: https://github.com/woocommerce/woocommerce/blob/942615101ba00c939c107c3a4820c3d466864872/includes/wc-user-functions.php#L749. - add_action( 'wp_loaded', array( 'WC_Admin_Api_Init', 'customers_report_data_store_init' ) ); + // Initialize syncing hooks. + add_action( 'wp_loaded', array( __CLASS__, 'orders_lookup_update_init' ) ); // Initialize scheduled action handlers. add_action( self::QUEUE_BATCH_ACTION, array( __CLASS__, 'queue_batches' ), 10, 3 ); - add_action( self::QUEUE_DEPEDENT_ACTION, array( __CLASS__, 'queue_dependent_action' ), 10, 2 ); + add_action( self::QUEUE_DEPEDENT_ACTION, array( __CLASS__, 'queue_dependent_action' ), 10, 3 ); add_action( self::CUSTOMERS_BATCH_ACTION, array( __CLASS__, 'customer_lookup_process_batch' ) ); add_action( self::ORDERS_BATCH_ACTION, array( __CLASS__, 'orders_lookup_process_batch' ) ); add_action( self::ORDERS_LOOKUP_BATCH_INIT, array( __CLASS__, 'orders_lookup_batch_init' ) ); + add_action( self::SINGLE_ORDER_ACTION, array( __CLASS__, 'orders_lookup_process_order' ) ); // Add currency symbol to orders endpoint response. add_filter( 'woocommerce_rest_prepare_shop_order_object', array( __CLASS__, 'add_currency_symbol_to_order_response' ) ); @@ -433,7 +435,7 @@ class WC_Admin_Api_Init { // so that the orders can be associated with the `customer_id` column. self::customer_lookup_batch_init(); // Queue orders lookup to occur after customers lookup generation is done. - self::queue_dependent_action( self::ORDERS_LOOKUP_BATCH_INIT, self::CUSTOMERS_BATCH_ACTION ); + self::queue_dependent_action( self::ORDERS_LOOKUP_BATCH_INIT, array(), self::CUSTOMERS_BATCH_ACTION ); } /** @@ -457,16 +459,58 @@ class WC_Admin_Api_Init { } /** - * Init orders data store. + * Schedule an action to process a single Order. + * + * @param int $order_id Order ID. + * @return void */ - public static function orders_data_store_init() { + public static function schedule_single_order_process( $order_id ) { + if ( 'shop_order' !== get_post_type( $order_id ) ) { + return; + } + + // This can get called multiple times for a single order, so we look + // for existing pending jobs for the same order to avoid duplicating efforts. + $existing_jobs = self::queue()->search( + array( + 'status' => 'pending', + 'per_page' => 1, + 'claimed' => false, + 'search' => "[{$order_id}]", + ) + ); + + if ( $existing_jobs ) { + $existing_job = current( $existing_jobs ); + + // Bail out if there's a pending single order action, or a pending dependent action. + if ( + ( self::SINGLE_ORDER_ACTION === $existing_job->get_hook() ) || + ( + self::QUEUE_DEPEDENT_ACTION === $existing_job->get_hook() && + in_array( self::SINGLE_ORDER_ACTION, $existing_job->get_args() ) + ) + ) { + return; + } + } + + // We want to ensure that customer lookup updates are scheduled before order updates. + self::queue_dependent_action( self::SINGLE_ORDER_ACTION, array( $order_id ), self::CUSTOMERS_BATCH_ACTION ); + } + + /** + * Attach order lookup update hooks. + */ + public static function orders_lookup_update_init() { // Activate WC_Order extension. WC_Admin_Order::add_filters(); - // Initialize data stores. + + add_action( 'save_post_shop_order', array( __CLASS__, 'schedule_single_order_process' ) ); + add_action( 'woocommerce_order_refunded', array( __CLASS__, 'schedule_single_order_process' ) ); + WC_Admin_Reports_Orders_Stats_Data_Store::init(); - WC_Admin_Reports_Products_Data_Store::init(); - WC_Admin_Reports_Taxes_Data_Store::init(); - WC_Admin_Reports_Coupons_Data_Store::init(); + WC_Admin_Reports_Customers_Data_Store::init(); } /** @@ -512,19 +556,35 @@ class WC_Admin_Api_Init { $order_ids = $order_query->get_orders(); foreach ( $order_ids as $order_id ) { - // @todo: schedule single order update if this fails? - WC_Admin_Reports_Orders_Stats_Data_Store::sync_order( $order_id ); - WC_Admin_Reports_Products_Data_Store::sync_order_products( $order_id ); - WC_Admin_Reports_Coupons_Data_Store::sync_order_coupons( $order_id ); - WC_Admin_Reports_Taxes_Data_Store::sync_order_taxes( $order_id ); + self::orders_lookup_process_order( $order_id ); } } /** - * Init customers report data store. + * Process a single order to update lookup tables for. + * If an error is encountered in one of the updates, a retry action is scheduled. + * + * @param int $order_id Order ID. + * @return void */ - public static function customers_report_data_store_init() { - WC_Admin_Reports_Customers_Data_Store::init(); + public static function orders_lookup_process_order( $order_id ) { + $result = array_sum( + array( + WC_Admin_Reports_Orders_Stats_Data_Store::sync_order( $order_id ), + WC_Admin_Reports_Products_Data_Store::sync_order_products( $order_id ), + WC_Admin_Reports_Coupons_Data_Store::sync_order_coupons( $order_id ), + WC_Admin_Reports_Taxes_Data_Store::sync_order_taxes( $order_id ), + ) + ); + + // If all updates were either skipped or successful, we're done. + // The update methods return -1 for skip, or a boolean success indicator. + if ( 4 === absint( $result ) ) { + return; + } + + // Otherwise assume an error occurred and reschedule. + self::schedule_single_order_process( $order_id ); } /** @@ -592,9 +652,10 @@ class WC_Admin_Api_Init { * Queue an action to run after another. * * @param string $action Action to run after prerequisite. + * @param array $action_args Action arguments. * @param string $prerequisite_action Prerequisite action. */ - public static function queue_dependent_action( $action, $prerequisite_action ) { + public static function queue_dependent_action( $action, $action_args, $prerequisite_action ) { $blocking_jobs = self::queue()->search( array( 'status' => 'pending', @@ -613,10 +674,10 @@ class WC_Admin_Api_Init { self::queue()->schedule_single( $after_blocking_job, self::QUEUE_DEPEDENT_ACTION, - array( $action, $prerequisite_action ) + array( $action, $action_args, $prerequisite_action ) ); } else { - self::queue()->schedule_single( time() + 5, $action ); + self::queue()->schedule_single( time() + 5, $action, $action_args ); } } diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-coupons-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-coupons-data-store.php index 934ad18fcc6..337f12cdbc7 100644 --- a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-coupons-data-store.php +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-coupons-data-store.php @@ -51,15 +51,6 @@ class WC_Admin_Reports_Coupons_Data_Store extends WC_Admin_Reports_Data_Store im $this->report_columns['orders_count'] = str_replace( 'order_id', $table_name . '.order_id', $this->report_columns['orders_count'] ); } - /** - * Set up all the hooks for maintaining and populating table data. - */ - public static function init() { - add_action( 'save_post', array( __CLASS__, 'sync_order_coupons' ) ); - add_action( 'clean_post_cache', array( __CLASS__, 'sync_order_coupons' ) ); - add_action( 'woocommerce_order_refunded', array( __CLASS__, 'sync_order_coupons' ) ); - } - /** * Returns comma separated ids of included coupons, based on query arguments from the user. * @@ -315,19 +306,21 @@ class WC_Admin_Reports_Coupons_Data_Store extends WC_Admin_Reports_Data_Store im * * @since 3.5.0 * @param int $order_id Order ID. - * @return void + * @return int|bool Returns -1 if order won't be processed, or a boolean indicating processing success. */ public static function sync_order_coupons( $order_id ) { global $wpdb; $order = wc_get_order( $order_id ); if ( ! $order ) { - return; + return -1; } $coupon_items = $order->get_items( 'coupon' ); + $num_updated = 0; + foreach ( $coupon_items as $coupon_item ) { - $wpdb->replace( + $result = $wpdb->replace( $wpdb->prefix . self::TABLE_NAME, array( 'order_id' => $order_id, @@ -342,7 +335,11 @@ class WC_Admin_Reports_Coupons_Data_Store extends WC_Admin_Reports_Data_Store im '%s', ) ); + + $num_updated += intval( $result ); } + + return ( count( $coupon_items ) === $num_updated ); } } diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-stats-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-stats-data-store.php index 5ea9cd13fe8..e3efe7e9ef9 100644 --- a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-stats-data-store.php +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-stats-data-store.php @@ -73,10 +73,6 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto * Set up all the hooks for maintaining and populating table data. */ public static function init() { - add_action( 'save_post', array( __CLASS__, 'sync_order' ) ); - // @todo: this is required as order update skips save_post. - add_action( 'clean_post_cache', array( __CLASS__, 'sync_order' ) ); - add_action( 'woocommerce_order_refunded', array( __CLASS__, 'sync_order' ) ); add_action( 'woocommerce_refund_deleted', array( __CLASS__, 'sync_on_refund_delete' ), 10, 2 ); add_action( 'delete_post', array( __CLASS__, 'delete_order' ) ); } @@ -360,18 +356,19 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto * Add order information to the lookup table when orders are created or modified. * * @param int $post_id Post ID. + * @return int|bool Returns -1 if order won't be processed, or a boolean indicating processing success. */ public static function sync_order( $post_id ) { if ( 'shop_order' !== get_post_type( $post_id ) ) { - return; + return -1; } $order = wc_get_order( $post_id ); if ( ! $order ) { - return; + return -1; } - self::update( $order ); + return self::update( $order ); } /** @@ -388,14 +385,14 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto * Update the database with stats data. * * @param WC_Order $order Order to update row for. - * @return int|bool|null Number or rows modified or false on failure. + * @return int|bool Returns -1 if order won't be processed, or a boolean indicating processing success. */ public static function update( $order ) { global $wpdb; $table_name = $wpdb->prefix . self::TABLE_NAME; if ( ! $order->get_id() || ! $order->get_date_created() ) { - return false; + return -1; } $data = array( @@ -450,7 +447,9 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto } // Update or add the information to the DB. - return $wpdb->replace( $table_name, $data, $format ); + $result = $wpdb->replace( $table_name, $data, $format ); + + return ( 1 === $result ); } /** diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-data-store.php index e1c0ce9d89e..0880d1a025c 100644 --- a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-data-store.php +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-data-store.php @@ -82,15 +82,6 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i $this->report_columns['orders_count'] = str_replace( 'order_id', $table_name . '.order_id', $this->report_columns['orders_count'] ); } - /** - * Set up all the hooks for maintaining and populating table data. - */ - public static function init() { - add_action( 'save_post', array( __CLASS__, 'sync_order_products' ) ); - add_action( 'clean_post_cache', array( __CLASS__, 'sync_order_products' ) ); - add_action( 'woocommerce_order_refunded', array( __CLASS__, 'sync_order_products' ) ); - } - /** * Fills ORDER BY clause of SQL request based on user supplied parameters. * @@ -319,7 +310,7 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i * * @since 3.5.0 * @param int $order_id Order ID. - * @return void + * @return int|bool Returns -1 if order won't be processed, or a boolean indicating processing success. */ public static function sync_order_products( $order_id ) { global $wpdb; @@ -328,10 +319,13 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i // This hook gets called on refunds as well, so return early to avoid errors. if ( ! $order || 'shop_order_refund' === $order->get_type() ) { - return; + return -1; } - foreach ( $order->get_items() as $order_item ) { + $order_items = $order->get_items(); + $num_updated = 0; + + foreach ( $order_items as $order_item ) { $order_item_id = $order_item->get_id(); $quantity_refunded = $order->get_item_quantity_refunded( $order_item ); $amount_refunded = $order->get_item_amount_refunded( $order_item ); @@ -355,13 +349,13 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i $net_revenue = $order_item->get_subtotal( 'edit' ) - $amount_refunded; if ( $quantity_refunded >= $order_item->get_quantity( 'edit' ) ) { - $wpdb->delete( + $result = $wpdb->delete( $wpdb->prefix . self::TABLE_NAME, array( 'order_item_id' => $order_item_id ), array( '%d' ) ); // WPCS: cache ok, DB call ok. } else { - $wpdb->replace( + $result = $wpdb->replace( $wpdb->prefix . self::TABLE_NAME, array( 'order_item_id' => $order_item_id, @@ -398,7 +392,11 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i ) ); // WPCS: cache ok, DB call ok, unprepared SQL ok. } + + $num_updated += intval( $result ); } + + return ( count( $order_items ) === $num_updated ); } } diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-taxes-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-taxes-data-store.php index f5323f7536d..86fa771bc01 100644 --- a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-taxes-data-store.php +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-taxes-data-store.php @@ -66,15 +66,6 @@ class WC_Admin_Reports_Taxes_Data_Store extends WC_Admin_Reports_Data_Store impl $this->report_columns['orders_count'] = str_replace( 'order_id', $table_name . '.order_id', $this->report_columns['orders_count'] ); } - /** - * Set up all the hooks for maintaining and populating table data. - */ - public static function init() { - add_action( 'save_post', array( __CLASS__, 'sync_order_taxes' ) ); - add_action( 'clean_post_cache', array( __CLASS__, 'sync_order_taxes' ) ); - add_action( 'woocommerce_order_refunded', array( __CLASS__, 'sync_order_taxes' ) ); - } - /** * Updates the database query with parameters used for Taxes report: categories and order status. * @@ -255,17 +246,20 @@ class WC_Admin_Reports_Taxes_Data_Store extends WC_Admin_Reports_Data_Store impl * Create or update an entry in the wc_order_tax_lookup table for an order. * * @param int $order_id Order ID. - * @return void + * @return int|bool Returns -1 if order won't be processed, or a boolean indicating processing success. */ public static function sync_order_taxes( $order_id ) { global $wpdb; $order = wc_get_order( $order_id ); if ( ! $order ) { - return; + return -1; } - foreach ( $order->get_items( 'tax' ) as $tax_item ) { - $wpdb->replace( + $tax_items = $order->get_items( 'tax' ); + $num_updated = 0; + + foreach ( $tax_items as $tax_item ) { + $result = $wpdb->replace( $wpdb->prefix . self::TABLE_NAME, array( 'order_id' => $order->get_id(), @@ -284,7 +278,11 @@ class WC_Admin_Reports_Taxes_Data_Store extends WC_Admin_Reports_Data_Store impl '%f', ) ); + + $num_updated += intval( $result ); } + + return ( count( $tax_items ) === $num_updated ); } } diff --git a/plugins/woocommerce-admin/tests/api-init.php b/plugins/woocommerce-admin/tests/api-init.php new file mode 100644 index 00000000000..5e196af6961 --- /dev/null +++ b/plugins/woocommerce-admin/tests/api-init.php @@ -0,0 +1,85 @@ +queue = new WC_Admin_Test_Action_Queue(); + WC_Admin_Api_Init::set_queue( $this->queue ); + } + + /** + * Tear down. + */ + public function tearDown() { + parent::tearDown(); + WC_Admin_Api_Init::set_queue( null ); + $this->queue->actions = array(); + } + + /** + * Cause a failure when updating order stats for the test order, without deleting it. + * + * @param string $query Query. + * @return string + */ + public function filter_order_query( $query ) { + if ( + 0 === strpos( $query, 'REPLACE INTO' ) && + false !== strpos( $query, WC_Admin_Reports_Orders_Stats_Data_Store::TABLE_NAME ) + ) { + remove_filter( 'query', array( $this, 'filter_order_query' ) ); + return 'THIS WONT MATCH'; + } + + return $query; + } + + /** + * Test that a retry job is scheduled for a failed sync. + * + * @return void + */ + public function test_order_sync_retries_on_failure() { + // Create a test Order. + $product = new WC_Product_Simple(); + $product->set_name( 'Test Product' ); + $product->set_regular_price( 25 ); + $product->save(); + + $order = WC_Helper_Order::create_order( 1, $product ); + $order->set_status( 'completed' ); + $order->set_total( 100 ); // $25 x 4. + $order->save(); + + // Clear the existing action queue (the above save adds an action). + $this->queue->actions = array(); + + // Force a failure by sabotaging the query run after retreiving order coupons. + add_filter( 'query', array( $this, 'filter_order_query' ) ); + + // Initiate sync. + WC_Admin_Api_Init::orders_lookup_process_order( $order->get_id() ); + + // Verify that a retry job was scheduled. + $this->assertCount( 1, $this->queue->actions ); + $this->assertArraySubset( + array( + 'hook' => WC_Admin_Api_Init::SINGLE_ORDER_ACTION, + 'args' => array( $order->get_id() ), + ), + $this->queue->actions[0] + ); + } +} diff --git a/plugins/woocommerce-admin/tests/api/reports-categories.php b/plugins/woocommerce-admin/tests/api/reports-categories.php index 13eb432ec33..ac14cea7fac 100644 --- a/plugins/woocommerce-admin/tests/api/reports-categories.php +++ b/plugins/woocommerce-admin/tests/api/reports-categories.php @@ -64,6 +64,8 @@ class WC_Tests_API_Reports_Categories extends WC_REST_Unit_Test_Case { $order->set_total( 100 ); // $25 x 4. $order->save(); + WC_Helper_Queue::run_all_pending(); + $uncategorized_term = get_term_by( 'slug', 'uncategorized', 'product_cat' ); $response = $this->server->dispatch( new WP_REST_Request( 'GET', $this->endpoint ) ); diff --git a/plugins/woocommerce-admin/tests/api/reports-coupons-stats.php b/plugins/woocommerce-admin/tests/api/reports-coupons-stats.php index eb09fa697f6..a17f10a37db 100644 --- a/plugins/woocommerce-admin/tests/api/reports-coupons-stats.php +++ b/plugins/woocommerce-admin/tests/api/reports-coupons-stats.php @@ -87,6 +87,8 @@ class WC_Tests_API_Reports_Coupons_Stats extends WC_REST_Unit_Test_Case { $order_2c->set_date_created( $time ); $order_2c->save(); + WC_Helper_Queue::run_all_pending(); + $request = new WP_REST_Request( 'GET', $this->endpoint ); $request->set_query_params( array( diff --git a/plugins/woocommerce-admin/tests/api/reports-coupons.php b/plugins/woocommerce-admin/tests/api/reports-coupons.php index ad7d3b51aa4..36b1edda328 100644 --- a/plugins/woocommerce-admin/tests/api/reports-coupons.php +++ b/plugins/woocommerce-admin/tests/api/reports-coupons.php @@ -82,6 +82,8 @@ class WC_Tests_API_Reports_Coupons extends WC_REST_Unit_Test_Case { $order_2c->calculate_totals(); $order_2c->save(); + WC_Helper_Queue::run_all_pending(); + $response = $this->server->dispatch( new WP_REST_Request( 'GET', $this->endpoint ) ); $coupon_reports = $response->get_data(); diff --git a/plugins/woocommerce-admin/tests/api/reports-customers-stats.php b/plugins/woocommerce-admin/tests/api/reports-customers-stats.php index 316cf2613b8..367f5c642e4 100644 --- a/plugins/woocommerce-admin/tests/api/reports-customers-stats.php +++ b/plugins/woocommerce-admin/tests/api/reports-customers-stats.php @@ -126,6 +126,8 @@ class WC_Tests_API_Reports_Customers_Stats extends WC_REST_Unit_Test_Case { $order->set_total( 9.12 ); $order->save(); + WC_Helper_Queue::run_all_pending(); + $request = new WP_REST_Request( 'GET', $this->endpoint ); $response = $this->server->dispatch( $request ); $reports = $response->get_data(); diff --git a/plugins/woocommerce-admin/tests/api/reports-customers.php b/plugins/woocommerce-admin/tests/api/reports-customers.php index 781050fe3f1..f2386a45201 100644 --- a/plugins/woocommerce-admin/tests/api/reports-customers.php +++ b/plugins/woocommerce-admin/tests/api/reports-customers.php @@ -134,6 +134,8 @@ class WC_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { $order->set_total( 100 ); $order->save(); + WC_Helper_Queue::run_all_pending(); + $request = new WP_REST_Request( 'GET', $this->endpoint ); $request->set_query_params( array( diff --git a/plugins/woocommerce-admin/tests/api/reports-orders.php b/plugins/woocommerce-admin/tests/api/reports-orders.php index c625473eef4..114d368999f 100644 --- a/plugins/woocommerce-admin/tests/api/reports-orders.php +++ b/plugins/woocommerce-admin/tests/api/reports-orders.php @@ -67,6 +67,8 @@ class WC_Tests_API_Reports_Orders extends WC_REST_Unit_Test_Case { $order->set_total( 100 ); // $25 x 4. $order->save(); + WC_Helper_Queue::run_all_pending(); + $expected_customer_id = WC_Admin_Reports_Customers_Data_Store::get_customer_id_by_user_id( 1 ); $response = $this->server->dispatch( new WP_REST_Request( 'GET', $this->endpoint ) ); diff --git a/plugins/woocommerce-admin/tests/api/reports-performance-indicators.php b/plugins/woocommerce-admin/tests/api/reports-performance-indicators.php index 6821d615aba..119f8e7a015 100644 --- a/plugins/woocommerce-admin/tests/api/reports-performance-indicators.php +++ b/plugins/woocommerce-admin/tests/api/reports-performance-indicators.php @@ -84,6 +84,8 @@ class WC_Tests_API_Reports_Performance_Indicators extends WC_REST_Unit_Test_Case $object->set_user_ip_address( '1.2.3.4' ); $object->save(); + WC_Helper_Queue::run_all_pending(); + $time = time(); $request = new WP_REST_Request( 'GET', $this->endpoint ); $request->set_query_params( diff --git a/plugins/woocommerce-admin/tests/api/reports-products-stats.php b/plugins/woocommerce-admin/tests/api/reports-products-stats.php index 29825af677d..aab71f9c9df 100644 --- a/plugins/woocommerce-admin/tests/api/reports-products-stats.php +++ b/plugins/woocommerce-admin/tests/api/reports-products-stats.php @@ -71,6 +71,8 @@ class WC_Tests_API_Reports_Products_Stats extends WC_REST_Unit_Test_Case { $order->set_total( 97 ); // $25x4 products + $10 shipping - $20 discount + $7 tax. $order->save(); + WC_Helper_Queue::run_all_pending(); + $request = new WP_REST_Request( 'GET', $this->endpoint ); $request->set_query_params( array( diff --git a/plugins/woocommerce-admin/tests/api/reports-products.php b/plugins/woocommerce-admin/tests/api/reports-products.php index 9e7ada0f358..b8912503522 100644 --- a/plugins/woocommerce-admin/tests/api/reports-products.php +++ b/plugins/woocommerce-admin/tests/api/reports-products.php @@ -67,6 +67,8 @@ class WC_Tests_API_Reports_Products extends WC_REST_Unit_Test_Case { $order->set_total( 100 ); // $25 x 4. $order->save(); + WC_Helper_Queue::run_all_pending(); + $response = $this->server->dispatch( new WP_REST_Request( 'GET', $this->endpoint ) ); $reports = $response->get_data(); diff --git a/plugins/woocommerce-admin/tests/api/reports-taxes.php b/plugins/woocommerce-admin/tests/api/reports-taxes.php index 31eda8216d9..217a2a4b77d 100644 --- a/plugins/woocommerce-admin/tests/api/reports-taxes.php +++ b/plugins/woocommerce-admin/tests/api/reports-taxes.php @@ -91,6 +91,8 @@ class WC_Tests_API_Reports_Taxes extends WC_REST_Unit_Test_Case { ) ); + WC_Helper_Queue::run_all_pending(); + $response = $this->server->dispatch( new WP_REST_Request( 'GET', $this->endpoint ) ); $reports = $response->get_data(); diff --git a/plugins/woocommerce-admin/tests/api/reports-variations.php b/plugins/woocommerce-admin/tests/api/reports-variations.php index 861e358e8c1..70e4f126591 100644 --- a/plugins/woocommerce-admin/tests/api/reports-variations.php +++ b/plugins/woocommerce-admin/tests/api/reports-variations.php @@ -65,6 +65,8 @@ class WC_Tests_API_Reports_Variations extends WC_REST_Unit_Test_Case { $order->set_total( 100 ); // $25 x 4. $order->save(); + WC_Helper_Queue::run_all_pending(); + $response = $this->server->dispatch( new WP_REST_Request( 'GET', $this->endpoint ) ); $reports = $response->get_data(); diff --git a/plugins/woocommerce-admin/tests/batch-queue.php b/plugins/woocommerce-admin/tests/batch-queue.php index d8711afdc60..afb3079ab4f 100644 --- a/plugins/woocommerce-admin/tests/batch-queue.php +++ b/plugins/woocommerce-admin/tests/batch-queue.php @@ -136,7 +136,7 @@ class WC_Tests_Reports_Regenerate_Batching extends WC_REST_Unit_Test_Case { // insert a blocking job. WC_Admin_Api_Init::queue()->schedule_single( time(), 'blocking_job', array( 'stuff' ) ); // queue an action that depends on blocking job. - WC_Admin_Api_Init::queue_dependent_action( 'dependent_action', 'blocking_job' ); + WC_Admin_Api_Init::queue_dependent_action( 'dependent_action', array(), 'blocking_job' ); // verify that the action was properly blocked. $this->assertEmpty( WC_Admin_Api_Init::queue()->search( @@ -151,13 +151,13 @@ class WC_Tests_Reports_Regenerate_Batching extends WC_REST_Unit_Test_Case { WC_Admin_Api_Init::queue()->search( array( 'hook' => WC_Admin_Api_Init::QUEUE_DEPEDENT_ACTION, - 'args' => array( 'dependent_action', 'blocking_job' ), + 'args' => array( 'dependent_action', array(), 'blocking_job' ), ) ) ); // queue an action that isn't blocked. - WC_Admin_Api_Init::queue_dependent_action( 'another_dependent_action', 'nonexistant_blocking_job' ); + WC_Admin_Api_Init::queue_dependent_action( 'another_dependent_action', array(), 'nonexistant_blocking_job' ); // verify that the dependent action was queued. $this->assertCount( 1, @@ -172,7 +172,7 @@ class WC_Tests_Reports_Regenerate_Batching extends WC_REST_Unit_Test_Case { WC_Admin_Api_Init::queue()->search( array( 'hook' => WC_Admin_Api_Init::QUEUE_DEPEDENT_ACTION, - 'args' => array( 'another_dependent_action', 'nonexistant_blocking_job' ), + 'args' => array( 'another_dependent_action', array(), 'nonexistant_blocking_job' ), ) ) ); diff --git a/plugins/woocommerce-admin/tests/bootstrap.php b/plugins/woocommerce-admin/tests/bootstrap.php index 55caa41ce7d..0b2ebe86311 100755 --- a/plugins/woocommerce-admin/tests/bootstrap.php +++ b/plugins/woocommerce-admin/tests/bootstrap.php @@ -123,3 +123,4 @@ wc_test_includes(); require_once dirname( __FILE__ ) . '/framework/helpers/class-wc-helper-reports.php'; require_once dirname( __FILE__ ) . '/framework/helpers/class-wc-helper-admin-notes.php'; require_once dirname( __FILE__ ) . '/framework/helpers/class-wc-test-action-queue.php'; +require_once dirname( __FILE__ ) . '/framework/helpers/class-wc-helper-queue.php'; diff --git a/plugins/woocommerce-admin/tests/framework/helpers/class-wc-helper-queue.php b/plugins/woocommerce-admin/tests/framework/helpers/class-wc-helper-queue.php new file mode 100644 index 00000000000..57b5c779c71 --- /dev/null +++ b/plugins/woocommerce-admin/tests/framework/helpers/class-wc-helper-queue.php @@ -0,0 +1,32 @@ +queue()->search( + array( + 'per_page' => -1, + 'status' => 'pending', + 'claimed' => false, + ) + ); + + foreach ( $jobs as $job ) { + $job->execute(); + } + } +} diff --git a/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-coupons-stats.php b/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-coupons-stats.php index e130bfb0c6a..2c6de71d4ae 100644 --- a/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-coupons-stats.php +++ b/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-coupons-stats.php @@ -54,6 +54,8 @@ class WC_Tests_Reports_Coupons_Stats extends WC_Unit_Test_Case { $order_2c->calculate_totals(); $order_2c->save(); + WC_Helper_Queue::run_all_pending(); + $data_store = new WC_Admin_Reports_Coupons_Stats_Data_Store(); $start_time = date( 'Y-m-d 00:00:00', $order->get_date_created()->getOffsetTimestamp() ); $end_time = date( 'Y-m-d 23:59:59', $order->get_date_created()->getOffsetTimestamp() ); diff --git a/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-coupons.php b/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-coupons.php index aeebb325518..58ac90c541d 100644 --- a/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-coupons.php +++ b/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-coupons.php @@ -54,6 +54,8 @@ class WC_Tests_Reports_Coupons extends WC_Unit_Test_Case { $order_2c->calculate_totals(); $order_2c->save(); + WC_Helper_Queue::run_all_pending(); + $data_store = new WC_Admin_Reports_Coupons_Data_Store(); $start_time = date( 'Y-m-d 00:00:00', $order->get_date_created()->getOffsetTimestamp() ); $end_time = date( 'Y-m-d 23:59:59', $order->get_date_created()->getOffsetTimestamp() ); diff --git a/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-orders-stats.php b/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-orders-stats.php index 91e6afbebc8..7f0f69f1a5d 100644 --- a/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-orders-stats.php +++ b/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-orders-stats.php @@ -41,6 +41,8 @@ class WC_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { ) ); + WC_Helper_Queue::run_all_pending(); + $data_store = new WC_Admin_Reports_Orders_Stats_Data_Store(); $start_time = date( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); @@ -176,10 +178,13 @@ class WC_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { $order->set_shipping_total( 0 ); $order->set_cart_tax( 0 ); $order->save(); + // Wait one second to avoid potentially ambiguous new/returning customer. sleep( 1 ); } + WC_Helper_Queue::run_all_pending(); + $data_store = new WC_Admin_Reports_Orders_Stats_Data_Store(); $start_time = date( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); @@ -203,8 +208,8 @@ class WC_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { 'taxes' => 0, 'shipping' => 0, 'net_revenue' => 100, - 'num_returning_customers' => 0, - 'num_new_customers' => 1, + 'num_returning_customers' => 1, + 'num_new_customers' => 0, 'products' => 1, 'segments' => array(), ), @@ -226,8 +231,8 @@ class WC_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { 'num_items_sold' => 4, 'avg_items_per_order' => 4, 'avg_order_value' => 100, - 'num_returning_customers' => 0, - 'num_new_customers' => 1, + 'num_returning_customers' => 1, + 'num_new_customers' => 0, 'segments' => array(), ), ), @@ -483,6 +488,8 @@ class WC_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { } } + WC_Helper_Queue::run_all_pending(); + $data_store = new WC_Admin_Reports_Orders_Stats_Data_Store(); // Tests for before & after set to current hour. @@ -1697,6 +1704,8 @@ class WC_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { $returning_order->set_date_created( $order_1_time + 1 ); // This is guaranteed to belong to the same hour by the adjustment to $order_1_time. $returning_order->save(); + WC_Helper_Queue::run_all_pending(); + $query_args = array( 'after' => $current_hour_start->format( WC_Admin_Reports_Interval::$sql_datetime_format ), // I don't think this makes sense.... date( 'Y-m-d H:i:s', $orders[0]->get_date_created()->getOffsetTimestamp() + 1 ), // Date after initial order to get a returning customer. 'before' => $current_hour_end->format( WC_Admin_Reports_Interval::$sql_datetime_format ), @@ -3324,6 +3333,8 @@ class WC_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { $order_2->calculate_totals(); $order_2->save(); + WC_Helper_Queue::run_all_pending(); + $data_store = new WC_Admin_Reports_Orders_Stats_Data_Store(); // Tests for before & after set to current hour. diff --git a/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-products.php b/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-products.php index 0b21cada12e..7d6cf74074e 100644 --- a/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-products.php +++ b/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-products.php @@ -38,6 +38,8 @@ class WC_Tests_Reports_Products extends WC_Unit_Test_Case { $order->set_total( 97 ); // $25x4 products + $10 shipping - $20 discount + $7 tax. $order->save(); + WC_Helper_Queue::run_all_pending(); + $data_store = new WC_Admin_Reports_Products_Data_Store(); $start_time = date( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); $end_time = date( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() + HOUR_IN_SECONDS ); @@ -113,6 +115,8 @@ class WC_Tests_Reports_Products extends WC_Unit_Test_Case { $order_2->set_date_created( $date_created_2 ); $order_2->save(); + WC_Helper_Queue::run_all_pending(); + $data_store = new WC_Admin_Reports_Products_Data_Store(); $start_time = date( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); $end_time = date( 'Y-m-d H:00:00', $order_2->get_date_created()->getOffsetTimestamp() + HOUR_IN_SECONDS ); @@ -211,6 +215,9 @@ class WC_Tests_Reports_Products extends WC_Unit_Test_Case { $order->set_shipping_tax( 2 ); $order->set_total( 97 ); // $25x4 products + $10 shipping - $20 discount + $7 tax. $order->save(); + + WC_Helper_Queue::run_all_pending(); + $data_store = new WC_Admin_Reports_Products_Data_Store(); $start_time = date( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); $end_time = date( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() + HOUR_IN_SECONDS ); @@ -289,6 +296,8 @@ class WC_Tests_Reports_Products extends WC_Unit_Test_Case { break; } + WC_Helper_Queue::run_all_pending(); + $data_store = new WC_Admin_Reports_Products_Data_Store(); $start_time = date( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); $end_time = date( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() + HOUR_IN_SECONDS ); diff --git a/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-revenue-stats.php b/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-revenue-stats.php index fa74b3b9d22..a830010ab97 100644 --- a/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-revenue-stats.php +++ b/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-revenue-stats.php @@ -37,6 +37,8 @@ class WC_Admin_Tests_Reports_Revenue_Stats extends WC_Unit_Test_Case { $order->set_total( 97 ); // $25x4 products + $10 shipping - $20 discount + $7 tax. $order->save(); + WC_Helper_Queue::run_all_pending(); + // /reports/revenue/stats is mapped to Orders_Data_Store. $data_store = new WC_Admin_Reports_Orders_Stats_Data_Store(); diff --git a/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-variations.php b/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-variations.php index 082e8d6e307..fd44d4e957f 100644 --- a/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-variations.php +++ b/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-variations.php @@ -39,6 +39,8 @@ class WC_Tests_Reports_Variations extends WC_Unit_Test_Case { $order->set_status( 'completed' ); $order->save(); + WC_Helper_Queue::run_all_pending(); + $data_store = new WC_Admin_Reports_Variations_Data_Store(); $start_time = date( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); $end_time = date( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() + HOUR_IN_SECONDS ); @@ -106,6 +108,8 @@ class WC_Tests_Reports_Variations extends WC_Unit_Test_Case { $order->set_status( 'completed' ); $order->save(); + WC_Helper_Queue::run_all_pending(); + $data_store = new WC_Admin_Reports_Variations_Data_Store(); $start_time = date( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); $end_time = date( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() + HOUR_IN_SECONDS );