fix(image-pipeline): surface per-image failures in the event log

Two gaps made posts 650/657 appear to silently "lose" their featured
images on 2026-04-20:

1. The batch provider tagged each failed key with ['error' => ...] but
   the ERROR log line it emitted didn't identify which key failed, so
   operators couldn't tell image_a from image_b in the event log when
   one of two parallel requests died.

2. Image_Pipeline::process_image_a/_b folded the per-key error into
   $result['errors'] and returned silently, with no WARNING row. Ops
   only learned about the miss when the outer catch in the caller fired
   — and on the 2026-04-20 incident that outer catch didn't fire, so
   nothing was logged at all except an unrelated pricing warning.

Fix:

- class-open-router-image-batch.php: collect_result() takes the request
  key as its first argument and every Logger::error message reads
  'OpenRouter batch image gen <class> for key "<key>": <detail>'.
  Matches the shape of OpenRouter_Image_Support::log_retry.

- class-image-pipeline.php: process_image_a and process_image_b now
  emit a Logger::warning('image_pipeline') row whenever $image_data is
  missing or carries an error key, in addition to pushing to
  $result['errors']. The warning fires whether or not the outer catch
  runs.

Test: ImagePipelineTest::test_partial_batch_failure_emits_warning_log_row
mocks $wpdb, runs the pipeline with image_a erroring, and asserts at
least one warning/image_pipeline row was inserted.

No behavior change beyond logging; cost impact zero.

Part of the v0.8.0 image bundle (issues from thread image-mime-bug).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
peptiderepo
2026-04-21 00:59:54 +07:00
parent 24a025c173
commit a3ed663bae
3 changed files with 98 additions and 9 deletions

View File

@@ -211,6 +211,59 @@ class ImagePipelineTest extends BaseTestCase {
$this->assertStringContainsString( 'API timeout', implode( ' ', $result['errors'] ) );
}
/**
* Test that a Logger::warning row is persisted when image_data carries an error.
*
* Covers Issue 2 from the image-mime-bug thread: previously the pipeline
* pushed the error into $result['errors'] silently, with no WARNING-level
* event_log row visible to ops. This regression-guards that a WARNING row
* now fires whether or not the outer catch in the caller triggers.
*/
public function test_partial_batch_failure_emits_warning_log_row(): void {
// Seed Logger's log_level so warnings actually get written.
Functions\when( 'get_option' )->alias( function ( $key, $default = false ) {
static $options = [
'prautoblogger_image_enabled' => '1',
'prautoblogger_log_level' => 'info',
];
return $options[ $key ] ?? $default;
} );
$mock_wpdb = $this->create_mock_wpdb();
$captured_rows = [];
$mock_wpdb->method( 'insert' )->willReturnCallback(
function ( $table, $row ) use ( &$captured_rows ) {
$captured_rows[] = $row;
return 1;
}
);
$GLOBALS['wpdb'] = $mock_wpdb;
$provider = $this->createMock( \PRAutoBlogger_Image_Provider_Interface::class );
$provider->method( 'estimate_cost' )->willReturn( 0.05 );
$provider->method( 'generate_image_batch' )->willReturn( [
'image_a' => [ 'error' => 'HTTP 400: flux-1-schnell is not a valid model ID' ],
] );
$cost_tracker = $this->createMock( \PRAutoBlogger_Cost_Tracker::class );
$cost_tracker->method( 'would_exceed_budget' )->willReturn( false );
$pipeline = new \PRAutoBlogger_Image_Pipeline( $provider, $cost_tracker );
$pipeline->generate_and_attach_images( 1, [ 'post_title' => 'Test' ], null );
$warning_rows = array_filter(
$captured_rows,
static fn( $row ) => ( $row['level'] ?? '' ) === 'warning'
&& ( $row['context'] ?? '' ) === 'image_pipeline'
);
$this->assertNotEmpty(
$warning_rows,
'Expected at least one image_pipeline WARNING row when image_data has an error key.'
);
unset( $GLOBALS['wpdb'] );
}
/**
* Test graceful handling when one image in the batch returns an error.
*/