test: split PublisherTest and OpenRouterProviderTest under the 300-line rule
QA F2 (P2): this PR pushed PublisherTest to 333 lines and OpenRouterProviderTest to 318. Both splits are mechanical (test groups moved verbatim to a sibling file; setUp duplicated; no test logic or assertion changes): - OpenRouterProviderTest (318 -> 169): wire_full_provider() + the two v0.18.1 retry-path integration tests move to OpenRouterProviderRetryTest.php (223). The remaining file is byte-identical to its pre-PR state on main. - PublisherTest (333 -> 202): the five post-creation side-effect tests (taxonomy, tags, run_id log linking, action/filter hooks — incl. the two pre-existing skipped ones, moved as-is) move to PublisherSideEffectsTest.php (183). Guard tests stay with the create_post() funnel tests. Suite count unchanged (302 tests); all four files pass locally. Agent-Session: prab-empty-draft-guard-20260611
This commit is contained in:
183
tests/unit/Core/PublisherSideEffectsTest.php
Normal file
183
tests/unit/Core/PublisherSideEffectsTest.php
Normal file
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
/**
|
||||
* Side-effect tests for PRAutoBlogger_Publisher::publish().
|
||||
*
|
||||
* Validates the post-creation side effects: taxonomy assignment
|
||||
* (category from article_type, tags from keywords), generation-log
|
||||
* linking by run_id, and the public action/filter hooks. Split from
|
||||
* PublisherTest.php to keep both files under the 300-line rule;
|
||||
* post-creation/status/metadata/guard tests stay in PublisherTest.
|
||||
*
|
||||
* @package PRAutoBlogger\Tests\Core
|
||||
*/
|
||||
|
||||
namespace PRAutoBlogger\Tests\Core;
|
||||
|
||||
use PRAutoBlogger\Tests\BaseTestCase;
|
||||
use Brain\Monkey\Functions;
|
||||
use Brain\Monkey\Actions;
|
||||
use Brain\Monkey\Filters;
|
||||
|
||||
class PublisherSideEffectsTest extends BaseTestCase {
|
||||
|
||||
private \PRAutoBlogger_Article_Idea $idea;
|
||||
private \PRAutoBlogger_Editorial_Review $review;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->idea = new \PRAutoBlogger_Article_Idea( $this->get_article_idea_fixture() );
|
||||
$this->review = new \PRAutoBlogger_Editorial_Review( $this->get_editorial_review_fixture() );
|
||||
|
||||
// Common stubs needed by Publisher.
|
||||
$this->stub_get_option( [
|
||||
'prautoblogger_writing_pipeline' => 'multi_step',
|
||||
'prautoblogger_writing_model' => 'anthropic/claude-sonnet-4',
|
||||
'prautoblogger_default_author' => 1,
|
||||
] );
|
||||
|
||||
Functions\when( 'sanitize_text_field' )->returnArg();
|
||||
Functions\when( 'wp_kses_post' )->returnArg();
|
||||
// v0.18.1 empty-content guard strips tags before the emptiness check.
|
||||
Functions\when( 'wp_strip_all_tags' )->alias( function ( $text ) {
|
||||
return trim( strip_tags( (string) $text ) );
|
||||
} );
|
||||
Functions\when( 'is_wp_error' )->justReturn( false );
|
||||
Functions\when( 'apply_filters' )->returnArg( 2 );
|
||||
Functions\when( 'absint' )->alias( function ( $val ) { return abs( (int) $val ); } );
|
||||
Functions\when( 'get_users' )->justReturn( [ 1 ] );
|
||||
Functions\when( 'get_term_by' )->justReturn( false );
|
||||
Functions\when( 'wp_insert_term' )->justReturn( [ 'term_id' => 5 ] );
|
||||
Functions\when( 'wp_set_post_categories' )->justReturn( true );
|
||||
Functions\when( 'wp_set_post_tags' )->justReturn( true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that taxonomy terms are assigned based on article_type from the idea.
|
||||
*/
|
||||
public function test_publish_assigns_category_from_article_type(): void {
|
||||
Functions\when( 'wp_insert_post' )->justReturn( 46 );
|
||||
|
||||
// get_term_by returns false (category doesn't exist), so wp_insert_term is called.
|
||||
$captured_category = null;
|
||||
Functions\when( 'wp_insert_term' )->alias( function ( $name, $taxonomy ) use ( &$captured_category ) {
|
||||
$captured_category = $name;
|
||||
return [ 'term_id' => 10 ];
|
||||
} );
|
||||
|
||||
$captured_category_ids = null;
|
||||
Functions\when( 'wp_set_post_categories' )->alias( function ( $post_id, $cat_ids ) use ( &$captured_category_ids ) {
|
||||
$captured_category_ids = $cat_ids;
|
||||
return true;
|
||||
} );
|
||||
|
||||
$wpdb = $this->create_mock_wpdb();
|
||||
$wpdb->method( 'query' )->willReturn( 0 );
|
||||
$wpdb->method( 'prepare' )->willReturn( '' );
|
||||
$GLOBALS['wpdb'] = $wpdb;
|
||||
|
||||
$publisher = new \PRAutoBlogger_Publisher();
|
||||
$publisher->publish( '<p>Content</p>', $this->idea, $this->review );
|
||||
|
||||
// Article type is 'guide' → category should be 'Guides'.
|
||||
$this->assertSame( 'Guides', $captured_category );
|
||||
$this->assertSame( [ 10 ], $captured_category_ids );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that target keywords are set as post tags.
|
||||
*/
|
||||
public function test_publish_sets_tags_from_keywords(): void {
|
||||
Functions\when( 'wp_insert_post' )->justReturn( 47 );
|
||||
|
||||
$captured_tags = null;
|
||||
Functions\when( 'wp_set_post_tags' )->alias( function ( $post_id, $tags, $append ) use ( &$captured_tags ) {
|
||||
$captured_tags = $tags;
|
||||
return true;
|
||||
} );
|
||||
|
||||
$wpdb = $this->create_mock_wpdb();
|
||||
$wpdb->method( 'query' )->willReturn( 0 );
|
||||
$wpdb->method( 'prepare' )->willReturn( '' );
|
||||
$GLOBALS['wpdb'] = $wpdb;
|
||||
|
||||
$publisher = new \PRAutoBlogger_Publisher();
|
||||
$publisher->publish( '<p>Content</p>', $this->idea, $this->review );
|
||||
|
||||
$this->assertSame( [ 'test', 'keyword', 'example' ], $captured_tags );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that generation log entries are linked via run_id.
|
||||
*/
|
||||
public function test_publish_links_generation_logs_by_run_id(): void {
|
||||
Functions\when( 'wp_insert_post' )->justReturn( 48 );
|
||||
|
||||
// Create an idea fixture without source_ids to avoid triggering SourceCollector
|
||||
// queries that would overwrite captured_query in the mock.
|
||||
$fixture = $this->get_article_idea_fixture();
|
||||
$fixture['source_ids'] = [];
|
||||
$idea = new \PRAutoBlogger_Article_Idea( $fixture );
|
||||
|
||||
$captured_query = null;
|
||||
$wpdb = $this->create_mock_wpdb();
|
||||
$wpdb->method( 'prepare' )->willReturnCallback( function ( $sql, ...$args ) use ( &$captured_query ) {
|
||||
$captured_query = $sql;
|
||||
return $sql;
|
||||
} );
|
||||
$wpdb->method( 'query' )->willReturn( 1 );
|
||||
$GLOBALS['wpdb'] = $wpdb;
|
||||
|
||||
$publisher = new \PRAutoBlogger_Publisher();
|
||||
$publisher->publish( '<p>Content</p>', $idea, $this->review, 'run_link_test' );
|
||||
|
||||
// Should use run_id-based query, not timestamp-based.
|
||||
$this->assertStringContainsString( 'run_id', $captured_query );
|
||||
$this->assertStringNotContainsString( 'created_at', $captured_query );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the prautoblogger_post_created action is fired after publishing.
|
||||
*/
|
||||
public function test_publish_fires_post_created_action(): void {
|
||||
// TODO(peptiderepo): this test is latently broken — Mockery expects do_action_prautoblogger_post_created
|
||||
// to be called, but Publisher::publish() is not firing it in the current mock setup. Track in a follow-up
|
||||
// PR; skipping for now so CI parity can land. The bug is likely a mis-mocked do_action or a refactor
|
||||
// that removed the action fire.
|
||||
$this->markTestSkipped( 'Latent bug — see TODO; tracked for follow-up PR.' );
|
||||
Functions\when( 'wp_insert_post' )->justReturn( 49 );
|
||||
Functions\when( 'do_action' )->alias( function () {} );
|
||||
|
||||
Actions\expectDone( 'prautoblogger_post_created' )
|
||||
->once()
|
||||
->with( 49, 'publish', $this->idea, $this->review );
|
||||
|
||||
$wpdb = $this->create_mock_wpdb();
|
||||
$wpdb->method( 'query' )->willReturn( 0 );
|
||||
$wpdb->method( 'prepare' )->willReturn( '' );
|
||||
$GLOBALS['wpdb'] = $wpdb;
|
||||
|
||||
$publisher = new \PRAutoBlogger_Publisher();
|
||||
$publisher->publish( '<p>Content</p>', $this->idea, $this->review );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the prautoblogger_filter_post_data filter is applied.
|
||||
*/
|
||||
public function test_publish_applies_post_data_filter(): void {
|
||||
// TODO(peptiderepo): latently broken alongside test_publish_fires_post_created_action.
|
||||
// apply_filters('prautoblogger_post_data', ...) expected but not called. Follow-up PR.
|
||||
$this->markTestSkipped( 'Latent bug — see TODO; tracked for follow-up PR.' );
|
||||
Functions\when( 'wp_insert_post' )->justReturn( 50 );
|
||||
|
||||
Filters\expectApplied( 'prautoblogger_filter_post_data' )->once();
|
||||
|
||||
$wpdb = $this->create_mock_wpdb();
|
||||
$wpdb->method( 'query' )->willReturn( 0 );
|
||||
$wpdb->method( 'prepare' )->willReturn( '' );
|
||||
$GLOBALS['wpdb'] = $wpdb;
|
||||
|
||||
$publisher = new \PRAutoBlogger_Publisher();
|
||||
$publisher->publish( '<p>Content</p>', $this->idea, $this->review );
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,6 @@ namespace PRAutoBlogger\Tests\Core;
|
||||
|
||||
use PRAutoBlogger\Tests\BaseTestCase;
|
||||
use Brain\Monkey\Functions;
|
||||
use Brain\Monkey\Actions;
|
||||
use Brain\Monkey\Filters;
|
||||
|
||||
class PublisherTest extends BaseTestCase {
|
||||
|
||||
@@ -171,135 +169,6 @@ class PublisherTest extends BaseTestCase {
|
||||
$publisher->publish( '<p>Content</p>', $this->idea, $this->review );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that taxonomy terms are assigned based on article_type from the idea.
|
||||
*/
|
||||
public function test_publish_assigns_category_from_article_type(): void {
|
||||
Functions\when( 'wp_insert_post' )->justReturn( 46 );
|
||||
|
||||
// get_term_by returns false (category doesn't exist), so wp_insert_term is called.
|
||||
$captured_category = null;
|
||||
Functions\when( 'wp_insert_term' )->alias( function ( $name, $taxonomy ) use ( &$captured_category ) {
|
||||
$captured_category = $name;
|
||||
return [ 'term_id' => 10 ];
|
||||
} );
|
||||
|
||||
$captured_category_ids = null;
|
||||
Functions\when( 'wp_set_post_categories' )->alias( function ( $post_id, $cat_ids ) use ( &$captured_category_ids ) {
|
||||
$captured_category_ids = $cat_ids;
|
||||
return true;
|
||||
} );
|
||||
|
||||
$wpdb = $this->create_mock_wpdb();
|
||||
$wpdb->method( 'query' )->willReturn( 0 );
|
||||
$wpdb->method( 'prepare' )->willReturn( '' );
|
||||
$GLOBALS['wpdb'] = $wpdb;
|
||||
|
||||
$publisher = new \PRAutoBlogger_Publisher();
|
||||
$publisher->publish( '<p>Content</p>', $this->idea, $this->review );
|
||||
|
||||
// Article type is 'guide' → category should be 'Guides'.
|
||||
$this->assertSame( 'Guides', $captured_category );
|
||||
$this->assertSame( [ 10 ], $captured_category_ids );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that target keywords are set as post tags.
|
||||
*/
|
||||
public function test_publish_sets_tags_from_keywords(): void {
|
||||
Functions\when( 'wp_insert_post' )->justReturn( 47 );
|
||||
|
||||
$captured_tags = null;
|
||||
Functions\when( 'wp_set_post_tags' )->alias( function ( $post_id, $tags, $append ) use ( &$captured_tags ) {
|
||||
$captured_tags = $tags;
|
||||
return true;
|
||||
} );
|
||||
|
||||
$wpdb = $this->create_mock_wpdb();
|
||||
$wpdb->method( 'query' )->willReturn( 0 );
|
||||
$wpdb->method( 'prepare' )->willReturn( '' );
|
||||
$GLOBALS['wpdb'] = $wpdb;
|
||||
|
||||
$publisher = new \PRAutoBlogger_Publisher();
|
||||
$publisher->publish( '<p>Content</p>', $this->idea, $this->review );
|
||||
|
||||
$this->assertSame( [ 'test', 'keyword', 'example' ], $captured_tags );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that generation log entries are linked via run_id.
|
||||
*/
|
||||
public function test_publish_links_generation_logs_by_run_id(): void {
|
||||
Functions\when( 'wp_insert_post' )->justReturn( 48 );
|
||||
|
||||
// Create an idea fixture without source_ids to avoid triggering SourceCollector
|
||||
// queries that would overwrite captured_query in the mock.
|
||||
$fixture = $this->get_article_idea_fixture();
|
||||
$fixture['source_ids'] = [];
|
||||
$idea = new \PRAutoBlogger_Article_Idea( $fixture );
|
||||
|
||||
$captured_query = null;
|
||||
$wpdb = $this->create_mock_wpdb();
|
||||
$wpdb->method( 'prepare' )->willReturnCallback( function ( $sql, ...$args ) use ( &$captured_query ) {
|
||||
$captured_query = $sql;
|
||||
return $sql;
|
||||
} );
|
||||
$wpdb->method( 'query' )->willReturn( 1 );
|
||||
$GLOBALS['wpdb'] = $wpdb;
|
||||
|
||||
$publisher = new \PRAutoBlogger_Publisher();
|
||||
$publisher->publish( '<p>Content</p>', $idea, $this->review, 'run_link_test' );
|
||||
|
||||
// Should use run_id-based query, not timestamp-based.
|
||||
$this->assertStringContainsString( 'run_id', $captured_query );
|
||||
$this->assertStringNotContainsString( 'created_at', $captured_query );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the prautoblogger_post_created action is fired after publishing.
|
||||
*/
|
||||
public function test_publish_fires_post_created_action(): void {
|
||||
// TODO(peptiderepo): this test is latently broken — Mockery expects do_action_prautoblogger_post_created
|
||||
// to be called, but Publisher::publish() is not firing it in the current mock setup. Track in a follow-up
|
||||
// PR; skipping for now so CI parity can land. The bug is likely a mis-mocked do_action or a refactor
|
||||
// that removed the action fire.
|
||||
$this->markTestSkipped( 'Latent bug — see TODO; tracked for follow-up PR.' );
|
||||
Functions\when( 'wp_insert_post' )->justReturn( 49 );
|
||||
Functions\when( 'do_action' )->alias( function () {} );
|
||||
|
||||
Actions\expectDone( 'prautoblogger_post_created' )
|
||||
->once()
|
||||
->with( 49, 'publish', $this->idea, $this->review );
|
||||
|
||||
$wpdb = $this->create_mock_wpdb();
|
||||
$wpdb->method( 'query' )->willReturn( 0 );
|
||||
$wpdb->method( 'prepare' )->willReturn( '' );
|
||||
$GLOBALS['wpdb'] = $wpdb;
|
||||
|
||||
$publisher = new \PRAutoBlogger_Publisher();
|
||||
$publisher->publish( '<p>Content</p>', $this->idea, $this->review );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the prautoblogger_filter_post_data filter is applied.
|
||||
*/
|
||||
public function test_publish_applies_post_data_filter(): void {
|
||||
// TODO(peptiderepo): latently broken alongside test_publish_fires_post_created_action.
|
||||
// apply_filters('prautoblogger_post_data', ...) expected but not called. Follow-up PR.
|
||||
$this->markTestSkipped( 'Latent bug — see TODO; tracked for follow-up PR.' );
|
||||
Functions\when( 'wp_insert_post' )->justReturn( 50 );
|
||||
|
||||
Filters\expectApplied( 'prautoblogger_filter_post_data' )->once();
|
||||
|
||||
$wpdb = $this->create_mock_wpdb();
|
||||
$wpdb->method( 'query' )->willReturn( 0 );
|
||||
$wpdb->method( 'prepare' )->willReturn( '' );
|
||||
$GLOBALS['wpdb'] = $wpdb;
|
||||
|
||||
$publisher = new \PRAutoBlogger_Publisher();
|
||||
$publisher->publish( '<p>Content</p>', $this->idea, $this->review );
|
||||
}
|
||||
|
||||
/**
|
||||
* v0.18.1 belt-and-braces: save_as_draft() with empty content throws
|
||||
* and never reaches wp_insert_post — no empty draft post can be
|
||||
|
||||
Reference in New Issue
Block a user