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

66
tests/pure-logic.test.mjs Normal file
View file

@ -0,0 +1,66 @@
// Testet die reine Urteils-/Blocker-Parselogik des /optimize-Loops.
// Diese Funktionen entscheiden, ob der Coder einen Fix bekommt — Regressionen hier sind kritisch.
import { test } from "node:test";
import assert from "node:assert/strict";
import { extractFn } from "./helpers.mjs";
const parseVerdict = extractFn("parseVerdict");
const parseBlockers = extractFn("parseBlockers");
const normalizeForComparison = extractFn("normalizeForComparison");
test("parseVerdict: klares PASS", () => {
assert.equal(parseVerdict("Review fertig.\nUrteil: PASS\nKeine Blocker."), "PASS");
});
test("parseVerdict: FAIL", () => {
assert.equal(parseVerdict("Urteil: FAIL"), "FAIL");
});
test("parseVerdict: PASS WITH CONCERNS wird NICHT als PASS verkürzt (kritisch für Fix-Auslösung)", () => {
// Würde die Regex hier 'PASS' zuerst matchen, käme kein Coder-Fix zustande.
assert.equal(parseVerdict("Urteil: PASS WITH CONCERNS"), "PASS WITH CONCERNS");
});
test("parseVerdict: case-insensitiv und mit Bullet-Präfix", () => {
assert.equal(parseVerdict("- urteil: pass with concerns"), "PASS WITH CONCERNS");
});
test("parseVerdict: ohne erkennbares Urteil → UNREADABLE", () => {
assert.equal(parseVerdict("Sieht solide aus, keine Anmerkungen."), "UNREADABLE");
});
test("parseBlockers: extrahiert Bullet-Blocker und stoppt vor Major", () => {
const txt = "- Blocker: B1 Argument-Handling unklar\n- Major: M1 chmod fehlt";
const blockers = parseBlockers(txt);
assert.match(blockers, /B1 Argument-Handling/);
assert.doesNotMatch(blockers, /M1/);
});
test("parseBlockers: erkennt **Blocker**-Überschrift und stoppt vor Minor", () => {
const txt = "**Blocker**\nKaputter Import in main.py\n**Minor**\nTippfehler";
const blockers = parseBlockers(txt);
assert.match(blockers, /Kaputter Import/);
assert.doesNotMatch(blockers, /Tippfehler/);
});
test("parseBlockers: leer, wenn kein Blocker-Abschnitt vorhanden", () => {
assert.equal(parseBlockers("Urteil: PASS\nAlles in Ordnung."), "");
});
test("normalizeForComparison: Whitespace-Kollaps, Kleinschreibung, trailing-Satzzeichen", () => {
assert.equal(
normalizeForComparison(" Blocker A. "),
normalizeForComparison("blocker a")
);
});
test("normalizeForComparison: gleiche Blocker trotz Formatierungsunterschied → Loop-Erkennung greift", () => {
const a = "- Race Condition in waitForIdle!";
const b = "Race condition in waitForIdle";
assert.notEqual(normalizeForComparison(a), ""); // sanity
assert.equal(
normalizeForComparison(a.replace(/^- /, "")),
normalizeForComparison(b)
);
});