fix(images): CTO review blockers on PR #9
Addresses four blockers from CTO review in
convo/prautoblogger/threads/2026-04-image-pipeline/04-cto-block-pr9.md:
1. PHPUnit red — 2 retry-path tests failing on bad mocks.
The success-after-retry branch calls wp_remote_retrieve_header()
only on the success attempt; the original mocks queued two mime
values and popped the first (text/plain), which made the provider's
normalize_response_body() throw "unexpected shape". Replaced the
pop-queue with a single justReturn('image/png'). The retry-after
header read on the 5xx/429 branch still works (coerces to int 0,
falls back to exponential backoff).
2. XSS defense-in-depth — wrap untrusted API response bodies in
esc_html() before they land in exception / user-facing messages.
- class-cloudflare-image-provider.php: 4xx-throw and
retry-exhaustion-throw now escape substr($raw, 0, 300) and
$last_error respectively.
- class-cloudflare-image-validator.php: network-error and
HTTP-error-body messages now use esc_html__() on the template
and esc_html() on the substituted values.
3. PHPCS hygiene on the 4 new files (CTO path A). Converted the
one-line private-method docblocks to full multi-line format with
@param / @return / @throws tags. Added a docblock to __construct.
4. File-size cap. The docblock expansion pushed
class-cloudflare-image-provider.php over 300 lines. Extracted
credential lookups + URL builder + log forwarders into a new
class-cloudflare-image-support.php (101 lines). Provider is now
271 lines, all new files under the 300-line cap.
No production behaviour changed beyond the escape wrapping. The
provider's public API, retry policy, and response handling are
identical to c823618.
File sizes after fix:
- interface-image-provider.php: 100
- class-cloudflare-image-provider.php: 271
- class-cloudflare-image-pricing.php: 78
- class-cloudflare-image-validator.php: 116
- class-cloudflare-image-support.php: 101
Agent-Session: cowork-2026-04-15-image-pipeline-commit-1a-cto-blockers
This commit is contained in:
@@ -308,39 +308,29 @@ class CloudflareImageProviderTest extends BaseTestCase {
|
||||
public function test_generate_image_retries_on_5xx_then_succeeds(): void {
|
||||
$this->with_token( 'test-token' );
|
||||
|
||||
$codes = [ 500, 200 ];
|
||||
Functions\when( 'wp_remote_post' )->alias(
|
||||
function () use ( &$codes ) {
|
||||
$code = array_shift( $codes ) ?? 200;
|
||||
return [
|
||||
'body' => 500 === $code ? 'upstream hiccup' : self::FAKE_IMAGE_BYTES,
|
||||
'response' => [ 'code' => $code ],
|
||||
'headers' => [ 'content-type' => 500 === $code ? 'text/plain' : 'image/png' ],
|
||||
];
|
||||
}
|
||||
);
|
||||
$response_codes = [ 500, 200 ];
|
||||
$response_bodies = [ 'upstream hiccup', self::FAKE_IMAGE_BYTES ];
|
||||
|
||||
$response_codes = [ 500, 200 ];
|
||||
Functions\when( 'wp_remote_post' )->justReturn( [
|
||||
'body' => self::FAKE_IMAGE_BYTES,
|
||||
'response' => [ 'code' => 200 ],
|
||||
'headers' => [ 'content-type' => 'image/png' ],
|
||||
] );
|
||||
Functions\when( 'wp_remote_retrieve_response_code' )->alias(
|
||||
function () use ( &$response_codes ) {
|
||||
return array_shift( $response_codes ) ?? 200;
|
||||
}
|
||||
);
|
||||
$response_bodies = [ 'upstream hiccup', self::FAKE_IMAGE_BYTES ];
|
||||
Functions\when( 'wp_remote_retrieve_body' )->alias(
|
||||
function () use ( &$response_bodies ) {
|
||||
return array_shift( $response_bodies ) ?? self::FAKE_IMAGE_BYTES;
|
||||
}
|
||||
);
|
||||
$mime_types = [ 'text/plain', 'image/png' ];
|
||||
Functions\when( 'wp_remote_retrieve_header' )->alias(
|
||||
function ( $_resp, $name ) use ( &$mime_types ) {
|
||||
if ( 'content-type' === strtolower( (string) $name ) ) {
|
||||
return array_shift( $mime_types ) ?? 'image/png';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
);
|
||||
// wp_remote_retrieve_header is called only on the success attempt —
|
||||
// the 5xx branch returns before any header lookup. One justReturn
|
||||
// for the content-type is enough; retry-after lookups accept the
|
||||
// same stub and will coerce to (int) 0.
|
||||
Functions\when( 'wp_remote_retrieve_header' )->justReturn( 'image/png' );
|
||||
|
||||
$provider = new \PRAutoBlogger_Cloudflare_Image_Provider();
|
||||
$result = $provider->generate_image( 'a prompt', 1080, 1080 );
|
||||
@@ -392,38 +382,28 @@ class CloudflareImageProviderTest extends BaseTestCase {
|
||||
public function test_generate_image_retries_on_429(): void {
|
||||
$this->with_token( 'test-token' );
|
||||
|
||||
$codes = [ 429, 200 ];
|
||||
Functions\when( 'wp_remote_post' )->alias(
|
||||
function () use ( &$codes ) {
|
||||
$code = array_shift( $codes ) ?? 200;
|
||||
return [
|
||||
'body' => 429 === $code ? 'slow down' : self::FAKE_IMAGE_BYTES,
|
||||
'response' => [ 'code' => $code ],
|
||||
'headers' => [ 'content-type' => 429 === $code ? 'text/plain' : 'image/png' ],
|
||||
];
|
||||
}
|
||||
);
|
||||
$response_codes = [ 429, 200 ];
|
||||
$response_codes = [ 429, 200 ];
|
||||
$response_bodies = [ 'slow down', self::FAKE_IMAGE_BYTES ];
|
||||
|
||||
Functions\when( 'wp_remote_post' )->justReturn( [
|
||||
'body' => self::FAKE_IMAGE_BYTES,
|
||||
'response' => [ 'code' => 200 ],
|
||||
'headers' => [ 'content-type' => 'image/png' ],
|
||||
] );
|
||||
Functions\when( 'wp_remote_retrieve_response_code' )->alias(
|
||||
function () use ( &$response_codes ) {
|
||||
return array_shift( $response_codes ) ?? 200;
|
||||
}
|
||||
);
|
||||
$response_bodies = [ 'slow down', self::FAKE_IMAGE_BYTES ];
|
||||
Functions\when( 'wp_remote_retrieve_body' )->alias(
|
||||
function () use ( &$response_bodies ) {
|
||||
return array_shift( $response_bodies ) ?? self::FAKE_IMAGE_BYTES;
|
||||
}
|
||||
);
|
||||
$mime_types = [ 'text/plain', 'image/png' ];
|
||||
Functions\when( 'wp_remote_retrieve_header' )->alias(
|
||||
function ( $_resp, $name ) use ( &$mime_types ) {
|
||||
if ( 'content-type' === strtolower( (string) $name ) ) {
|
||||
return array_shift( $mime_types ) ?? 'image/png';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
);
|
||||
// Content-type is only read on the success attempt; retry-after is
|
||||
// read on the 429 attempt and (int)-coerces anything non-numeric
|
||||
// back to 0, falling through to exponential backoff.
|
||||
Functions\when( 'wp_remote_retrieve_header' )->justReturn( 'image/png' );
|
||||
|
||||
$provider = new \PRAutoBlogger_Cloudflare_Image_Provider();
|
||||
$result = $provider->generate_image( 'a prompt', 1080, 1080 );
|
||||
|
||||
Reference in New Issue
Block a user