test: Test-Suite für Single-Server-Konfig, Loop-Logik und Skripte

Node-eigener Test-Runner (keine Dependencies). 35 Tests:
- pure-logic: parseVerdict/parseBlockers/normalizeForComparison (real aus
  der Extension extrahiert), inkl. PASS-WITH-CONCERNS-Abgrenzung
- config: models.json/settings.json-Invarianten (kein Port 8002, nur
  llama-cpp-single/qwen3.5-single), Querkonsistenz
- extension-guards: Regression-Schutz fuer jeden Fix (select positional,
  kein switchModel, before_agent_start-Hook, passed-Logik, Loop-Fallback)
- scripts: bash -n + Single-Server-Invarianten

Start: ./run-tests.sh

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-15 02:58:21 +02:00
commit ba733fe684
6 changed files with 266 additions and 0 deletions

34
tests/helpers.mjs Normal file
View file

@ -0,0 +1,34 @@
// Gemeinsame Test-Helfer.
//
// Wichtig: Die pi-Extension läuft nur im pi-Runtime (importiert @earendil-works/pi-coding-agent
// und typebox). Wir importieren sie daher NICHT direkt, sondern extrahieren die reinen,
// abhängigkeitsfreien Funktionen zur Testzeit aus der Quelle. So testen wir das Original
// (keine divergierende Kopie), ohne die laufende Extension umbauen oder das Runtime laden zu müssen.
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
export const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
export const extSource = readFileSync(join(repoRoot, "pi-coder-judge-extension.ts"), "utf8");
// Extrahiert eine Top-Level-Funktion (Einrückungstiefe 0) per Name aus der Extension-Quelle und
// gibt sie als ausführbare Funktion zurück. Nur für reine Funktionen ohne externe Bezüge geeignet.
// Die schließende "}" der Funktion steht auf Spalte 0; innere Blöcke sind eingerückt — deshalb
// matcht /^}/m zuverlässig das Funktionsende. Einfache TS-Annotationen (: string / : string[])
// werden vor der Auswertung entfernt.
export function extractFn(name) {
const re = new RegExp(`^function ${name}\\b[\\s\\S]*?^}`, "m");
const m = extSource.match(re);
if (!m) throw new Error(`Funktion '${name}' nicht in der Extension gefunden`);
const js = m[0].replace(/:\s*string(\[\])?/g, "");
return new Function(`${js}\nreturn ${name};`)();
}
export function readRepoFile(relPath) {
return readFileSync(join(repoRoot, relPath), "utf8");
}
export function readJson(relPath) {
return JSON.parse(readRepoFile(relPath));
}