diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Helpers/OrderHelper.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Helpers/OrderHelper.php index 10bea7dcbdc..4742d15e3f6 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Helpers/OrderHelper.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Helpers/OrderHelper.php @@ -331,5 +331,4 @@ class OrderHelper { return $order; } - } diff --git a/plugins/woocommerce/tests/php/helpers/HPOSToggleTrait.php b/plugins/woocommerce/tests/php/helpers/HPOSToggleTrait.php index 3cda5b546b4..f50cf300f9c 100644 --- a/plugins/woocommerce/tests/php/helpers/HPOSToggleTrait.php +++ b/plugins/woocommerce/tests/php/helpers/HPOSToggleTrait.php @@ -28,14 +28,14 @@ trait HPOSToggleTrait { OrderHelper::delete_order_custom_tables(); OrderHelper::create_order_custom_table_if_not_exist(); - $this->toggle_cot( true ); + $this->toggle_cot_feature_and_usage( true ); } /** * Call in teardown to disable COT/HPOS. */ public function clean_up_cot_setup(): void { - $this->toggle_cot( false ); + $this->toggle_cot_feature_and_usage( false ); // Add back removed filter. add_filter( 'query', array( $this, '_create_temporary_tables' ) ); @@ -43,12 +43,12 @@ trait HPOSToggleTrait { } /** - * Enables or disables the custom orders table across WP temporarily. + * Enables or disables the custom orders table feature, and sets the orders table as authoritative, across WP temporarily. * * @param boolean $enabled TRUE to enable COT or FALSE to disable. * @return void */ - private function toggle_cot( bool $enabled ): void { + private function toggle_cot_feature_and_usage( bool $enabled ): void { $features_controller = wc_get_container()->get( Featurescontroller::class ); $features_controller->change_feature_enable( 'custom_order_tables', $enabled ); @@ -59,6 +59,14 @@ trait HPOSToggleTrait { assert( is_a( $wc_data_store->get_current_class_name(), OrdersTableDataStore::class, true ) === $enabled ); } + /** + * Set the orders table or the posts table as the authoritative table to store orders. + * @param bool $cot_authoritative True to set the orders table as authoritative, false to set the posts table as authoritative. + */ + protected function toggle_cot_authoritative( bool $cot_authoritative ) { + update_option( CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, wc_bool_to_string( $cot_authoritative ) ); + } + /** * Helper function to enable COT <> Posts sync. */ diff --git a/plugins/woocommerce/tests/php/includes/wc-user-functions-test.php b/plugins/woocommerce/tests/php/includes/wc-user-functions-test.php index 6189bece62d..f016055a4d3 100644 --- a/plugins/woocommerce/tests/php/includes/wc-user-functions-test.php +++ b/plugins/woocommerce/tests/php/includes/wc-user-functions-test.php @@ -14,7 +14,7 @@ class WC_User_Functions_Tests extends WC_Unit_Test_Case { public function setUp(): void { parent::setUp(); $this->setup_cot(); - $this->toggle_cot( false ); + $this->toggle_cot_feature_and_usage( false ); } /** @@ -29,7 +29,7 @@ class WC_User_Functions_Tests extends WC_Unit_Test_Case { * Test wc_get_customer_order_count. Borrowed from `WC_Tests_Customer_Functions` class for COT. */ public function test_hpos_wc_customer_bought_product() { - $this->toggle_cot( true ); + $this->toggle_cot_feature_and_usage( true ); $customer_id_1 = wc_create_new_customer( 'test@example.com', 'testuser', 'testpassword' ); $customer_id_2 = wc_create_new_customer( 'test2@example.com', 'testuser2', 'testpassword2' ); $product_1 = new WC_Product_Simple(); diff --git a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/DataSynchronizerTests.php b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/DataSynchronizerTests.php index 2ad883cef55..af0543a3350 100644 --- a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/DataSynchronizerTests.php +++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/DataSynchronizerTests.php @@ -5,13 +5,17 @@ use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer; use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore; use Automattic\WooCommerce\Internal\Features\FeaturesController; use Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper; +use Automattic\WooCommerce\RestApi\UnitTests\HPOSToggleTrait; use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts; +require_once __DIR__ . '/../../../../helpers/HPOSToggleTrait.php'; + /** * Tests for DataSynchronizer class. */ -class DataSynchronizerTests extends WC_Unit_Test_Case { +class DataSynchronizerTests extends HposTestCase { use ArraySubsetAsserts; + use HPOSToggleTrait; /** * @var DataSynchronizer @@ -231,23 +235,23 @@ class DataSynchronizerTests extends WC_Unit_Test_Case { * @param bool $manual_sync True to trigger synchronization manually, false if automatic synchronization is enabled. */ public function test_synced_order_deletion_with_sync_disabled_and_posts_authoritative_generates_proper_deletion_record_if_cot_record_exists( bool $manual_sync ) { - $this->set_posts_authoritative(); + $this->toggle_cot_authoritative( false ); if ( $manual_sync ) { - $this->disable_data_sync(); + $this->disable_cot_sync(); $order = OrderHelper::create_order(); - $this->do_sync(); + $this->do_cot_sync(); } else { - $this->enable_data_sync(); + $this->enable_cot_sync(); $order = OrderHelper::create_order(); } - $this->disable_data_sync(); + $this->disable_cot_sync(); $order_id = $order->get_id(); $order->delete( true ); $this->assert_deletion_record_existence( $order_id, false ); - $this->assert_order_record_existence( $order_id, true ); + $this->assert_order_record_existence( $order_id, true, true ); } /** @@ -256,8 +260,8 @@ class DataSynchronizerTests extends WC_Unit_Test_Case { * @return void */ public function test_synced_order_deletion_with_sync_disabled_and_posts_authoritative_not_generating_deletion_record_if_cot_record_not_exists() { - $this->set_posts_authoritative(); - $this->disable_data_sync(); + $this->toggle_cot_authoritative( false ); + $this->disable_cot_sync(); $order = OrderHelper::create_order(); $order_id = $order->get_id(); @@ -282,19 +286,15 @@ class DataSynchronizerTests extends WC_Unit_Test_Case { $meta_table_name = OrdersTableDataStore::get_meta_table_name(); - if ( $cot_is_authoritative ) { - $this->set_cot_authoritative(); - } else { - $this->set_posts_authoritative(); - } + $this->toggle_cot_authoritative( $cot_is_authoritative ); - $this->enable_data_sync(); + $this->enable_cot_sync(); $order_1 = OrderHelper::create_order(); $order_2 = OrderHelper::create_order(); $order_3 = OrderHelper::create_order(); $order_4 = OrderHelper::create_order(); - $this->disable_data_sync(); + $this->disable_cot_sync(); if ( $new_records_exist ) { $order_5 = OrderHelper::create_order(); } @@ -323,19 +323,15 @@ class DataSynchronizerTests extends WC_Unit_Test_Case { * @param bool $cot_is_authoritative True to test with the orders table as authoritative, false to test with the posts table as authoritative. */ public function test_process_batch_processes_modified_and_deleted_orders( bool $cot_is_authoritative ) { - if ( $cot_is_authoritative ) { - $this->set_cot_authoritative(); - } else { - $this->set_posts_authoritative(); - } + $this->toggle_cot_authoritative( $cot_is_authoritative ); + $this->enable_cot_sync(); - $this->enable_data_sync(); $order_1 = OrderHelper::create_order(); $order_2 = OrderHelper::create_order(); $order_3 = OrderHelper::create_order(); $order_4 = OrderHelper::create_order(); - $this->disable_data_sync(); + $this->disable_cot_sync(); $order_1->set_date_modified( '2100-01-01 00:00:00' ); $order_1->save(); @@ -387,22 +383,19 @@ class DataSynchronizerTests extends WC_Unit_Test_Case { $this->register_legacy_proxy_function_mocks( array( 'wc_get_logger' => function() use ( $logger ) { - return $logger;}, + return $logger; + }, ) ); $this->sut = wc_get_container()->get( DataSynchronizer::class ); - if ( $cot_is_authoritative ) { - $this->set_cot_authoritative(); - } else { - $this->set_posts_authoritative(); - } + $this->toggle_cot_authoritative( $cot_is_authoritative ); + $this->enable_cot_sync(); - $this->enable_data_sync(); $order_1 = OrderHelper::create_order(); $order_2 = OrderHelper::create_order(); - $this->disable_data_sync(); + $this->disable_cot_sync(); $order_1_id = $order_1->get_id(); $order_1->delete( true ); @@ -419,104 +412,4 @@ class DataSynchronizerTests extends WC_Unit_Test_Case { $this->assertEquals( array( "Order {$order_1_id} doesn't exist in the backup table, thus it can't be deleted" ), $logger->warnings ); } - - /** - * Assert that a given order record exists or doesn't exist. - * - * @param int $order_id The order id to check. - * @param bool $in_cot True to assert that the order exists or not in the orders table, false to check in the posts table. - * @param bool $exists True to assert that the order exists, false to check that the order doesn't exist. - * @return void - */ - private function assert_order_record_existence( $order_id, $in_cot, $exists = true ) { - global $wpdb; - - $table_name = $in_cot ? OrdersTableDataStore::get_orders_table_name() : $wpdb->posts; - - //phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared - $exists = $wpdb->get_var( - $wpdb->prepare( - "SELECT EXISTS (SELECT id FROM {$table_name} WHERE id = %d)", - $order_id - ) - ); - //phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared - - if ( $exists ) { - $this->assertTrue( (bool) $exists, "No order found with id $order_id in table $table_name" ); - } else { - $this->assertFalse( (bool) $exists, "Unexpected order found with id $order_id in table $table_name" ); - } - } - - /** - * Assert that an order deletion record exists or doesn't exist in the orders meta table. - * - * @param int $order_id The order id to check. - * @param bool $deleted_from_cot True to assert that the record corresponds to an order deleted from the orders table, or from the posts table otherwise. - * @param bool $exists True to assert that the record exists, false to assert that the record doesn't exist. - * @return void - */ - private function assert_deletion_record_existence( $order_id, $deleted_from_cot, $exists = true ) { - global $wpdb; - - $meta_table_name = OrdersTableDataStore::get_meta_table_name(); - //phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared - $record = $wpdb->get_row( - $wpdb->prepare( - "SELECT meta_value FROM $meta_table_name WHERE order_id = %d AND meta_key = %s", - $order_id, - 'deleted_from' - ), - ARRAY_A - ); - //phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared - - if ( $exists ) { - $this->assertNotNull( $record, "No deletion record found for order id {$order_id}, value: {$record['meta_value']}" ); - } else { - $this->assertNull( $record, "Unexpected deletion record found for order id {$order_id}" ); - return; - } - - $deleted_from = $deleted_from_cot ? OrdersTableDataStore::get_orders_table_name() : $wpdb->posts; - - $this->assertEquals( $deleted_from, $record['meta_value'], "Deletion record for order {$order_id} has a value of {$record['meta_value']}, expected {$deleted_from}" ); - } - - /** - * Synchronize all the pending unsynchronized orders. - */ - private function do_sync() { - $batch = $this->sut->get_next_batch_to_process( 100 ); - $this->sut->process_batch( $batch ); - } - - /** - * Set the orders table as authoritative. - */ - private function set_cot_authoritative() { - update_option( CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, 'yes' ); - } - - /** - * Set the posts table as authoritative. - */ - private function set_posts_authoritative() { - update_option( CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, 'no' ); - } - - /** - * Enable the orders synchronization. - */ - private function enable_data_sync() { - update_option( $this->sut::ORDERS_DATA_SYNC_ENABLED_OPTION, 'yes' ); - } - - /** - * Disable the orders synchronization. - */ - private function disable_data_sync() { - update_option( $this->sut::ORDERS_DATA_SYNC_ENABLED_OPTION, 'no' ); - } } diff --git a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/HposTestCase.php b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/HposTestCase.php new file mode 100644 index 00000000000..dd595284fc8 --- /dev/null +++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/HposTestCase.php @@ -0,0 +1,87 @@ +posts; + $order_type = $order_type ?? 'shop_order%'; + $sql = $in_cot ? + "SELECT EXISTS (SELECT id FROM $table_name WHERE id = %d)" : + "SELECT EXISTS (SELECT ID FROM $table_name WHERE ID = %d AND post_type LIKE %s)"; + + //phpcs:disable WordPress.DB.PreparedSQL.NotPrepared + $exists = $wpdb->get_var( + $in_cot ? + $wpdb->prepare( $sql, $order_id ) : + $wpdb->prepare( $sql, $order_id, $order_type ) + ); + //phpcs:enable WordPress.DB.PreparedSQL.NotPrepared + + if ( $must_exist ) { + $this->assertTrue( (bool) $exists, "No order found with id $order_id in table $table_name" ); + } else { + $this->assertFalse( (bool) $exists, "Unexpected order found with id $order_id in table $table_name" ); + } + } + + /** + * Assert that an order deletion record exists or doesn't exist in the orders meta table. + * + * @param int $order_id The order id to check. + * @param bool $deleted_from_cot True to assert that the record corresponds to an order deleted from the orders table, or from the posts table otherwise. + * @param bool $must_exist True to assert that the record exists, false to assert that the record doesn't exist. + * @return void + */ + protected function assert_deletion_record_existence( $order_id, $deleted_from_cot, $must_exist = true ) { + global $wpdb; + + $meta_table_name = OrdersTableDataStore::get_meta_table_name(); + //phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $record = $wpdb->get_row( + $wpdb->prepare( + "SELECT meta_value FROM $meta_table_name WHERE order_id = %d AND meta_key = %s", + $order_id, + 'deleted_from' + ), + ARRAY_A + ); + //phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + + if ( $must_exist ) { + $this->assertNotNull( $record, "No deletion record found for order id {$order_id}" ); + } else { + $this->assertNull( $record, "Unexpected deletion record found for order id {$order_id}" ); + return; + } + + $deleted_from = $deleted_from_cot ? OrdersTableDataStore::get_orders_table_name() : $wpdb->posts; + + $this->assertEquals( $deleted_from, $record['meta_value'], "Deletion record for order {$order_id} has a value of {$record['meta_value']}, expected {$deleted_from}" ); + } + + /** + * Synchronize all the pending unsynchronized orders. + */ + protected function do_cot_sync() { + $sync = wc_get_container()->get( DataSynchronizer::class ); + $batch = $sync->get_next_batch_to_process( 100 ); + $sync->process_batch( $batch ); + } +} diff --git a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreRestOrdersControllerTests.php b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreRestOrdersControllerTests.php index f62af0035c9..d46f275f091 100644 --- a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreRestOrdersControllerTests.php +++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreRestOrdersControllerTests.php @@ -29,14 +29,14 @@ class OrdersTableDataStoreRestOrdersControllerTests extends \WC_REST_Orders_Cont OrderHelper::delete_order_custom_tables(); OrderHelper::create_order_custom_table_if_not_exist(); - $this->toggle_cot( true ); + $this->toggle_cot_feature_and_usage( true ); } /** * Destroys system under test. */ public function tearDown(): void { - $this->toggle_cot( false ); + $this->toggle_cot_feature_and_usage( false ); // Add back removed filter. add_filter( 'query', array( $this, '_create_temporary_tables' ) ); @@ -51,7 +51,7 @@ class OrdersTableDataStoreRestOrdersControllerTests extends \WC_REST_Orders_Cont public function test_orders_cpt() { wp_set_current_user( $this->user ); - $this->toggle_cot( false ); + $this->toggle_cot_feature_and_usage( false ); $post_order_id = OrderHelper::create_complex_wp_post_order(); ( wc_get_container()->get( PostsToOrdersMigrationController::class ) )->migrate_orders( array( $post_order_id ) ); @@ -62,7 +62,7 @@ class OrdersTableDataStoreRestOrdersControllerTests extends \WC_REST_Orders_Cont $response_cpt_data = $response_cpt->get_data(); // Re-enable COT. - $this->toggle_cot( true ); + $this->toggle_cot_feature_and_usage( true ); $response_cot = $this->server->dispatch( $request ); $this->assertEquals( 200, $response_cot->get_status() ); @@ -91,7 +91,7 @@ class OrdersTableDataStoreRestOrdersControllerTests extends \WC_REST_Orders_Cont * @param boolean $enabled TRUE to enable COT or FALSE to disable. * @return void */ - private function toggle_cot( bool $enabled ): void { + private function toggle_cot_feature_and_usage( bool $enabled ): void { $features_controller = wc_get_container()->get( Featurescontroller::class ); $features_controller->change_feature_enable( 'custom_order_tables', true ); 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 3d30cd7734b..4bbff934e0e 100644 --- a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php +++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php @@ -15,7 +15,7 @@ require_once __DIR__ . '/../../../../helpers/HPOSToggleTrait.php'; * * Test for OrdersTableDataStore class. */ -class OrdersTableDataStoreTests extends WC_Unit_Test_Case { +class OrdersTableDataStoreTests extends HposTestCase { use HPOSToggleTrait; /** @@ -50,7 +50,7 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { parent::setUp(); // Remove the Test Suiteā€™s use of temporary tables https://wordpress.stackexchange.com/a/220308. $this->setup_cot(); - $this->toggle_cot( false ); + $this->toggle_cot_feature_and_usage( false ); $container = wc_get_container(); $container->reset_all_resolved(); $this->sut = $container->get( OrdersTableDataStore::class ); @@ -731,7 +731,7 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { * @testDox Tests queries involving 'orderby' and meta queries. */ public function test_cot_query_meta_orderby() { - $this->toggle_cot( true ); + $this->toggle_cot_feature_and_usage( true ); $order1 = new \WC_Order(); $order1->add_meta_data( 'color', 'red' ); @@ -1988,7 +1988,7 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { * @testDox Test that multiple calls to read don't try to sync again. */ public function test_read_multiple_dont_sync_again_for_same_order() { - $this->toggle_cot( true ); + $this->toggle_cot_feature_and_usage( true ); $this->enable_cot_sync(); $order = $this->create_complex_cot_order(); @@ -2018,7 +2018,7 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { ) ); - $this->toggle_cot( true ); + $this->toggle_cot_feature_and_usage( true ); $order = new WC_Order(); $order->save(); @@ -2045,7 +2045,7 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { ) ); - $this->toggle_cot( true ); + $this->toggle_cot_feature_and_usage( true ); $order = new WC_Order(); $order->save(); @@ -2064,7 +2064,7 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { * @testDox Make sure get_order return false when checking an order of different order types without warning. */ public function test_get_order_with_id_for_different_type() { - $this->toggle_cot( true ); + $this->toggle_cot_feature_and_usage( true ); $this->disable_cot_sync(); $product = new \WC_Product(); $product->save(); @@ -2093,7 +2093,7 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { */ public function test_address_index_saved_on_update() { global $wpdb; - $this->toggle_cot( true ); + $this->toggle_cot_feature_and_usage( true ); $this->disable_cot_sync(); $order = new WC_Order(); $order->set_billing_address_1( '123 Main St' ); @@ -2115,4 +2115,185 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { $this->assertEquals( 1, $result ); } + + /** + * @testdox When sync is enabled and an order is deleted, records in both the authoritative and the backup tables are deleted, and no deletion records are created. + * + * @testWith [true] + * [false] + * + * @param bool $cot_is_authoritative True to test with the orders table as authoritative, false to test with the posts table as authoritative. + */ + public function test_order_deletion_with_sync_enabled( bool $cot_is_authoritative ) { + $this->allow_current_user_to_delete_posts(); + $this->toggle_cot_feature_and_usage( true ); + $this->toggle_cot_authoritative( $cot_is_authoritative ); + $this->enable_cot_sync(); + + list($order, $refund) = $this->create_order_with_refund(); + + $order_id = $order->get_id(); + $refund_id = $refund->get_id(); + + $order->delete( true ); + + $this->assert_no_order_records_or_deletion_records_exist( $order_id, $refund_id, $cot_is_authoritative ); + } + + /** + * @testdox Deletion records are created when an order is deleted with sync disabled, then when sync is enabled all order and deletion records are deleted. + * + * @testWith [true] + * [false] + * + * @param bool $cot_is_authoritative True to test with the orders table as authoritative, false to test with the posts table as authoritative. + */ + public function test_order_deletion_with_sync_disabled( bool $cot_is_authoritative ) { + $this->allow_current_user_to_delete_posts(); + $this->toggle_cot_feature_and_usage( true ); + $this->toggle_cot_authoritative( $cot_is_authoritative ); + $this->enable_cot_sync(); + + list($order, $refund) = $this->create_order_with_refund(); + $order_id = $order->get_id(); + $refund_id = $refund->get_id(); + + $this->disable_cot_sync(); + $order->delete( true ); + + $this->assert_order_record_existence( $order_id, true, ! $cot_is_authoritative ); + $this->assert_order_record_existence( $order_id, false, $cot_is_authoritative ); + $this->assert_order_record_existence( $refund_id, true, ! $cot_is_authoritative ); + $this->assert_order_record_existence( $refund_id, false, $cot_is_authoritative ); + + $this->assert_deletion_record_existence( $order_id, $cot_is_authoritative, true ); + $this->assert_deletion_record_existence( $refund_id, $cot_is_authoritative, true ); + + $this->do_cot_sync(); + + $this->assert_no_order_records_or_deletion_records_exist( $order_id, $refund_id, $cot_is_authoritative ); + } + + /** + * @testdox When the orders table is authoritative, sync is disabled and an order is deleted, existing placeholder records are deleted from the posts table. + */ + public function test_order_deletion_with_sync_disabled_when_placeholders_are_created() { + $this->allow_current_user_to_delete_posts(); + $this->toggle_cot_feature_and_usage( true ); + $this->toggle_cot_authoritative( true ); + $this->disable_cot_sync(); + + list($order, $refund) = $this->create_order_with_refund(); + $order_id = $order->get_id(); + $refund_id = $refund->get_id(); + + $this->assert_order_record_existence( $order_id, true, true ); + $this->assert_order_record_existence( $order_id, false, true, 'shop_order_placehold' ); + $this->assert_order_record_existence( $refund_id, true, true ); + $this->assert_order_record_existence( $refund_id, false, true, 'shop_order_placehold' ); + + $order->delete( true ); + + $this->assert_no_order_records_or_deletion_records_exist( $order_id, $refund_id, false ); + } + + /** + * @testdox When deleting an order whose associated post type is hierarchical, child orders aren't deleted and get the parent id of their parent order. + */ + public function test_order_deletion_when_post_type_is_hierarchical_results_in_child_order_upshifting() { + $this->reset_container_resolutions(); + $this->reset_legacy_proxy_mocks(); + $this->register_legacy_proxy_function_mocks( + array( + 'is_post_type_hierarchical' => function( $post_type ) { + return 'shop_order' === $post_type ? true : is_post_type_hierarchical( $post_type ); + }, + ) + ); + $this->sut = wc_get_container()->get( OrdersTableDataStore::class ); + + $this->allow_current_user_to_delete_posts(); + $this->toggle_cot_feature_and_usage( true ); + $this->toggle_cot_authoritative( true ); + $this->disable_cot_sync(); + + list($order, $refund) = $this->create_order_with_refund(); + $order_id = $order->get_id(); + $refund_id = $refund->get_id(); + + $this->switch_data_store( $order, $this->sut ); + + $order2 = OrderHelper::create_order(); + $order2_id = $order2->get_id(); + $order->set_parent_id( $order2_id ); + $order->save(); + + $order->delete( true ); + + $this->assert_order_record_existence( $order_id, true, false ); + $this->assert_order_record_existence( $refund_id, true, true ); + + $refund = wc_get_order( $refund_id ); + $this->assertEquals( $order2_id, $refund->get_parent_id() ); + } + + /** + * Mock the current user capabilities so that it's allowed to delete posts. + * + * @return void + */ + private function allow_current_user_to_delete_posts() { + $this->register_legacy_proxy_function_mocks( + array( + 'current_user_can' => function( $capability ) { + return 'delete_posts' === $capability ? true : current_user_can( $capability ); + }, + ) + ); + } + + /** + * Assert than no records exist whatsoever, and no deletion records either, for a given order and for its refund. + * + * @param int $order_id The order id to test. + * @param int $refund_id The refund id to test. + * @param bool $cot_is_authoritative True if the deletion record existence is to be checked for the orders table, false for the posts table. + * @return void + */ + private function assert_no_order_records_or_deletion_records_exist( $order_id, $refund_id, $cot_is_authoritative ) { + $this->assert_order_record_existence( $order_id, true, false ); + $this->assert_order_record_existence( $order_id, false, false ); + $this->assert_order_record_existence( $refund_id, true, false ); + $this->assert_order_record_existence( $refund_id, false, false ); + + $this->assert_deletion_record_existence( $order_id, $cot_is_authoritative, false ); + $this->assert_deletion_record_existence( $refund_id, $cot_is_authoritative, false ); + } + + /** + * Create an order and a refund. + * + * @return array An array containing the order as the first element and the refund as the second element. + */ + private function create_order_with_refund() { + $order = OrderHelper::create_order(); + + $item = current( $order->get_items() )->get_data(); + $refund = wc_create_refund( + array( + 'order_id' => $order->get_id(), + 'line_items' => array( + $item['id'] => array( + 'id' => $item['id'], + 'qty' => $item['quantity'], + 'refund_total' => $item['total'], + 'refund_tax' => $item['total_tax'], + ), + ), + ) + ); + $refund->save(); + + return array( $order, $refund ); + } } diff --git a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableRefundDataStoreTests.php b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableRefundDataStoreTests.php index 21bb0ddd481..04e1a1c8b9b 100644 --- a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableRefundDataStoreTests.php +++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableRefundDataStoreTests.php @@ -79,7 +79,7 @@ class OrdersTableRefundDataStoreTests extends WC_Unit_Test_Case { */ public function test_refunds_backfill() { $this->enable_cot_sync(); - $this->toggle_cot( true ); + $this->toggle_cot_feature_and_usage( true ); $order = OrderHelper::create_complex_data_store_order( $this->order_data_store ); $refund = wc_create_refund( array(