feat: Pipeline Settings M3 — assembled-instructions preview + prompt version history/diff (#173, v0.25.0)
Read-only assembled-instructions preview (last-run rendered prompt) with Template-vs-Preview guardrail; per-prompt version history + LCS diff. Stage-list-driven. QA APPROVE df92ed3; CI green. Agent-Session: pipeline-settings-m3-merge
This commit is contained in:
131
tests/unit/Ajax/PipelineHistoryHandlerTest.php
Normal file
131
tests/unit/Ajax/PipelineHistoryHandlerTest.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
/**
|
||||
* Unit tests for PRAutoBlogger_Pipeline_History_Handler.
|
||||
*
|
||||
* Tests cover:
|
||||
* (1) compute_diff() LCS algorithm via reflection on the private static method.
|
||||
* (2) resolve_key_from_slug() round-trip through the Step_Map allowlist.
|
||||
*
|
||||
* NOTE: PHP is unavailable in the sandbox. Structure was self-verified
|
||||
* prior to push (see PR description for grep + brace-count proof).
|
||||
*
|
||||
* @see ajax/class-pipeline-history-handler.php
|
||||
* @see admin/class-pipeline-settings-step-map.php
|
||||
*
|
||||
* @package PRAutoBlogger\Tests\Ajax
|
||||
*/
|
||||
|
||||
namespace PRAutoBlogger\Tests\Ajax;
|
||||
|
||||
use PRAutoBlogger\Tests\BaseTestCase;
|
||||
use Brain\Monkey\Functions;
|
||||
|
||||
class PipelineHistoryHandlerTest extends BaseTestCase {
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
Functions\when( 'current_user_can' )->justReturn( true );
|
||||
Functions\when( 'wp_verify_nonce' )->justReturn( true );
|
||||
Functions\when( 'sanitize_key' )->alias(
|
||||
function ( string $key ): string {
|
||||
return strtolower( (string) preg_replace( '/[^a-z0-9_\-]/', '', $key ) );
|
||||
}
|
||||
);
|
||||
Functions\when( 'wp_unslash' )->alias(
|
||||
function ( $val ) {
|
||||
return $val;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* compute_diff detects added and removed lines.
|
||||
*/
|
||||
public function test_compute_diff_detects_changes(): void {
|
||||
$old = "Line A\nLine B\nLine C";
|
||||
$new = "Line A\nLine X\nLine C";
|
||||
|
||||
$method = new \ReflectionMethod( 'PRAutoBlogger_Pipeline_History_Handler', 'compute_diff' );
|
||||
$method->setAccessible( true );
|
||||
$lines = $method->invoke( null, $old, $new );
|
||||
|
||||
$types = array_column( $lines, 'type' );
|
||||
|
||||
$this->assertContains( 'removed', $types, 'Expected a removed line' );
|
||||
$this->assertContains( 'added', $types, 'Expected an added line' );
|
||||
$this->assertContains( 'context', $types, 'Expected context lines' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Identical texts produce only context lines.
|
||||
*/
|
||||
public function test_compute_diff_identical_texts_no_changes(): void {
|
||||
$body = "Same line\nAnother same line";
|
||||
|
||||
$method = new \ReflectionMethod( 'PRAutoBlogger_Pipeline_History_Handler', 'compute_diff' );
|
||||
$method->setAccessible( true );
|
||||
$lines = $method->invoke( null, $body, $body );
|
||||
|
||||
foreach ( $lines as $line ) {
|
||||
$this->assertNotEquals( 'added', $line['type'], 'No added lines for identical input' );
|
||||
$this->assertNotEquals( 'removed', $line['type'], 'No removed lines for identical input' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Each diff line has the required type and text keys.
|
||||
*/
|
||||
public function test_compute_diff_line_shape(): void {
|
||||
$old = "alpha\nbeta";
|
||||
$new = "alpha\ngamma";
|
||||
|
||||
$method = new \ReflectionMethod( 'PRAutoBlogger_Pipeline_History_Handler', 'compute_diff' );
|
||||
$method->setAccessible( true );
|
||||
$lines = $method->invoke( null, $old, $new );
|
||||
|
||||
foreach ( $lines as $line ) {
|
||||
$this->assertArrayHasKey( 'type', $line );
|
||||
$this->assertArrayHasKey( 'text', $line );
|
||||
$this->assertIsString( $line['type'] );
|
||||
$this->assertIsString( $line['text'] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* resolve_key_from_slug returns canonical key for a known slug.
|
||||
*/
|
||||
public function test_resolve_key_from_slug_returns_canonical_key(): void {
|
||||
$method = new \ReflectionMethod( 'PRAutoBlogger_Pipeline_History_Handler', 'resolve_key_from_slug' );
|
||||
$method->setAccessible( true );
|
||||
|
||||
$result = $method->invoke( null, 'research-system' );
|
||||
$this->assertEquals( 'research.system', $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* resolve_key_from_slug returns null for an unknown slug.
|
||||
*/
|
||||
public function test_resolve_key_from_slug_returns_null_for_unknown(): void {
|
||||
$method = new \ReflectionMethod( 'PRAutoBlogger_Pipeline_History_Handler', 'resolve_key_from_slug' );
|
||||
$method->setAccessible( true );
|
||||
|
||||
$result = $method->invoke( null, 'not-a-real-key-xyz' );
|
||||
$this->assertNull( $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* Far-away context lines are collapsed with an omitted marker.
|
||||
*/
|
||||
public function test_compute_diff_omits_far_context(): void {
|
||||
$old_lines = array( 'changed', 'ctx1', 'ctx2', 'ctx3', 'ctx4', 'ctx5', 'ctx6', 'ctx7', 'ctx8', 'end' );
|
||||
$new_lines = array( 'CHANGED', 'ctx1', 'ctx2', 'ctx3', 'ctx4', 'ctx5', 'ctx6', 'ctx7', 'ctx8', 'end' );
|
||||
|
||||
$method = new \ReflectionMethod( 'PRAutoBlogger_Pipeline_History_Handler', 'compute_diff' );
|
||||
$method->setAccessible( true );
|
||||
$lines = $method->invoke( null, implode( "\n", $old_lines ), implode( "\n", $new_lines ) );
|
||||
|
||||
$types = array_column( $lines, 'type' );
|
||||
$this->assertContains( 'omitted', $types, 'Far-away context lines should be omitted' );
|
||||
}
|
||||
}
|
||||
123
tests/unit/Ajax/PipelinePreviewSourceTest.php
Normal file
123
tests/unit/Ajax/PipelinePreviewSourceTest.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
/**
|
||||
* Unit tests for PRAutoBlogger_Pipeline_Preview_Source.
|
||||
*
|
||||
* Tests cover pure static helper methods via reflection:
|
||||
* (1) extract_rendered_from_messages() role-preference logic.
|
||||
* (2) stage_for_key() mapping from key to stage slug.
|
||||
*
|
||||
* sample_render() and last_run() require WP DB integration so are
|
||||
* deferred to the functional smoke test suite on prod.
|
||||
*
|
||||
* NOTE: PHP is unavailable in the sandbox. Structure self-verified
|
||||
* prior to push (grep + brace-count proof in PR description).
|
||||
*
|
||||
* @see admin/class-pipeline-preview-source.php
|
||||
* @see core/class-stage-display-map.php
|
||||
*
|
||||
* @package PRAutoBlogger\Tests\Ajax
|
||||
*/
|
||||
|
||||
namespace PRAutoBlogger\Tests\Ajax;
|
||||
|
||||
use PRAutoBlogger\Tests\BaseTestCase;
|
||||
|
||||
class PipelinePreviewSourceTest extends BaseTestCase {
|
||||
|
||||
/**
|
||||
* extract_rendered_from_messages prefers system role for *.system keys.
|
||||
*/
|
||||
public function test_extract_prefers_system_role_for_system_keys(): void {
|
||||
$messages = array(
|
||||
array( 'role' => 'system', 'content' => 'System prompt text here' ),
|
||||
array( 'role' => 'user', 'content' => 'User message here' ),
|
||||
);
|
||||
|
||||
$method = new \ReflectionMethod( 'PRAutoBlogger_Pipeline_Preview_Source', 'extract_rendered_from_messages' );
|
||||
$method->setAccessible( true );
|
||||
|
||||
$result = $method->invoke( null, $messages, 'analysis.system' );
|
||||
$this->assertEquals( 'System prompt text here', $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* extract_rendered_from_messages prefers user role for agent keys.
|
||||
*/
|
||||
public function test_extract_prefers_user_role_for_agent_keys(): void {
|
||||
$messages = array(
|
||||
array( 'role' => 'system', 'content' => 'System prompt text here' ),
|
||||
array( 'role' => 'user', 'content' => 'User message here' ),
|
||||
);
|
||||
|
||||
$method = new \ReflectionMethod( 'PRAutoBlogger_Pipeline_Preview_Source', 'extract_rendered_from_messages' );
|
||||
$method->setAccessible( true );
|
||||
|
||||
$result = $method->invoke( null, $messages, 'analysis.user' );
|
||||
$this->assertEquals( 'User message here', $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* extract_rendered_from_messages falls back to longest message when no role match.
|
||||
*/
|
||||
public function test_extract_fallback_longest_when_no_role_match(): void {
|
||||
$messages = array(
|
||||
array( 'role' => 'assistant', 'content' => 'Short' ),
|
||||
array( 'role' => 'assistant', 'content' => 'This is the longest message in the array by far' ),
|
||||
);
|
||||
|
||||
$method = new \ReflectionMethod( 'PRAutoBlogger_Pipeline_Preview_Source', 'extract_rendered_from_messages' );
|
||||
$method->setAccessible( true );
|
||||
|
||||
$result = $method->invoke( null, $messages, 'content.draft' );
|
||||
$this->assertEquals( 'This is the longest message in the array by far', $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* extract returns empty string when messages array is empty.
|
||||
*/
|
||||
public function test_extract_empty_messages_returns_empty(): void {
|
||||
$method = new \ReflectionMethod( 'PRAutoBlogger_Pipeline_Preview_Source', 'extract_rendered_from_messages' );
|
||||
$method->setAccessible( true );
|
||||
|
||||
$result = $method->invoke( null, array(), 'analysis.system' );
|
||||
$this->assertSame( '', $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* stage_for_key maps 'analysis.system' to 'analysis' stage.
|
||||
*/
|
||||
public function test_stage_for_key_maps_analysis_system(): void {
|
||||
$method = new \ReflectionMethod( 'PRAutoBlogger_Pipeline_Preview_Source', 'stage_for_key' );
|
||||
$method->setAccessible( true );
|
||||
|
||||
$result = $method->invoke( null, 'analysis.system' );
|
||||
$this->assertEquals( 'analysis', $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* stage_for_key maps 'research.system' to 'llm_research' stage.
|
||||
*
|
||||
* Stage_Display_Map::MAP lists 'llm_research' before the Phase 2b 'research'
|
||||
* entry; both share prompt_key => 'research.system' so the first-match-wins
|
||||
* foreach returns 'llm_research'. This is correct: current prod runs write
|
||||
* 'llm_research' to prab_generation_log.stage (LLM_Research_Provider line 74).
|
||||
*/
|
||||
public function test_stage_for_key_maps_research_system(): void {
|
||||
$method = new \ReflectionMethod( 'PRAutoBlogger_Pipeline_Preview_Source', 'stage_for_key' );
|
||||
$method->setAccessible( true );
|
||||
|
||||
$result = $method->invoke( null, 'research.system' );
|
||||
$this->assertEquals( 'llm_research', $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* stage_for_key returns null for an unmapped key.
|
||||
*/
|
||||
public function test_stage_for_key_returns_null_for_unknown(): void {
|
||||
$method = new \ReflectionMethod( 'PRAutoBlogger_Pipeline_Preview_Source', 'stage_for_key' );
|
||||
$method->setAccessible( true );
|
||||
|
||||
$result = $method->invoke( null, 'not.a.real.key' );
|
||||
$this->assertNull( $result );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user