Files
prautoblogger/tests/unit/Core/AnalysisPromptsTest.php
peptiderepo 50369bb644 fix(v0183): GA4 SQL fix + manual-run resilience + honest status
R1: move phpcs:ignore out of SQL string in class-analysis-prompts.php:81
    (same pattern as v0.18.2 cost-reporter fix). GA4 self-improvement loop
    has returned empty context on every pipeline run since v0.16.0 — this
    restores get_performance_context() to the LLM. Sibling occurrence also
    fixed in class-post-assembler.php:112.

R2(a): update_generation_stage() now renews STATUS_TTL on every stage call
    so the transient outlasts long LLM calls during background runs.

R2(b): on_ajax_generation_status() detects lock-held-but-transient-gone
    when lock age > STATUS_TTL (= Hostinger background-process kill).
    Marks stuck run failed, releases lock, returns status:error with
    'infrastructure timeout' message. Daily reaper remains the backstop.

R3: when transient absent but lock held within TTL, return status:running
    (with started_at from lock) instead of status:idle.

Executor split: class-executor.php was 351 lines (>300). AJAX generation
    handlers extracted to new class-generation-status-poller.php (260 lines);
    executor now 226 lines. Public hook wiring unchanged via delegation.
    Generation_Lock gains get_acquired_at() for R2b/R3 lock-age checks.

Tests: AnalysisPromptsTest (regression SQL + behavioral context shaping),
       GenerationStatusPollerTest (R2a TTL renewal, R2b abort, R3 running,
       idle path).

Closes: R1 (analysis-prompts SQL), R2 (manual-run resilience), R3 (honest status)
Version: 0.18.3
Agent-Session: prab-v0183-20260612
2026-06-12 03:15:14 +00:00

125 lines
3.9 KiB
PHP

<?php
/**
* Tests for PRAutoBlogger_Analysis_Prompts.
*
* Regression + behavioral coverage for the GA4 self-improvement loop:
* - SQL passed to $wpdb->get_results() must not embed phpcs:ignore comments
* (MariaDB rejects the query if it does).
* - get_performance_context() returns a non-empty, correctly-shaped string
* when seeded content_scores rows are present.
*
* @package PRAutoBlogger\Tests\Core
*/
namespace PRAutoBlogger\Tests\Core;
use PRAutoBlogger\Tests\BaseTestCase;
use Brain\Monkey\Functions;
class AnalysisPromptsTest extends BaseTestCase {
/** @var \PHPUnit\Framework\MockObject\MockObject Mock $wpdb. */
private $wpdb;
protected function setUp(): void {
parent::setUp();
$this->wpdb = $this->create_mock_wpdb();
$GLOBALS['wpdb'] = $this->wpdb;
}
protected function tearDown(): void {
unset( $GLOBALS['wpdb'] );
parent::tearDown();
}
/**
* Regression: get_performance_context() SQL must not embed phpcs:ignore
* inside the string literal — MariaDB rejects the query if it does,
* causing get_performance_context() to silently return '' on every run.
*
* @see includes/core/class-analysis-prompts.php — fixed in v0.18.3
*/
public function test_get_performance_context_sql_has_no_inline_phpcs_ignore(): void {
$captured_sql = null;
$this->wpdb->method( 'prepare' )->willReturnCallback(
function ( $sql ) use ( &$captured_sql ) {
// get_results is called WITHOUT prepare (direct string interpolation
// of a table prefix); capture the first arg to get_results instead.
return $sql;
}
);
$this->wpdb->method( 'get_results' )->willReturnCallback(
function ( $sql ) use ( &$captured_sql ) {
$captured_sql = $sql;
return array();
}
);
\PRAutoBlogger_Analysis_Prompts::get_performance_context();
$this->assertNotNull( $captured_sql, 'get_results() was not called' );
$this->assertStringNotContainsString(
'// phpcs:ignore',
(string) $captured_sql,
'SQL passed to get_results() must not contain a phpcs:ignore comment — MariaDB rejects it as a syntax error'
);
}
/**
* Behavioral: get_performance_context() returns a non-empty, correctly-shaped
* string when the scores table contains rows with composite_score > 0.
*
* The returned string must:
* - start with the "Top performing" header line
* - contain at least one bullet entry of the form '- "…" (score: N.N)'
* - not be empty
*/
public function test_get_performance_context_returns_shaped_context_with_seeded_rows(): void {
// Simulate two content_scores rows returned from the DB query.
$rows = array(
array(
'post_id' => 100,
'composite_score' => '9.2',
'post_title' => 'BPC-157 and Gut Healing — What the Research Shows',
),
array(
'post_id' => 101,
'composite_score' => '7.8',
'post_title' => 'Semaglutide Dosing Guide for Weight Loss',
),
);
$this->wpdb->method( 'get_results' )->willReturn( $rows );
$context = \PRAutoBlogger_Analysis_Prompts::get_performance_context();
$this->assertNotEmpty( $context, 'get_performance_context() returned empty string with seeded rows' );
$this->assertStringContainsString(
'Top performing past articles',
$context,
'Context block must start with the header line'
);
$this->assertStringContainsString(
'BPC-157 and Gut Healing',
$context,
'Context block must contain the first seeded article title'
);
$this->assertStringContainsString(
'score: 9.2',
$context,
'Context block must include formatted composite_score'
);
}
/**
* Edge case: get_performance_context() returns '' when no rows exist.
*/
public function test_get_performance_context_returns_empty_string_when_no_rows(): void {
$this->wpdb->method( 'get_results' )->willReturn( array() );
$context = \PRAutoBlogger_Analysis_Prompts::get_performance_context();
$this->assertSame( '', $context, 'get_performance_context() must return empty string when scores table is empty' );
}
}