cf-removal: add v0.10.0 migration + tests

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.
This commit is contained in:
peptiderepo
2026-04-21 16:52:17 +00:00
parent 21430ce3bf
commit 97129ed457
5 changed files with 289 additions and 14 deletions

View File

@@ -0,0 +1,163 @@
<?php
/**
* Tests for the v0.10.0 one-shot migration that removes Cloudflare
* Workers AI as an image provider — exercises the cloudflare-user
* path, the non-cloudflare-user path, idempotency, and the critical
* scope-boundary invariant that `prautoblogger_ai_gateway_base_url`
* (shared with the OpenRouter AI Gateway route) is left intact.
*
* See thread 2026-04-cloudflare-workers-ai-removal/01-cto-handoff.md.
*
* @package PRAutoBlogger\Tests\Core
*/
namespace PRAutoBlogger\Tests\Core;
use PRAutoBlogger\Tests\BaseTestCase;
use Brain\Monkey\Functions;
class ActivatorRemoveCloudflareTest extends BaseTestCase {
/** @var array<string, mixed> */
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'] );
}
}

View File

@@ -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' )
);
}
/**