- Dossier_Actions (4 AJAX endpoints, manage_options+nonce): save fork / queue replay / queue rebuild / stage-status poll. Run identity always derived server-side from post meta; the AJAX layer validates + queues ONLY — all mutations happen in the cron handlers (chained-cron, CPO hard constraint). Fork saves lock message structure (count/roles) so every version stays replayable. - Edit panel per editable stage: per-message textareas prefilled from the latest fork or the recorded request_json; explicit fork + downstream-stale + queued-execution copy; spend strip with ceiling warning (filterable fraction, default 0.8). Disabled stages always show WHY (policy reasons; missing-input retention copy). - Stale / human-edited / attempt chips per stage + header badges (M2 slot filled); dossier polls stage status while a re-run is active (board poll interval setting) and re-renders when the run settles. - F2: sidebar Models & Prompts card (per-stage model/pv/role) + Run Spend card. F3: log-only stages (image_a/b, llm_research, ...) render from generation_log + the _prautoblogger_image_role attachment grid. - M2 correctness: dossier is now item-scoped (idea-hash meta) and excludes gen_log rows linked to other posts of a multi-article run. - Run-list visibility (product AC): board cards get a Human-modified chip; Review Queue rows get a stale-stages chip linking to the dossier. Both via single batched queries (no N+1). - v0.20.0 + CHANGELOG + ARCHITECTURE #24 (+ file tree, schema tables, db-version note) in this commit per versioning discipline. Agent-Session: engineer-prab-m3-20260612
121 lines
3.6 KiB
PHP
121 lines
3.6 KiB
PHP
<?php
|
|
/**
|
|
* Tests for the v0.20.0 board-card human_modified flag (CPO product AC:
|
|
* human-modified runs are visually distinct at the run-list level).
|
|
*
|
|
* Locks: ONE batched query flags all cards (no N+1), unflagged runs and
|
|
* missing-table sites degrade to false.
|
|
*
|
|
* @package PRAutoBlogger\Tests\Admin
|
|
*/
|
|
|
|
namespace PRAutoBlogger\Tests\Admin;
|
|
|
|
use PRAutoBlogger\Tests\BaseTestCase;
|
|
use Brain\Monkey\Functions;
|
|
|
|
class BoardHumanModifiedFlagTest extends BaseTestCase {
|
|
|
|
/**
|
|
* @var \PHPUnit\Framework\MockObject\MockObject Mock $wpdb.
|
|
*/
|
|
private $wpdb;
|
|
|
|
/**
|
|
* Number of get_col calls (N+1 guard).
|
|
*
|
|
* @var int
|
|
*/
|
|
private int $col_queries = 0;
|
|
|
|
protected function setUp(): void {
|
|
parent::setUp();
|
|
$this->col_queries = 0;
|
|
$this->wpdb = $this->create_mock_wpdb();
|
|
$GLOBALS['wpdb'] = $this->wpdb;
|
|
$this->wpdb->method( 'prepare' )->willReturnCallback(
|
|
static function ( $sql, ...$args ) {
|
|
if ( 1 === count( $args ) && is_array( $args[0] ) ) {
|
|
$args = $args[0];
|
|
}
|
|
return $sql . ' /* ' . implode( ',', array_map( 'strval', $args ) ) . ' */';
|
|
}
|
|
);
|
|
Functions\when( 'get_post_meta' )->alias(
|
|
static function ( $post_id, $key ) {
|
|
if ( '_prautoblogger_run_id' === $key ) {
|
|
return 'run-' . $post_id;
|
|
}
|
|
if ( '_prautoblogger_total_cost' === $key ) {
|
|
return '0.05';
|
|
}
|
|
return '';
|
|
}
|
|
);
|
|
Functions\when( 'get_edit_post_link' )->justReturn( 'https://example.com/edit' );
|
|
Functions\when( 'admin_url' )->alias( static fn( $p = '' ) => 'https://example.com/wp-admin/' . $p );
|
|
\PRAutoBlogger_Run_Stage_State::flush_cache();
|
|
}
|
|
|
|
protected function tearDown(): void {
|
|
\WP_Query::$_test_posts_queue = array();
|
|
\PRAutoBlogger_Run_Stage_State::flush_cache();
|
|
unset( $GLOBALS['wpdb'] );
|
|
parent::tearDown();
|
|
}
|
|
|
|
/** Build a WP_Post for the queue. */
|
|
private function make_post( int $id ): \WP_Post {
|
|
$post = new \WP_Post();
|
|
$post->ID = $id;
|
|
$post->post_title = 'Post ' . $id;
|
|
$post->post_date_gmt = '2026-06-12 00:00:00';
|
|
return $post;
|
|
}
|
|
|
|
/**
|
|
* Cards whose runs carry human_modified rows get the flag; others
|
|
* stay false; exactly ONE batched query runs for the whole column.
|
|
*/
|
|
public function test_cards_flagged_in_one_batched_query(): void {
|
|
\WP_Query::$_test_posts_queue[] = array( $this->make_post( 1 ), $this->make_post( 2 ), $this->make_post( 3 ) );
|
|
|
|
$this->wpdb->method( 'get_var' )->willReturn( 'wp_prautoblogger_run_stages' );
|
|
$this->wpdb->method( 'get_col' )->willReturnCallback(
|
|
function ( $sql ) {
|
|
++$this->col_queries;
|
|
$this->assertStringContainsString( 'human_modified = 1', (string) $sql );
|
|
return array( 'run-2' ); // Only post 2's run was edited.
|
|
}
|
|
);
|
|
|
|
$cards = ( new \PRAutoBlogger_Board_Data_Provider() )->get_in_review_cards();
|
|
|
|
$this->assertCount( 3, $cards );
|
|
$this->assertFalse( $cards[0]['human_modified'] );
|
|
$this->assertTrue( $cards[1]['human_modified'] );
|
|
$this->assertFalse( $cards[2]['human_modified'] );
|
|
$this->assertSame( 1, $this->col_queries, 'one batched query, never per-card' );
|
|
}
|
|
|
|
/**
|
|
* Missing substrate table: flags default false, zero flag queries.
|
|
*/
|
|
public function test_missing_table_degrades_to_false(): void {
|
|
\WP_Query::$_test_posts_queue[] = array( $this->make_post( 1 ) );
|
|
|
|
$this->wpdb->method( 'get_var' )->willReturn( null );
|
|
$this->wpdb->method( 'get_col' )->willReturnCallback(
|
|
function () {
|
|
++$this->col_queries;
|
|
return array();
|
|
}
|
|
);
|
|
|
|
$cards = ( new \PRAutoBlogger_Board_Data_Provider() )->get_in_review_cards();
|
|
|
|
$this->assertFalse( $cards[0]['human_modified'] );
|
|
$this->assertSame( 0, $this->col_queries );
|
|
}
|
|
}
|