feat(phase2b): P2b.4 — tier routing + Authority pipeline + citation/imagery gates (#181, v0.31.0)
Authority_Pipeline orchestrator (research→curate→draft→editorial→seo→publish gate) + per-category tier router behind master flag prautoblogger_authority_pipeline_enabled (DEFAULT OFF — zero behavior change until enabled). Cost-ceiling halts→hold; citation + imagery gates. QA APPROVE b086079; CI green (phpcs+phpunit VPS-verified). Agent-Session: prab-phase2b-4-merge
This commit is contained in:
402
tests/unit/Core/AuthorityPipelineTest.php
Normal file
402
tests/unit/Core/AuthorityPipelineTest.php
Normal file
@@ -0,0 +1,402 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for PRAutoBlogger_Authority_Pipeline and tier routing integration.
|
||||
*
|
||||
* Covers: Economy path taken when flag OFF (proven via TierRouter unit tests),
|
||||
* cost ceiling halt -> draft hold, citation gate (below/at threshold),
|
||||
* imagery suppressed on held articles, and stage order via mock calls.
|
||||
*
|
||||
* All dependencies that would make HTTP calls are mocked or stubbed.
|
||||
*
|
||||
* @package PRAutoBlogger\Tests\Core
|
||||
*/
|
||||
|
||||
namespace PRAutoBlogger\Tests\Core;
|
||||
|
||||
use PRAutoBlogger\Tests\BaseTestCase;
|
||||
use Brain\Monkey\Functions;
|
||||
|
||||
class AuthorityPipelineTest extends BaseTestCase {
|
||||
|
||||
/** @var object Mock $wpdb. */
|
||||
private object $wpdb;
|
||||
|
||||
/** @var array Rows inserted to run_decisions. */
|
||||
private array $decision_rows = array();
|
||||
|
||||
/** @var array Post meta written. */
|
||||
private array $meta_written = array();
|
||||
|
||||
/** @var array Posts that had wp_publish_post() called. */
|
||||
private array $published_posts = array();
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->decision_rows = array();
|
||||
$this->meta_written = array();
|
||||
$this->published_posts = array();
|
||||
|
||||
$this->wpdb = $this->create_mock_wpdb();
|
||||
$GLOBALS["wpdb"] = $this->wpdb;
|
||||
$this->wpdb->prefix = "wp_";
|
||||
|
||||
$this->wpdb->method( "prepare" )->willReturnCallback(
|
||||
static fn( $sql, ...$args ) => $sql . " /* " . implode( ",", array_map( "strval", $args ) ) . " */"
|
||||
);
|
||||
|
||||
$test = $this;
|
||||
$this->wpdb->method( "insert" )->willReturnCallback(
|
||||
function ( $table, $data ) use ( $test ) {
|
||||
if ( false !== strpos( $table, "run_decisions" ) ) {
|
||||
$test->decision_rows[] = $data;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
);
|
||||
|
||||
$this->wpdb->method( "get_var" )->willReturnCallback(
|
||||
static function ( $sql ) {
|
||||
if ( false !== strpos( $sql, "SHOW TABLES" ) ) {
|
||||
return "wp_prautoblogger_run_decisions";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
);
|
||||
$this->wpdb->method( "query" )->willReturn( 1 );
|
||||
$this->wpdb->method( "get_row" )->willReturn( null );
|
||||
$this->wpdb->method( "get_col" )->willReturn( array() );
|
||||
|
||||
\PRAutoBlogger_Audit_Writer::flush_cache();
|
||||
\PRAutoBlogger_Run_Stage_State::flush_cache();
|
||||
|
||||
// Common WP function stubs.
|
||||
Functions\when( "current_time" )->justReturn( "2026-06-24 00:00:00" );
|
||||
Functions\when( "wp_json_encode" )->alias( "json_encode" );
|
||||
Functions\when( "gmdate" )->alias( "gmdate" );
|
||||
Functions\when( "__" )->alias( static fn( $s ) => $s );
|
||||
Functions\when( "sanitize_text_field" )->alias( static fn( $s ) => $s );
|
||||
Functions\when( "wp_kses_post" )->alias( static fn( $s ) => $s );
|
||||
Functions\when( "wp_strip_all_tags" )->alias( "strip_tags" );
|
||||
Functions\when( "apply_filters" )->alias( static fn( $tag, $val ) => $val );
|
||||
Functions\when( "do_action" )->justReturn( null );
|
||||
Functions\when( "wp_insert_post" )->justReturn( 42 );
|
||||
Functions\when( "wp_set_post_terms" )->justReturn( array() );
|
||||
Functions\when( "wp_set_post_categories" )->justReturn( array() );
|
||||
Functions\when( "wp_set_post_tags" )->justReturn( array() );
|
||||
Functions\when( "post_type_exists" )->justReturn( false );
|
||||
Functions\when( "get_users" )->justReturn( array( 1 ) );
|
||||
Functions\when( "get_term_by" )->justReturn( false );
|
||||
Functions\when( "wp_insert_term" )->justReturn( array( "term_id" => 1 ) );
|
||||
Functions\when( "absint" )->alias( "intval" );
|
||||
Functions\when( "is_wp_error" )->justReturn( false );
|
||||
Functions\when( "get_post_meta" )->justReturn( "" );
|
||||
|
||||
$test = $this;
|
||||
Functions\when( "wp_publish_post" )->alias(
|
||||
function ( $id ) use ( $test ) {
|
||||
$test->published_posts[] = $id;
|
||||
}
|
||||
);
|
||||
|
||||
Functions\when( "update_post_meta" )->alias(
|
||||
function ( $post_id, $key, $value ) use ( $test ) {
|
||||
$test->meta_written[ $key ] = $value;
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
$this->stub_get_option( array(
|
||||
"prautoblogger_log_level" => "error",
|
||||
"prautoblogger_auto_publish" => "0",
|
||||
"prautoblogger_citation_score_threshold" => 0.0,
|
||||
"prautoblogger_writing_pipeline" => "single_pass",
|
||||
"prautoblogger_writing_model" => "openai/gpt-4o-mini",
|
||||
"prautoblogger_default_author" => 1,
|
||||
) );
|
||||
}
|
||||
|
||||
protected function tearDown(): void {
|
||||
\PRAutoBlogger_Audit_Writer::flush_cache();
|
||||
\PRAutoBlogger_Run_Stage_State::flush_cache();
|
||||
unset( $GLOBALS["wpdb"] );
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
// -- Cost ceiling halt ------------------------------------------------
|
||||
|
||||
/**
|
||||
* When the research stage throws PRAutoBlogger_Cost_Ceiling_Exception,
|
||||
* the pipeline must HOLD the article as draft (status="halted") and
|
||||
* must NOT force-complete or publish it.
|
||||
*/
|
||||
public function test_cost_ceiling_halt_holds_article(): void {
|
||||
$fanout = $this->make_fanout_throwing_ceiling();
|
||||
$tracker = $this->make_cost_tracker_stub();
|
||||
$pipeline = new \PRAutoBlogger_Authority_Pipeline( $tracker, $fanout );
|
||||
$result = $pipeline->run( "run-ceiling", $this->make_idea(), $tracker );
|
||||
|
||||
$this->assertSame( "halted", $result["status"] );
|
||||
$this->assertSame( 0, $result["published"] );
|
||||
$this->assertEmpty( $this->published_posts );
|
||||
}
|
||||
|
||||
// -- Citation gate ----------------------------------------------------
|
||||
|
||||
/**
|
||||
* When citation_score (computed by Seo_Stage from kept_sources) is below
|
||||
* the threshold, the article must be held as draft, not published.
|
||||
* auto_publish=1 is set so the only thing blocking publish is the gate.
|
||||
*/
|
||||
public function test_citation_gate_holds_below_threshold(): void {
|
||||
$this->stub_get_option( array(
|
||||
"prautoblogger_log_level" => "error",
|
||||
"prautoblogger_auto_publish" => "1",
|
||||
"prautoblogger_citation_score_threshold" => 0.8,
|
||||
"prautoblogger_writing_pipeline" => "single_pass",
|
||||
"prautoblogger_writing_model" => "openai/gpt-4o-mini",
|
||||
"prautoblogger_default_author" => 1,
|
||||
) );
|
||||
|
||||
$low_quality_sources = array(
|
||||
array( "url" => "https://a.com", "title" => "A", "quality_score" => 0.3 ),
|
||||
);
|
||||
|
||||
$fanout = $this->make_fanout_stub( $this->make_fanout_results( $low_quality_sources ) );
|
||||
$judge = $this->make_judge_stub( $low_quality_sources );
|
||||
$loop = $this->make_editorial_loop_stub( "<p>approved content</p>", false );
|
||||
$generator = $this->make_generator_stub( "<p>draft content</p>" );
|
||||
$tracker = $this->make_cost_tracker_stub();
|
||||
$pipeline = new \PRAutoBlogger_Authority_Pipeline( $tracker, $fanout, $judge, $loop, $generator );
|
||||
$result = $pipeline->run( "run-citation-low", $this->make_idea(), $tracker );
|
||||
|
||||
$this->assertSame( "held-citation", $result["status"] );
|
||||
$this->assertSame( 0, $result["published"] );
|
||||
$this->assertEmpty( $this->published_posts );
|
||||
$this->assertArrayHasKey( "_prautoblogger_imagery_suppressed", $this->meta_written );
|
||||
$this->assertSame( "1", $this->meta_written["_prautoblogger_imagery_suppressed"] );
|
||||
}
|
||||
|
||||
/**
|
||||
* When citation_score equals the threshold, the gate passes.
|
||||
* auto_publish=0 so we verify status is not "held-citation".
|
||||
*/
|
||||
public function test_citation_gate_passes_at_threshold(): void {
|
||||
$this->stub_get_option( array(
|
||||
"prautoblogger_log_level" => "error",
|
||||
"prautoblogger_auto_publish" => "0",
|
||||
"prautoblogger_citation_score_threshold" => 0.8,
|
||||
"prautoblogger_writing_pipeline" => "single_pass",
|
||||
"prautoblogger_writing_model" => "openai/gpt-4o-mini",
|
||||
"prautoblogger_default_author" => 1,
|
||||
) );
|
||||
|
||||
$high_quality_sources = array(
|
||||
array( "url" => "https://a.com", "title" => "A", "quality_score" => 0.8 ),
|
||||
);
|
||||
|
||||
$fanout = $this->make_fanout_stub( $this->make_fanout_results( $high_quality_sources ) );
|
||||
$judge = $this->make_judge_stub( $high_quality_sources );
|
||||
$loop = $this->make_editorial_loop_stub( "<p>approved content</p>", false );
|
||||
$generator = $this->make_generator_stub( "<p>draft content</p>" );
|
||||
$tracker = $this->make_cost_tracker_stub();
|
||||
$pipeline = new \PRAutoBlogger_Authority_Pipeline( $tracker, $fanout, $judge, $loop, $generator );
|
||||
$result = $pipeline->run( "run-citation-at", $this->make_idea(), $tracker );
|
||||
|
||||
$this->assertNotSame( "held-citation", $result["status"] );
|
||||
$this->assertArrayNotHasKey( "_prautoblogger_imagery_suppressed", $this->meta_written );
|
||||
}
|
||||
|
||||
// -- Imagery gate -----------------------------------------------------
|
||||
|
||||
/**
|
||||
* When the editorial loop escalates (article held), imagery must be
|
||||
* suppressed via _prautoblogger_imagery_suppressed=1 post-meta.
|
||||
*/
|
||||
public function test_imagery_suppressed_on_hold(): void {
|
||||
$sources = array( array( "url" => "https://a.com", "title" => "A", "quality_score" => 0.9 ) );
|
||||
$fanout = $this->make_fanout_stub( $this->make_fanout_results( $sources ) );
|
||||
$judge = $this->make_judge_stub( $sources );
|
||||
$loop = $this->make_editorial_loop_stub( "", true );
|
||||
$generator = $this->make_generator_stub( "<p>draft content</p>" );
|
||||
$tracker = $this->make_cost_tracker_stub();
|
||||
$pipeline = new \PRAutoBlogger_Authority_Pipeline( $tracker, $fanout, $judge, $loop, $generator );
|
||||
$result = $pipeline->run( "run-imagery", $this->make_idea(), $tracker );
|
||||
|
||||
$this->assertSame( "held-escalated", $result["status"] );
|
||||
$this->assertArrayHasKey( "_prautoblogger_imagery_suppressed", $this->meta_written );
|
||||
$this->assertSame( "1", $this->meta_written["_prautoblogger_imagery_suppressed"] );
|
||||
}
|
||||
|
||||
// -- Economy path flag check ------------------------------------------
|
||||
|
||||
/**
|
||||
* When the master flag is OFF, Tier_Router returns "economy".
|
||||
* The Authority pipeline is never instantiated or called.
|
||||
*/
|
||||
public function test_master_flag_off_economy_path_used(): void {
|
||||
Functions\when( "get_option" )->alias(
|
||||
function ( $name, $default = false ) {
|
||||
if ( "prautoblogger_authority_pipeline_enabled" === $name ) {
|
||||
return false;
|
||||
}
|
||||
if ( "prautoblogger_log_level" === $name ) {
|
||||
return "error";
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
);
|
||||
|
||||
$idea = $this->make_idea();
|
||||
$router = new \PRAutoBlogger_Tier_Router();
|
||||
$tier = $router->resolve( $idea );
|
||||
|
||||
$this->assertSame( "economy", $tier );
|
||||
$this->assertArrayNotHasKey( "_prautoblogger_imagery_suppressed", $this->meta_written );
|
||||
}
|
||||
|
||||
// -- Stage ordering ---------------------------------------------------
|
||||
|
||||
/**
|
||||
* Stage order: research -> curate -> draft -> editorial -> seo -> gate.
|
||||
* Verify that fanout, judge, generator, and editorial loop are called.
|
||||
*/
|
||||
public function test_authority_chain_runs_stages_in_order(): void {
|
||||
$sources = array( array( "url" => "https://a.com", "title" => "A", "quality_score" => 0.9 ) );
|
||||
$called = array();
|
||||
|
||||
$fanout = $this->getMockBuilder( \PRAutoBlogger_Research_Fanout::class )
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods( array( "dispatch" ) )
|
||||
->getMock();
|
||||
$fanout->expects( $this->once() )->method( "dispatch" )
|
||||
->willReturnCallback(
|
||||
function () use ( &$called, $sources ) {
|
||||
$called[] = "research";
|
||||
return $this->make_fanout_results( $sources );
|
||||
}
|
||||
);
|
||||
|
||||
$judge = $this->getMockBuilder( \PRAutoBlogger_Research_Judge::class )
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods( array( "curate" ) )
|
||||
->getMock();
|
||||
$judge->expects( $this->once() )->method( "curate" )
|
||||
->willReturnCallback(
|
||||
function () use ( &$called, $sources ) {
|
||||
$called[] = "curate";
|
||||
return $sources;
|
||||
}
|
||||
);
|
||||
|
||||
$generator = $this->getMockBuilder( \PRAutoBlogger_Content_Generator::class )
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods( array( "generate" ) )
|
||||
->getMock();
|
||||
$generator->expects( $this->once() )->method( "generate" )
|
||||
->willReturnCallback(
|
||||
function () use ( &$called ) {
|
||||
$called[] = "draft";
|
||||
return "<p>draft content</p>";
|
||||
}
|
||||
);
|
||||
|
||||
$loop = $this->getMockBuilder( \PRAutoBlogger_Editorial_Loop::class )
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods( array( "run", "was_escalated" ) )
|
||||
->getMock();
|
||||
$loop->expects( $this->once() )->method( "run" )
|
||||
->willReturnCallback(
|
||||
function () use ( &$called ) {
|
||||
$called[] = "editorial";
|
||||
return "<p>approved content</p>";
|
||||
}
|
||||
);
|
||||
$loop->method( "was_escalated" )->willReturn( false );
|
||||
|
||||
$tracker = $this->make_cost_tracker_stub();
|
||||
$pipeline = new \PRAutoBlogger_Authority_Pipeline( $tracker, $fanout, $judge, $loop, $generator );
|
||||
$result = $pipeline->run( "run-order", $this->make_idea(), $tracker );
|
||||
|
||||
$this->assertSame( array( "research", "curate", "draft", "editorial" ), $called );
|
||||
$this->assertIsArray( $result );
|
||||
$this->assertArrayHasKey( "status", $result );
|
||||
}
|
||||
|
||||
// -- Helpers ----------------------------------------------------------
|
||||
|
||||
private function make_idea(): \PRAutoBlogger_Article_Idea {
|
||||
return new \PRAutoBlogger_Article_Idea( array(
|
||||
"topic" => "BPC-157",
|
||||
"article_type" => "guide",
|
||||
"suggested_title" => "BPC-157 Guide",
|
||||
"summary" => "Comprehensive guide.",
|
||||
"score" => 0.9,
|
||||
) );
|
||||
}
|
||||
|
||||
private function make_cost_tracker_stub(): object {
|
||||
$tracker = $this->getMockBuilder( \PRAutoBlogger_Cost_Tracker::class )
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods( array( "get_run_id", "get_current_run_cost", "log_api_call" ) )
|
||||
->getMock();
|
||||
$tracker->method( "get_run_id" )->willReturn( "run-test" );
|
||||
$tracker->method( "get_current_run_cost" )->willReturn( 0.01 );
|
||||
return $tracker;
|
||||
}
|
||||
|
||||
private function make_fanout_throwing_ceiling(): object {
|
||||
$fanout = $this->getMockBuilder( \PRAutoBlogger_Research_Fanout::class )
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods( array( "dispatch" ) )
|
||||
->getMock();
|
||||
$fanout->method( "dispatch" )->willThrowException(
|
||||
new \PRAutoBlogger_Cost_Ceiling_Exception( "Test cost ceiling breach." )
|
||||
);
|
||||
return $fanout;
|
||||
}
|
||||
|
||||
private function make_fanout_stub( array $results ): object {
|
||||
$fanout = $this->getMockBuilder( \PRAutoBlogger_Research_Fanout::class )
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods( array( "dispatch" ) )
|
||||
->getMock();
|
||||
$fanout->method( "dispatch" )->willReturn( $results );
|
||||
return $fanout;
|
||||
}
|
||||
|
||||
private function make_judge_stub( array $kept_sources ): object {
|
||||
$judge = $this->getMockBuilder( \PRAutoBlogger_Research_Judge::class )
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods( array( "curate" ) )
|
||||
->getMock();
|
||||
$judge->method( "curate" )->willReturn( $kept_sources );
|
||||
return $judge;
|
||||
}
|
||||
|
||||
private function make_editorial_loop_stub( string $content, bool $escalated ): object {
|
||||
$loop = $this->getMockBuilder( \PRAutoBlogger_Editorial_Loop::class )
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods( array( "run", "was_escalated" ) )
|
||||
->getMock();
|
||||
$loop->method( "run" )->willReturn( $content );
|
||||
$loop->method( "was_escalated" )->willReturn( $escalated );
|
||||
return $loop;
|
||||
}
|
||||
|
||||
private function make_generator_stub( string $content ): object {
|
||||
$gen = $this->getMockBuilder( \PRAutoBlogger_Content_Generator::class )
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods( array( "generate" ) )
|
||||
->getMock();
|
||||
$gen->method( "generate" )->willReturn( $content );
|
||||
return $gen;
|
||||
}
|
||||
|
||||
private function make_fanout_results( array $sources ): array {
|
||||
return array(
|
||||
array( "sources" => $sources, "agent_role" => "mechanisms" ),
|
||||
array( "sources" => $sources, "agent_role" => "clinical" ),
|
||||
);
|
||||
}
|
||||
}
|
||||
123
tests/unit/Core/TierRouterTest.php
Normal file
123
tests/unit/Core/TierRouterTest.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for PRAutoBlogger_Tier_Router.
|
||||
*
|
||||
* Covers: master flag OFF always returns 'economy', unclassified category
|
||||
* defaults to authority, explicit 'economy' demotion, explicit 'authority'
|
||||
* entry treated as default authority.
|
||||
*
|
||||
* No LLM calls — the router is a pure option-read + conditional.
|
||||
*
|
||||
* @package PRAutoBlogger\Tests\Core
|
||||
*/
|
||||
|
||||
namespace PRAutoBlogger\Tests\Core;
|
||||
|
||||
use PRAutoBlogger\Tests\BaseTestCase;
|
||||
use Brain\Monkey\Functions;
|
||||
|
||||
class TierRouterTest extends BaseTestCase {
|
||||
|
||||
/** @var \PRAutoBlogger_Article_Idea Shared idea fixture. */
|
||||
private \PRAutoBlogger_Article_Idea $idea;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->idea = new \PRAutoBlogger_Article_Idea( array(
|
||||
'topic' => 'BPC-157 overview',
|
||||
'article_type' => 'guide',
|
||||
'suggested_title' => 'BPC-157 Guide',
|
||||
'summary' => 'Comprehensive guide.',
|
||||
'score' => 0.9,
|
||||
) );
|
||||
}
|
||||
|
||||
// ── Master flag OFF ──────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* When the master flag is OFF (default false), resolve() must always
|
||||
* return 'economy' regardless of category map contents.
|
||||
*
|
||||
* This is the critical proof that P2b.4 deployment = zero behavior change.
|
||||
*/
|
||||
public function test_master_flag_off_always_returns_economy(): void {
|
||||
$this->stub_get_option( array(
|
||||
'prautoblogger_authority_pipeline_enabled' => false,
|
||||
'prautoblogger_category_tiers' => array( 'guide' => 'authority' ),
|
||||
) );
|
||||
|
||||
$router = new \PRAutoBlogger_Tier_Router();
|
||||
$this->assertSame( 'economy', $router->resolve( $this->idea ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* String '0' is also a falsy master flag — must return 'economy'.
|
||||
*/
|
||||
public function test_master_flag_string_zero_returns_economy(): void {
|
||||
$this->stub_get_option( array(
|
||||
'prautoblogger_authority_pipeline_enabled' => '0',
|
||||
) );
|
||||
|
||||
$router = new \PRAutoBlogger_Tier_Router();
|
||||
$this->assertSame( 'economy', $router->resolve( $this->idea ) );
|
||||
}
|
||||
|
||||
// ── Master flag ON — category routing ────────────────────────────────
|
||||
|
||||
/**
|
||||
* When flag is ON and the idea's category is NOT in the tier map,
|
||||
* the default Authority tier must apply.
|
||||
*/
|
||||
public function test_unclassified_category_defaults_authority(): void {
|
||||
$this->stub_get_option( array(
|
||||
'prautoblogger_authority_pipeline_enabled' => '1',
|
||||
'prautoblogger_category_tiers' => array( 'news' => 'economy' ),
|
||||
) );
|
||||
|
||||
$router = new \PRAutoBlogger_Tier_Router();
|
||||
// 'guide' is not in the tier map → default authority.
|
||||
$this->assertSame( 'authority', $router->resolve( $this->idea ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* When flag is ON and the idea's category is explicitly set to 'economy'
|
||||
* in the tier map, resolve() must return 'economy'.
|
||||
*/
|
||||
public function test_explicitly_demoted_category_returns_economy(): void {
|
||||
$this->stub_get_option( array(
|
||||
'prautoblogger_authority_pipeline_enabled' => '1',
|
||||
'prautoblogger_category_tiers' => array( 'guide' => 'economy' ),
|
||||
) );
|
||||
|
||||
$router = new \PRAutoBlogger_Tier_Router();
|
||||
$this->assertSame( 'economy', $router->resolve( $this->idea ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* When flag is ON and the idea's category is explicitly set to 'authority'
|
||||
* in the tier map (redundant but valid), resolve() must return 'authority'.
|
||||
*/
|
||||
public function test_authority_category_returns_authority(): void {
|
||||
$this->stub_get_option( array(
|
||||
'prautoblogger_authority_pipeline_enabled' => '1',
|
||||
'prautoblogger_category_tiers' => array( 'guide' => 'authority' ),
|
||||
) );
|
||||
|
||||
$router = new \PRAutoBlogger_Tier_Router();
|
||||
$this->assertSame( 'authority', $router->resolve( $this->idea ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* When flag is ON and the tier map is empty, all categories default to 'authority'.
|
||||
*/
|
||||
public function test_empty_tier_map_defaults_authority(): void {
|
||||
$this->stub_get_option( array(
|
||||
'prautoblogger_authority_pipeline_enabled' => '1',
|
||||
'prautoblogger_category_tiers' => array(),
|
||||
) );
|
||||
|
||||
$router = new \PRAutoBlogger_Tier_Router();
|
||||
$this->assertSame( 'authority', $router->resolve( $this->idea ) );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user