- 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
104 lines
3.4 KiB
PHP
104 lines
3.4 KiB
PHP
<?php
|
|
/**
|
|
* Dossier_Actions: rerun + status endpoint tests (v0.20.0, M3).
|
|
*
|
|
* Locks the chained-cron contract (queue only — ZERO state mutations in
|
|
* the AJAX layer) and the read-only stage-status poll shape.
|
|
*
|
|
* @package PRAutoBlogger\Tests\Admin
|
|
*/
|
|
|
|
namespace PRAutoBlogger\Tests\Admin;
|
|
|
|
use Brain\Monkey\Functions;
|
|
|
|
class DossierActionsRerunTest extends DossierActionsTestCase {
|
|
|
|
/**
|
|
* Chained-cron contract: rerun_stage only schedules — no run/stage
|
|
* state is mutated by the AJAX layer, and the response says queued.
|
|
*/
|
|
public function test_rerun_stage_queues_without_any_state_mutation(): void {
|
|
$this->wire_eligible_context();
|
|
// A saved fork exists for the replay check.
|
|
$wpdb_get_row_fork = array(
|
|
'version' => '1',
|
|
'request_json' => '{"model":"m","messages":[{"role":"user","content":"edited"}]}',
|
|
);
|
|
// Rebuild get_row: forks resolve, run row terminal.
|
|
$_POST = array(
|
|
'post_id' => '99',
|
|
'stage' => 'draft',
|
|
'agent_role' => 'writer',
|
|
);
|
|
// Replace the get_row wiring (fork now exists).
|
|
$wpdb = $this->create_mock_wpdb();
|
|
$wpdb->options = 'wp_options';
|
|
$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 ) ) . ' */';
|
|
}
|
|
);
|
|
$wpdb->method( 'get_var' )->willReturnCallback(
|
|
static function ( $sql ) {
|
|
if ( false !== strpos( (string) $sql, 'SHOW TABLES' ) ) {
|
|
return ( false !== strpos( (string) $sql, 'stage_inputs' ) ) ? 'wp_prautoblogger_stage_inputs' : 'wp_prautoblogger_runs';
|
|
}
|
|
return null; // Lock free.
|
|
}
|
|
);
|
|
$wpdb->method( 'get_row' )->willReturnCallback(
|
|
static function ( $sql ) use ( $wpdb_get_row_fork ) {
|
|
if ( false !== strpos( (string) $sql, 'stage_inputs' ) ) {
|
|
return $wpdb_get_row_fork;
|
|
}
|
|
return array(
|
|
'run_id' => 'run-1',
|
|
'status' => 'done',
|
|
);
|
|
}
|
|
);
|
|
$wpdb->method( 'query' )->willReturnCallback(
|
|
function ( $sql ) {
|
|
$this->queries[] = (string) $sql;
|
|
return 1;
|
|
}
|
|
);
|
|
$GLOBALS['wpdb'] = $wpdb;
|
|
\PRAutoBlogger_Run_State::flush_cache();
|
|
\PRAutoBlogger_Stage_Input_Store::flush_cache();
|
|
|
|
$actions = new \PRAutoBlogger_Dossier_Actions();
|
|
$this->dispatch( array( $actions, 'on_rerun_stage' ) );
|
|
|
|
$this->assertTrue( $this->json['ok'] );
|
|
$this->assertTrue( $this->json['data']['queued'] );
|
|
$this->assertCount( 1, $this->scheduled );
|
|
$this->assertSame( \PRAutoBlogger_Rerun_Executor::REPLAY_ACTION, $this->scheduled[0]['hook'] );
|
|
$this->assertSame( array( 'run-1', 99, 'draft', 'writer', 'idea:abc123' ), array_slice( $this->scheduled[0]['args'], 0, 5 ) );
|
|
foreach ( $this->queries as $sql ) {
|
|
$this->assertStringNotContainsString( 'UPDATE', $sql, 'the AJAX layer must not mutate state' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* stage_status: read-only poll returns run status + per-stage state
|
|
* with stale/human_modified flags.
|
|
*/
|
|
public function test_stage_status_returns_stage_states(): void {
|
|
$this->wire_eligible_context();
|
|
$this->wpdb->method( 'get_results' )->willReturn( array() );
|
|
$_POST = array( 'post_id' => '99' );
|
|
|
|
$actions = new \PRAutoBlogger_Dossier_Actions();
|
|
$this->dispatch( array( $actions, 'on_stage_status' ) );
|
|
|
|
$this->assertTrue( $this->json['ok'] );
|
|
$this->assertArrayHasKey( 'run_status', $this->json['data'] );
|
|
$this->assertArrayHasKey( 'stages', $this->json['data'] );
|
|
}
|
|
}
|