Completes the image + A/B experiment workstream by integrating image generation into the post publishing pipeline. New classes: - class-image-pipeline.php: Orchestrates A/B generation (article-driven Image A → featured; source-driven Image B → post meta). Each image independently fallible; article publishes even if both fail. - class-image-prompt-builder.php: Synthesizes visual prompts from article title+content and Reddit source data. Appends CEO-locked 90s infomercial style suffix. Keeps prompts concise for FLUX.1 quality. - class-image-media-sideloader.php: Downloads image bytes, creates temp file, sideloads via media_handle_sideload(), sets alt text and metadata, cleans up temp files. Changes to existing files: - Publisher.attach_generated_images(): Runs image pipeline post-creation, sets _thumbnail_id (Image A) and _prautoblogger_image_b_id (Image B). Errors logged but don't block post publication. - Settings: prautoblogger_image_enabled (toggle, default off). Tests: - ImagePipelineTest.php: Orchestration, cost tracking, graceful failures. - ImagePromptBuilderTest.php: Prompt generation, length limits. Docs: - ARCHITECTURE.md: New step 6b in data flow, new files in file tree. - CHANGELOG.md: Commit 1a/1b breakdown. Co-authored-by: peptiderepo <peptiderepo@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
123 lines
3.4 KiB
PHP
123 lines
3.4 KiB
PHP
<?php
|
|
/**
|
|
* Tests for PRAutoBlogger_Image_Prompt_Builder.
|
|
*
|
|
* Validates prompt generation from article content and source data,
|
|
* including style suffix appending and HTML stripping.
|
|
*
|
|
* @package PRAutoBlogger\Tests\Core
|
|
*/
|
|
|
|
namespace PRAutoBlogger\Tests\Core;
|
|
|
|
use PRAutoBlogger\Tests\BaseTestCase;
|
|
use Brain\Monkey\Functions;
|
|
|
|
class ImagePromptBuilderTest extends BaseTestCase {
|
|
|
|
protected function setUp(): void {
|
|
parent::setUp();
|
|
|
|
// Mock WordPress functions.
|
|
Functions\when( 'wp_strip_all_tags' )->alias( function ( $str ) {
|
|
return wp_strip_all_tags( $str );
|
|
} );
|
|
Functions\when( 'sanitize_text_field' )->returnArg();
|
|
Functions\when( 'get_option' )->alias( function ( $key, $default = false ) {
|
|
static $options = [];
|
|
return $options[ $key ] ?? $default;
|
|
} );
|
|
}
|
|
|
|
/**
|
|
* Test build_article_prompt() generates a prompt from article title and content.
|
|
*/
|
|
public function test_build_article_prompt_from_content(): void {
|
|
$builder = new \PRAutoBlogger_Image_Prompt_Builder();
|
|
|
|
$article_data = [
|
|
'post_title' => 'How to Train Your Dragon',
|
|
'post_content' => '<p>Dragons are fascinating creatures. Learn the secrets.</p>',
|
|
];
|
|
|
|
$prompt = $builder->build_article_prompt( $article_data );
|
|
|
|
$this->assertStringContainsString( 'How to Train Your Dragon', $prompt );
|
|
$this->assertStringContainsString( 'Dragons are fascinating', $prompt );
|
|
// Style suffix should be appended.
|
|
$this->assertNotEmpty( $prompt );
|
|
$this->assertGreaterThan( 50, strlen( $prompt ) );
|
|
}
|
|
|
|
/**
|
|
* Test build_article_prompt() handles missing content gracefully.
|
|
*/
|
|
public function test_build_article_prompt_with_missing_content(): void {
|
|
$builder = new \PRAutoBlogger_Image_Prompt_Builder();
|
|
|
|
$article_data = [
|
|
'post_title' => 'Minimal Article',
|
|
];
|
|
|
|
$prompt = $builder->build_article_prompt( $article_data );
|
|
|
|
$this->assertStringContainsString( 'Minimal Article', $prompt );
|
|
$this->assertNotEmpty( $prompt );
|
|
}
|
|
|
|
/**
|
|
* Test build_source_prompt() generates a prompt from Reddit data.
|
|
*/
|
|
public function test_build_source_prompt_from_reddit_data(): void {
|
|
$builder = new \PRAutoBlogger_Image_Prompt_Builder();
|
|
|
|
$source_data = [
|
|
'title' => 'Help: My Robot Won\'t Stop Dancing',
|
|
'comments' => [
|
|
'Have you tried unplugging it?',
|
|
'Maybe it\'s just happy.',
|
|
],
|
|
];
|
|
|
|
$prompt = $builder->build_source_prompt( $source_data );
|
|
|
|
$this->assertStringContainsString( 'Robot', $prompt );
|
|
$this->assertStringContainsString( 'Dancing', $prompt );
|
|
$this->assertNotEmpty( $prompt );
|
|
}
|
|
|
|
/**
|
|
* Test build_source_prompt() handles missing comments.
|
|
*/
|
|
public function test_build_source_prompt_without_comments(): void {
|
|
$builder = new \PRAutoBlogger_Image_Prompt_Builder();
|
|
|
|
$source_data = [
|
|
'title' => 'Standalone Title',
|
|
];
|
|
|
|
$prompt = $builder->build_source_prompt( $source_data );
|
|
|
|
$this->assertStringContainsString( 'Standalone Title', $prompt );
|
|
$this->assertNotEmpty( $prompt );
|
|
}
|
|
|
|
/**
|
|
* Test that prompts don't exceed reasonable length.
|
|
*/
|
|
public function test_prompts_are_reasonably_sized(): void {
|
|
$builder = new \PRAutoBlogger_Image_Prompt_Builder();
|
|
|
|
$long_content = '<p>' . str_repeat( 'a', 5000 ) . '</p>';
|
|
$article_data = [
|
|
'post_title' => 'Test',
|
|
'post_content' => $long_content,
|
|
];
|
|
|
|
$prompt = $builder->build_article_prompt( $article_data );
|
|
|
|
// Prompt should be under 500 chars (concept + style suffix).
|
|
$this->assertLessThan( 500, strlen( $prompt ) );
|
|
}
|
|
}
|