Commit e1f386d059e for woocommerce

commit e1f386d059ebdcd34799fd97be62f4b463727344
Author: Jorge A. Torres <jorge.torres@automattic.com>
Date:   Wed Sep 9 14:44:00 2026 +0100

    Merge 11.2.0 database migrations under `11.2.0` (#68495)

diff --git a/AGENTS.md b/AGENTS.md
index 53138f5c28c..121ad660193 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -218,10 +218,12 @@ To rename subdivision codes, use `Automattic\WooCommerce\Database\Migrations\Mig

 ## Database Migrations

-Database migrations live in `WC_Install::$db_updates`; read that class for the current mechanics before adding one. Two invariants have broken real releases when violated:
+Database migrations live in `WC_Install::$db_updates`. Read that class for the current mechanics before adding one.

-- Migration keys are one-shot: sites that updated past a key never re-run it. A migration added after a prerelease of the same version has shipped needs a new suffixed key (see existing examples in `$db_updates`), and a key must never be ahead of the version it ships in.
-- Feature flag defaults are persisted, so changing `enabled_by_default` alone doesn't change behavior on existing sites; ship a migration or remove the flag.
+- Each key runs once per site. After a site's database version moves past a key, nothing added to that key later will run there. A key must also never be ahead of the version it ships in.
+- While the version is `X.Y.0-dev`, add new migrations to the plain `X.Y.0` key, even if it already has callbacks. Sequential keys (`X.Y.0-1`, `X.Y.0-2`) are only needed once beta 1 of `X.Y.0` has shipped, because testers on that beta already have their database at `X.Y.0` and would skip anything added to the plain key.
+- To test a migration locally, set the `woocommerce_db_version` and `woocommerce_version` options to the previous release, for example with `wp option update woocommerce_db_version 11.1.0` and the same for `woocommerce_version`. The next page load schedules every callback above that version through Action Scheduler. `wp wc update` runs them immediately instead.
+- Feature flag defaults are persisted, so changing `enabled_by_default` alone doesn't change behavior on existing sites. Ship a migration or remove the flag.

 ## Comments and Docblocks

diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php
index 9ef661b20d1..a27be7d4b16 100644
--- a/plugins/woocommerce/includes/class-wc-install.php
+++ b/plugins/woocommerce/includes/class-wc-install.php
@@ -354,12 +354,8 @@ class WC_Install {
 			'wc_update_1120_remove_abandoned_cart_recovery',
 			'wc_update_1120_migrate_stock_notifications_alpha_constant',
 			'wc_update_1120_delete_surface_cart_checkout_note',
-		),
-		'11.2.0-1' => array(
 			'wc_update_11201_migrate_tax_lookup_order_items',
 			'wc_update_11201_invalidate_analytics_reports_cache',
-		),
-		'11.2.0-2' => array(
 			'wc_update_11202_reset_refund_returning_customer_markers',
 		),
 	);
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php
index 6dacbcf264c..54e627bf2d8 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php
@@ -66,17 +66,17 @@ class WC_Admin_Tests_Install extends WP_UnitTestCase {
 	 * Ensure that a DB version callback is defined when there are updates.
 	 */
 	public function test_db_update_callbacks_exist() {
+		include_once WC_ABSPATH . 'includes/wc-update-functions.php';
+
 		$all_callbacks = \WC_Install::get_db_update_callbacks();

 		foreach ( $all_callbacks as $version => $version_callbacks ) {
 			// Verify all callbacks have been defined.
 			foreach ( $version_callbacks as $version_callback ) {
-				if ( strpos( $version_callback, 'wc_admin_update' ) === 0 ) {
-					$this->assertTrue(
-						function_exists( $version_callback ),
-						"Callback {$version_callback}() is not defined."
-					);
-				}
+				$this->assertTrue(
+					function_exists( $version_callback ),
+					"Callback {$version_callback}() is not defined."
+				);
 			}
 		}
 	}
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-install-test.php b/plugins/woocommerce/tests/php/includes/class-wc-install-test.php
index 23f00ebc6fc..39fb3d4c52d 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-install-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-install-test.php
@@ -445,6 +445,14 @@ class WC_Install_Test extends \WC_Unit_Test_Case {
 			empty( $versions ) || version_compare( preg_replace( '/-.*$/', '', end( $versions ) ), WC()->stable_version(), '<=' ),
 			'WC_Install::$db_update_callbacks must not contain versions that are ahead of current stable (except, possibly, for suffix).',
 		);
+
+		// Sequential keys (X.Y.Z-1, X.Y.Z-2) are not needed for -dev versions.
+		if ( '-dev' === substr( WC()->version, -4 ) ) {
+			$this->assertEmpty(
+				preg_grep( '/^' . preg_quote( WC()->stable_version(), '/' ) . '-/', $versions ),
+				sprintf( 'WC_Install::$db_update_callbacks must not contain sequential keys for %1$s while the version is %2$s. Add the callbacks to the plain \'%1$s\' key instead.', WC()->stable_version(), WC()->version ),
+			);
+		}
 	}

 	/**
diff --git a/plugins/woocommerce/tests/php/includes/wc-update-functions-test.php b/plugins/woocommerce/tests/php/includes/wc-update-functions-test.php
index 62766af6084..8d24d758f87 100644
--- a/plugins/woocommerce/tests/php/includes/wc-update-functions-test.php
+++ b/plugins/woocommerce/tests/php/includes/wc-update-functions-test.php
@@ -13,8 +13,6 @@ use Automattic\WooCommerce\Blocks\InboxNotifications;
 use Automattic\WooCommerce\Blocks\Options as BlockOptions;
 use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
 use Automattic\WooCommerce\Enums\OrderStatus;
-use Automattic\WooCommerce\Internal\Admin\OrderTaxLookupMigrator;
-use Automattic\WooCommerce\Internal\BatchProcessing\BatchProcessingController;
 use Automattic\WooCommerce\Internal\Features\FeaturesController;
 use Automattic\WooCommerce\Internal\VariationGallery\Package as VariationGalleryPackage;

@@ -359,15 +357,11 @@ class WC_Update_Functions_Test extends \WC_Unit_Test_Case {
 	}

 	/**
-	 * @testdox Migration registers and removes the deprecated variation gallery feature option.
+	 * @testdox Migration removes the deprecated variation gallery feature option.
 	 */
 	public function test_wc_update_11101_remove_deprecated_variation_gallery_option(): void {
 		include_once WC_ABSPATH . 'includes/wc-update-functions.php';

-		$db_updates = WC_Install::get_db_update_callbacks();
-		$this->assertArrayHasKey( '11.1.0-1', $db_updates );
-		$this->assertContains( 'wc_update_11101_remove_deprecated_variation_gallery_option', $db_updates['11.1.0-1'] );
-
 		delete_option( VariationGalleryPackage::ENABLE_OPTION_NAME );
 		wc_update_11101_remove_deprecated_variation_gallery_option();
 		$this->assertFalse( get_option( VariationGalleryPackage::ENABLE_OPTION_NAME ) );
@@ -382,15 +376,11 @@ class WC_Update_Functions_Test extends \WC_Unit_Test_Case {
 	}

 	/**
-	 * @testdox Migration registers and deletes the cached dashboard out-of-stock count.
+	 * @testdox Migration deletes the cached dashboard out-of-stock count.
 	 */
 	public function test_wc_update_1110_delete_dashboard_outofstock_count_transient(): void {
 		include_once WC_ABSPATH . 'includes/wc-update-functions.php';

-		$db_updates = WC_Install::get_db_update_callbacks();
-		$this->assertArrayHasKey( '11.1.0', $db_updates );
-		$this->assertContains( 'wc_update_1110_delete_dashboard_outofstock_count_transient', $db_updates['11.1.0'] );
-
 		set_transient( 'wc_outofstock_count', 3, DAY_IN_SECONDS );
 		$this->assertSame( 3, get_transient( 'wc_outofstock_count' ) );

@@ -404,10 +394,6 @@ class WC_Update_Functions_Test extends \WC_Unit_Test_Case {
 	public function test_wc_update_1120_migrate_stock_notifications_alpha_constant_opts_in(): void {
 		include_once WC_ABSPATH . 'includes/wc-update-functions.php';

-		$db_updates = WC_Install::get_db_update_callbacks();
-		$this->assertArrayHasKey( '11.2.0', $db_updates );
-		$this->assertContains( 'wc_update_1120_migrate_stock_notifications_alpha_constant', $db_updates['11.2.0'] );
-
 		delete_option( 'woocommerce_feature_customer_stock_notifications_enabled' );
 		Constants::set_constant( 'WOOCOMMERCE_BIS_ALPHA_ENABLED', true );

@@ -477,41 +463,11 @@ class WC_Update_Functions_Test extends \WC_Unit_Test_Case {
 	}

 	/**
-	 * @testdox Migration registers and queues the rebuild of the tax lookup table.
-	 */
-	public function test_wc_update_11201_migrate_tax_lookup_order_items(): void {
-		include_once WC_ABSPATH . 'includes/wc-update-functions.php';
-
-		$db_updates = WC_Install::get_db_update_callbacks();
-
-		// Under its own key, so that a store already on 11.2.0 from the batch that shipped beside
-		// it still runs the rebuild.
-		$this->assertArrayHasKey( '11.2.0-1', $db_updates );
-		$this->assertContains( 'wc_update_11201_migrate_tax_lookup_order_items', $db_updates['11.2.0-1'] );
-
-		$batch_processor = wc_get_container()->get( BatchProcessingController::class );
-		$batch_processor->remove_processor( OrderTaxLookupMigrator::class );
-
-		wc_update_11201_migrate_tax_lookup_order_items();
-
-		$this->assertTrue(
-			$batch_processor->is_enqueued( OrderTaxLookupMigrator::class ),
-			'The migration should hand the rebuild to the batch processing controller.'
-		);
-
-		$batch_processor->remove_processor( OrderTaxLookupMigrator::class );
-	}
-
-	/**
-	 * @testdox Migration registers for WooCommerce 11.2.0 and deletes the retired Surface Cart and Checkout note.
+	 * @testdox Migration deletes the retired Surface Cart and Checkout note.
 	 */
 	public function test_wc_update_1120_delete_surface_cart_checkout_note(): void {
 		include_once WC_ABSPATH . 'includes/wc-update-functions.php';

-		$db_updates = WC_Install::get_db_update_callbacks();
-		$this->assertArrayHasKey( '11.2.0', $db_updates );
-		$this->assertContains( 'wc_update_1120_delete_surface_cart_checkout_note', $db_updates['11.2.0'] );
-
 		$note = new Note();
 		$note->set_name( InboxNotifications::SURFACE_CART_CHECKOUT_NOTE_NAME );
 		$note->set_title( 'Surface Cart and Checkout' );
@@ -530,31 +486,6 @@ class WC_Update_Functions_Test extends \WC_Unit_Test_Case {
 		$this->assertFalse( Notes::get_note_by_name( InboxNotifications::SURFACE_CART_CHECKOUT_NOTE_NAME ), 'The update should remain safe when the note is already absent.' );
 	}

-	/**
-	 * @testdox Migration invalidates the Analytics report cache, so a response cached before the update stops being served.
-	 */
-	public function test_wc_update_11201_invalidate_analytics_reports_cache(): void {
-		include_once WC_ABSPATH . 'includes/wc-update-functions.php';
-
-		$db_updates = WC_Install::get_db_update_callbacks();
-
-		// Under its own key, so that a store already on 11.2.0 from the batch that shipped beside
-		// it still drops its stale responses.
-		$this->assertArrayHasKey( '11.2.0-1', $db_updates );
-		$this->assertContains( 'wc_update_11201_invalidate_analytics_reports_cache', $db_updates['11.2.0-1'] );
-
-		// The cache version is a timestamp, so pin an old one rather than race the clock.
-		set_transient( ReportsCache::VERSION_OPTION . '-transient-version', '1000000000' );
-
-		$key = 'wc_report_products_pre_update';
-		ReportsCache::set( $key, 'pre-update response' );
-		$this->assertSame( 'pre-update response', ReportsCache::get( $key ), 'The response should be served from cache before the update runs' );
-
-		wc_update_11201_invalidate_analytics_reports_cache();
-
-		$this->assertFalse( ReportsCache::get( $key ), 'A response cached before the update should no longer be served' );
-	}
-
 	/**
 	 * @testdox Migration resets stale refund markers in batches and invalidates cached Analytics reports.
 	 */
@@ -563,11 +494,6 @@ class WC_Update_Functions_Test extends \WC_Unit_Test_Case {

 		include_once WC_ABSPATH . 'includes/wc-update-functions.php';

-		$db_updates = WC_Install::get_db_update_callbacks();
-		// Under its own key, so that a store already past the 11.2.0 batches still resets its markers.
-		$this->assertArrayHasKey( '11.2.0-2', $db_updates );
-		$this->assertContains( 'wc_update_11202_reset_refund_returning_customer_markers', $db_updates['11.2.0-2'] );
-
 		$order = WC_Helper_Order::create_order();
 		$order->set_status( OrderStatus::COMPLETED );
 		$order->save();
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/OrderTaxLookupMigratorTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/OrderTaxLookupMigratorTest.php
index be4390990e6..486ce3aa1de 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/OrderTaxLookupMigratorTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/OrderTaxLookupMigratorTest.php
@@ -466,6 +466,18 @@ class OrderTaxLookupMigratorTest extends WC_Unit_Test_Case {
 		$this->assertFalse( $batch_processor->is_enqueued( OrderTaxLookupMigrator::class ), 'The tool should be able to stop the rebuild.' );
 	}

+	/**
+	 * @testdox The database update queues the rebuild.
+	 */
+	public function test_database_update_queues_the_rebuild(): void {
+		$batch_processor = wc_get_container()->get( BatchProcessingController::class );
+		$batch_processor->remove_processor( OrderTaxLookupMigrator::class );
+
+		wc_update_11201_migrate_tax_lookup_order_items();
+
+		$this->assertTrue( $batch_processor->is_enqueued( OrderTaxLookupMigrator::class ), 'The database update should hand the rebuild to the batch processing controller.' );
+	}
+
 	/**
 	 * @testdox The tool is disabled on a store with nothing to rebuild.
 	 */