v0.3.2 — adopt estate CI: PHPUnit suite + reusable pipeline + governance workflows

- Convert 17 hand-rolled tests to PHPUnit TestCases (tests: stubs pattern)
  P0 security tests preserved: key-leak/encryption (KeyStoreTest),
  capture-allowlist/executor-path sentinel (CaptureWriterTest)
- Add phpunit.xml.dist + update tests/bootstrap.php (Composer autoloader first)
- Add phpunit/phpunit ^9.6 + yoast/phpunit-polyfills ^2.0 to require-dev
- composer test -> vendor/bin/phpunit --configuration phpunit.xml.dist
- ci.yml: thin caller of peptide-e2e reusable CI (tests: stubs, has_js: true,
  workflow_call trigger, permissions: contents: write)
- deploy.yml: estate pattern caller of peptide-e2e deploy-app.yml@main
  (target_path: wp-content/plugins/pr-vision, gated on reusable CI)
  Pull-deploy on Hostinger stays as break-glass
- Add cto-review.yml (Haiku-only, A/B panel dropped 2026-06-12)
- Add main-push-audit.yml (direct-push tripwire)

Agent-Session: claude-adopt-ci-20260616
This commit is contained in:
peptiderepo
2026-06-16 15:25:22 +00:00
parent 1d8f024380
commit 01a9356c16
26 changed files with 2459 additions and 182 deletions

View File

@@ -1,15 +1,20 @@
<?php
/**
* Minimal test bootstrap -- stubs all WordPress functions used by the plugin
* so classes can be exercised in plain PHP without a WP install.
* PHPUnit bootstrap for PR Vision tests.
*
* Pattern mirrors peptide-repo-core's bootstrap (flat PHP, no PHPUnit).
* Loads WP stubs in plain PHP (no WP install required) and registers
* the plugin autoloader so all classes are available to PHPUnit test cases.
*
* Pattern: WP stubs (tests: stubs) — no DB, no service, fast.
*
* @package PrVision
*/
declare(strict_types=1);
// Composer autoloader must come first so PHPUnit classes are available.
require_once dirname( __DIR__ ) . '/vendor/autoload.php';
/* ── Constants ────────────────────────────────────────────────────────── */
if ( ! defined( 'ABSPATH' ) ) {
@@ -27,7 +32,7 @@ if ( ! defined( 'ARRAY_N' ) ) {
define( 'ARRAY_N', 'ARRAY_N' );
}
define( 'PRV_VERSION', '0.3.1' );
define( 'PRV_VERSION', '0.3.2' );
define( 'PRV_PLUGIN_FILE', __DIR__ . '/../pr-vision.php' );
define( 'PRV_PLUGIN_DIR', realpath( __DIR__ . '/..' ) . '/' );
define( 'PRV_PLUGIN_URL', 'http://example.test/wp-content/plugins/pr-vision/' );
@@ -270,47 +275,6 @@ $wpdb = new stdClass_wpdb();
function dbDelta( string $sql ): array { return []; }
/* ── Test assertion helpers ────────────────────────────────────────────── */
$GLOBALS['prv_test_report'] = [ 'pass' => 0, 'fail' => 0, 'failures' => [] ];
function prv_assert( bool $condition, string $label ): void {
if ( $condition ) {
$GLOBALS['prv_test_report']['pass']++;
echo " PASS: {$label}\n";
} else {
$GLOBALS['prv_test_report']['fail']++;
$GLOBALS['prv_test_report']['failures'][] = $label;
echo " FAIL: {$label}\n";
}
}
function prv_assert_equals( $expected, $actual, string $label ): void {
$pass = ( $expected === $actual );
prv_assert( $pass, $label . ( $pass ? '' : ' -- expected ' . var_export( $expected, true ) . ', got ' . var_export( $actual, true ) ) );
}
function prv_assert_throws( callable $fn, string $exception_class, string $label ): void {
try {
$fn();
prv_assert( false, $label . ' -- expected ' . $exception_class . ' but no exception thrown' );
} catch ( \Throwable $e ) {
prv_assert( $e instanceof $exception_class, $label . ' -- got ' . get_class( $e ) );
}
}
function prv_test_summary(): int {
$r = $GLOBALS['prv_test_report'];
echo "\n---\n";
echo "Totals: {$r['pass']} passed, {$r['fail']} failed\n";
if ( $r['fail'] > 0 ) {
echo "Failures:\n";
foreach ( $r['failures'] as $f ) { echo " - {$f}\n"; }
return 1;
}
return 0;
}
/* ── Load autoloader + all plugin classes ──────────────────────────────── */
require_once __DIR__ . '/../includes/core/class-prv-autoloader.php';