query(). * * @var string[] */ private array $queries = array(); protected function setUp(): void { parent::setUp(); $this->queries = array(); $this->wpdb = $this->create_mock_wpdb(); $GLOBALS['wpdb'] = $this->wpdb; $this->wpdb->method( 'get_var' )->willReturn( 'wp_prautoblogger_runs' ); $this->wpdb->method( 'prepare' )->willReturnCallback( static function ( $sql, ...$args ) { return $sql . ' /* ' . implode( ',', array_map( 'strval', $args ) ) . ' */'; } ); $this->stub_get_option( array( 'prautoblogger_per_run_cost_ceiling_usd' => '0.75' ) ); \PRAutoBlogger_Run_State::flush_cache(); } protected function tearDown(): void { \PRAutoBlogger_Run_State::flush_cache(); unset( $GLOBALS['wpdb'] ); parent::tearDown(); } /** * reopen() transitions ONLY terminal runs (atomic conditional UPDATE) * and re-snapshots the ceiling from the current setting. */ public function test_reopen_gates_on_terminal_status_and_resnapshots_ceiling(): void { $this->wpdb->method( 'query' )->willReturnCallback( function ( $sql ) { $this->queries[] = (string) $sql; return 1; } ); $this->assertTrue( \PRAutoBlogger_Run_State::reopen( 'run-1' ) ); $sql = $this->queries[0]; $this->assertStringContainsString( "SET status = 'running'", $sql ); $this->assertStringContainsString( "status IN ('done','failed','halted')", $sql ); $this->assertStringContainsString( 'ceiling_usd = %f', $sql ); $this->assertStringContainsString( '0.75', $sql ); // Current setting value. $this->assertStringContainsString( 'finished_at = NULL', $sql ); // Guardrail 4: accumulated spend is NEVER reset by a reopen. $this->assertStringNotContainsString( 'reserved_usd', $sql ); $this->assertStringNotContainsString( 'settled_usd', $sql ); $this->assertStringNotContainsString( 'overage_usd', $sql ); } /** * No row matched (run is pending/running or absent) -> false, so the * executor aborts instead of replaying against a closed gate. */ public function test_reopen_returns_false_when_not_terminal(): void { $this->wpdb->method( 'query' )->willReturn( 0 ); $this->assertFalse( \PRAutoBlogger_Run_State::reopen( 'run-1' ) ); } /** * Self-healing: missing table / empty run id -> false, no query. */ public function test_reopen_noop_paths(): void { $this->wpdb->method( 'query' )->willReturnCallback( function ( $sql ) { $this->queries[] = (string) $sql; return 1; } ); $this->assertFalse( \PRAutoBlogger_Run_State::reopen( '' ) ); $this->assertCount( 0, $this->queries ); } }