From 97129ed457e03bbbaa7a0b91447690b76ce9c165 Mon Sep 17 00:00:00 2001 From: peptiderepo Date: Tue, 21 Apr 2026 16:52:17 +0000 Subject: [PATCH] cf-removal: add v0.10.0 migration + tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New class PRAutoBlogger_Migrate_Remove_Cloudflare_V0100 (own file so class-activator.php stays under 300 lines). Runs from PRAutoBlogger::maybe_run_db_migrations() right after the v0.9.0 migration. Idempotent via prautoblogger_migrated_remove_cloudflare_v0100 flag option (picked up by the uninstall prefix sweep). Migrates any user on a Cloudflare image provider to Runware FLUX.1 schnell (closest equivalent, ~bash.0006/image). Detects the three shapes a legacy user can be in: registry-derived provider === 'cloudflare', image_provider option === 'cloudflare', or the legacy slug 'flux-1-schnell'. Cleans up three now-dead options: prautoblogger_cloudflare_ai_token, prautoblogger_cloudflare_account_id, prautoblogger_cf_image_via_gateway. Does NOT touch prautoblogger_ai_gateway_base_url — that option is shared with the OpenRouter AI Gateway route and must stay (live- verified test). Tests (tests/unit/Core/ActivatorRemoveCloudflareTest.php): - cloudflare user → runware schnell - non-cloudflare user preserved - runware user preserved - legacy 'flux-1-schnell' slug triggers migration - second run is a no-op (does not clobber admin manual changes) - AI Gateway URL preserved across every path ImageModelRegistryTest updated: allowed-provider set is now ['runware','openrouter']; new test_cloudflare_entries_removed_v0100 test pins the scope boundary. --- .../class-migrate-remove-cloudflare-v0100.php | 99 +++++++++++ includes/class-prautoblogger.php | 4 +- includes/core/class-image-pipeline.php | 8 +- .../Core/ActivatorRemoveCloudflareTest.php | 163 ++++++++++++++++++ .../unit/Services/ImageModelRegistryTest.php | 29 +++- 5 files changed, 289 insertions(+), 14 deletions(-) create mode 100644 includes/class-migrate-remove-cloudflare-v0100.php create mode 100644 tests/unit/Core/ActivatorRemoveCloudflareTest.php diff --git a/includes/class-migrate-remove-cloudflare-v0100.php b/includes/class-migrate-remove-cloudflare-v0100.php new file mode 100644 index 0000000..f864eb1 --- /dev/null +++ b/includes/class-migrate-remove-cloudflare-v0100.php @@ -0,0 +1,99 @@ + */ + private array $options = []; + + protected function setUp(): void { + parent::setUp(); + + $this->options = []; + + Functions\when( 'get_option' )->alias( + fn( $key, $default = false ) => $this->options[ $key ] ?? $default + ); + Functions\when( 'update_option' )->alias( + function ( $key, $value ) { + $this->options[ $key ] = $value; + return true; + } + ); + Functions\when( 'delete_option' )->alias( + function ( $key ) { + unset( $this->options[ $key ] ); + return true; + } + ); + } + + /** + * Cloudflare user: flipped to Runware schnell, CF credentials removed, + * gateway URL preserved, flag set. + */ + public function test_cloudflare_user_migrated_to_runware(): void { + $this->options = [ + 'prautoblogger_image_provider' => 'cloudflare', + 'prautoblogger_image_model' => 'flux-1-schnell', + 'prautoblogger_cloudflare_ai_token' => 'enc:SECRET', + 'prautoblogger_cloudflare_account_id' => 'acct-123', + 'prautoblogger_cf_image_via_gateway' => '1', + // Shared with OpenRouter AI Gateway — MUST be preserved. + 'prautoblogger_ai_gateway_base_url' => 'https://gateway.ai.cloudflare.com/v1/acct/gw/openrouter', + ]; + + \PRAutoBlogger_Migrate_Remove_Cloudflare_V0100::run(); + + $this->assertSame( 'runware', $this->options['prautoblogger_image_provider'] ); + $this->assertSame( 'runware:100@1', $this->options['prautoblogger_image_model'] ); + + $this->assertArrayNotHasKey( 'prautoblogger_cloudflare_ai_token', $this->options ); + $this->assertArrayNotHasKey( 'prautoblogger_cloudflare_account_id', $this->options ); + $this->assertArrayNotHasKey( 'prautoblogger_cf_image_via_gateway', $this->options ); + + $this->assertSame( + 'https://gateway.ai.cloudflare.com/v1/acct/gw/openrouter', + $this->options['prautoblogger_ai_gateway_base_url'], + 'AI Gateway URL (shared with OpenRouter route) must NOT be touched.' + ); + + $this->assertSame( '1', $this->options['prautoblogger_migrated_remove_cloudflare_v0100'] ); + } + + /** + * Non-Cloudflare user (e.g. already on OpenRouter Gemini): model + + * provider untouched; CF credentials still swept; flag set. + */ + public function test_non_cloudflare_user_preserved(): void { + $this->options = [ + 'prautoblogger_image_provider' => 'openrouter', + 'prautoblogger_image_model' => 'google/gemini-2.5-flash-image', + // Hypothetical orphans from an earlier experiment. + 'prautoblogger_cloudflare_ai_token' => 'enc:OLD', + 'prautoblogger_cloudflare_account_id' => 'acct-999', + 'prautoblogger_ai_gateway_base_url' => 'https://gateway.ai.cloudflare.com/v1/acct/gw/openrouter', + ]; + + \PRAutoBlogger_Migrate_Remove_Cloudflare_V0100::run(); + + $this->assertSame( 'openrouter', $this->options['prautoblogger_image_provider'] ); + $this->assertSame( 'google/gemini-2.5-flash-image', $this->options['prautoblogger_image_model'] ); + + $this->assertArrayNotHasKey( 'prautoblogger_cloudflare_ai_token', $this->options ); + $this->assertArrayNotHasKey( 'prautoblogger_cloudflare_account_id', $this->options ); + $this->assertSame( + 'https://gateway.ai.cloudflare.com/v1/acct/gw/openrouter', + $this->options['prautoblogger_ai_gateway_base_url'] + ); + $this->assertSame( '1', $this->options['prautoblogger_migrated_remove_cloudflare_v0100'] ); + } + + /** + * Runware user: provider + model untouched; flag set. + */ + public function test_runware_user_preserved(): void { + $this->options = [ + 'prautoblogger_image_provider' => 'runware', + 'prautoblogger_image_model' => 'runware:101@1', + ]; + + \PRAutoBlogger_Migrate_Remove_Cloudflare_V0100::run(); + + $this->assertSame( 'runware', $this->options['prautoblogger_image_provider'] ); + $this->assertSame( 'runware:101@1', $this->options['prautoblogger_image_model'] ); + $this->assertSame( '1', $this->options['prautoblogger_migrated_remove_cloudflare_v0100'] ); + } + + /** + * Legacy slug detection — the pre-registry `flux-1-schnell` model id + * still gets migrated even when the provider option is empty. + */ + public function test_legacy_slug_triggers_migration(): void { + $this->options = [ + 'prautoblogger_image_provider' => '', // Unset before v0.9.0 landed. + 'prautoblogger_image_model' => 'flux-1-schnell', + ]; + + \PRAutoBlogger_Migrate_Remove_Cloudflare_V0100::run(); + + $this->assertSame( 'runware', $this->options['prautoblogger_image_provider'] ); + $this->assertSame( 'runware:100@1', $this->options['prautoblogger_image_model'] ); + } + + /** + * Running the migration twice is idempotent — the second pass must + * return immediately and must not clobber a user change made after + * the first migration run. + */ + public function test_idempotent_second_run_is_noop(): void { + $this->options = [ + 'prautoblogger_image_provider' => 'cloudflare', + 'prautoblogger_image_model' => 'flux-1-schnell', + ]; + + \PRAutoBlogger_Migrate_Remove_Cloudflare_V0100::run(); + $this->assertSame( 'runware', $this->options['prautoblogger_image_provider'] ); + + // Simulate admin manually switching to OpenRouter after migration. + $this->options['prautoblogger_image_provider'] = 'openrouter'; + $this->options['prautoblogger_image_model'] = 'google/gemini-3.1-flash-image-preview'; + + \PRAutoBlogger_Migrate_Remove_Cloudflare_V0100::run(); + + // Second run must NOT overwrite the admin's manual choice. + $this->assertSame( 'openrouter', $this->options['prautoblogger_image_provider'] ); + $this->assertSame( 'google/gemini-3.1-flash-image-preview', $this->options['prautoblogger_image_model'] ); + } +} diff --git a/tests/unit/Services/ImageModelRegistryTest.php b/tests/unit/Services/ImageModelRegistryTest.php index b35ccce..73590b9 100644 --- a/tests/unit/Services/ImageModelRegistryTest.php +++ b/tests/unit/Services/ImageModelRegistryTest.php @@ -18,7 +18,8 @@ class ImageModelRegistryTest extends BaseTestCase { /** * Every entry returned by get_models() must carry a non-empty id and - * a provider of 'runware', 'openrouter', or 'cloudflare'. If this breaks, the + * a provider of 'runware' or 'openrouter' (Cloudflare Workers AI was + * removed as an image provider in v0.10.0). If this breaks, the * admin save flow will silently drop the provider derivation. */ public function test_every_registry_entry_has_id_and_known_provider(): void { @@ -31,7 +32,7 @@ class ImageModelRegistryTest extends BaseTestCase { $this->assertArrayHasKey( 'provider', $model ); $this->assertContains( $model['provider'], - [ 'runware', 'openrouter', 'cloudflare' ], + [ 'runware', 'openrouter' ], sprintf( 'Unexpected provider %s for model %s', (string) $model['provider'], (string) $model['id'] ) ); } @@ -42,14 +43,30 @@ class ImageModelRegistryTest extends BaseTestCase { * This is the core of the v0.8.0 collapsed-dropdown contract. */ public function test_provider_for_returns_paired_provider_for_known_model(): void { - $this->assertSame( - 'cloudflare', - \PRAutoBlogger_Image_Model_Registry::provider_for( 'flux-1-schnell' ) - ); $this->assertSame( 'openrouter', \PRAutoBlogger_Image_Model_Registry::provider_for( 'google/gemini-2.5-flash-image' ) ); + $this->assertSame( + 'runware', + \PRAutoBlogger_Image_Model_Registry::provider_for( 'runware:100@1' ) + ); + } + + /** + * v0.10.0 scope boundary: no Cloudflare entries remain in the registry. + * The legacy `flux-1-schnell` slug (pre-Runware) must resolve to '' so + * the migration's detection path catches it. If a registry regression + * re-adds a cloudflare entry this test fails loudly. + */ + public function test_cloudflare_entries_removed_v0100(): void { + foreach ( \PRAutoBlogger_Image_Model_Registry::get_models() as $model ) { + $this->assertNotSame( 'cloudflare', $model['provider'] ?? '' ); + } + $this->assertSame( + '', + \PRAutoBlogger_Image_Model_Registry::provider_for( 'flux-1-schnell' ) + ); } /**