516 lines
20 KiB
TypeScript
516 lines
20 KiB
TypeScript
// Unit-Tests für reine Hilfsfunktionen aus pi-coder-judge-extension.ts
|
||
//
|
||
// Ausführung (TypeScript):
|
||
// npx ts-node test-utils.ts
|
||
//
|
||
// Ausführung ohne ts-node (schneller):
|
||
// node --input-type=module < <(sed 's/: string//g; s/: unknown//g; s/: void//g; s/: boolean//g' test-utils.ts)
|
||
//
|
||
// Oder: Funktionen aus dieser Datei kopieren und als .js ausführen.
|
||
|
||
// ── Funktionen (aus Extension kopiert, kein pi-API-Import nötig) ─────────────
|
||
|
||
function normalizeForComparison(s: string): string {
|
||
return s.trim().replace(/\s+/g, " ").replace(/[.,;:!?]+$/g, "").toLowerCase();
|
||
}
|
||
|
||
function parseVerdict(text: string): string {
|
||
const m = text.match(/Urteil:\s*(PASS WITH CONCERNS|PASS|FAIL)/i);
|
||
return m ? m[1].toUpperCase() : "UNREADABLE";
|
||
}
|
||
|
||
function parseBlockers(text: string): string {
|
||
const m = text.match(
|
||
/(?:\*\*Blocker\*\*|##\s*Blocker|[-–*]\s*Blocker)[:\n]([\s\S]*?)(?:\n(?:\*\*Major\*\*|##\s*Major|[-–*]\s*Major)|\n(?:\*\*Minor\*\*|##\s*Minor|[-–*]\s*Minor)|$)/i
|
||
);
|
||
return m ? m[1].trim() : "";
|
||
}
|
||
|
||
// ── Test-Harness ──────────────────────────────────────────────────────────────
|
||
|
||
let passed = 0;
|
||
let failed = 0;
|
||
|
||
function expect(actual: unknown, expected: unknown, label: string): void {
|
||
if (actual === expected) {
|
||
console.log(` ✅ ${label}`);
|
||
passed++;
|
||
} else {
|
||
console.error(` ❌ ${label}`);
|
||
console.error(` erwartet: ${JSON.stringify(expected)}`);
|
||
console.error(` erhalten: ${JSON.stringify(actual)}`);
|
||
failed++;
|
||
}
|
||
}
|
||
|
||
// ── normalizeForComparison ────────────────────────────────────────────────────
|
||
|
||
console.log("\nnormalizeForComparison()");
|
||
|
||
expect(normalizeForComparison(" Foo Bar "), "foo bar",
|
||
"trimmt führende/nachfolgende Leerzeichen");
|
||
|
||
expect(normalizeForComparison("Foo.\n"), "foo",
|
||
"entfernt trailing Punkt + Newline");
|
||
|
||
expect(normalizeForComparison("A B"), "a b",
|
||
"kollabiert mehrfache Leerzeichen");
|
||
|
||
expect(normalizeForComparison("Foo:"), "foo",
|
||
"entfernt trailing Doppelpunkt");
|
||
|
||
expect(normalizeForComparison("Foo;"), "foo",
|
||
"entfernt trailing Semikolon");
|
||
|
||
expect(normalizeForComparison("Foo!"), "foo",
|
||
"entfernt trailing Ausrufezeichen");
|
||
|
||
expect(normalizeForComparison("Foo?"), "foo",
|
||
"entfernt trailing Fragezeichen");
|
||
|
||
expect(normalizeForComparison("UPPER CASE"), "upper case",
|
||
"konvertiert zu Kleinbuchstaben");
|
||
|
||
// Loop-Detection: gleiche Blocker nach Normalisierung erkannt
|
||
expect(
|
||
normalizeForComparison("missing error handling.") ===
|
||
normalizeForComparison("missing error handling"),
|
||
true,
|
||
"Loop-Detection: trailing Punkt macht keinen Unterschied"
|
||
);
|
||
|
||
expect(
|
||
normalizeForComparison("null check missing\n") ===
|
||
normalizeForComparison("null check missing"),
|
||
true,
|
||
"Loop-Detection: Newline am Ende macht keinen Unterschied"
|
||
);
|
||
|
||
expect(
|
||
normalizeForComparison("Fehler bei Import.") ===
|
||
normalizeForComparison("Fehler bei Import"),
|
||
true,
|
||
"Loop-Detection: mehrfache Leerzeichen + Punkt machen keinen Unterschied"
|
||
);
|
||
|
||
expect(
|
||
normalizeForComparison("Blocker A") === normalizeForComparison("Blocker B"),
|
||
false,
|
||
"Loop-Detection: verschiedene Blocker werden NICHT als gleich erkannt"
|
||
);
|
||
|
||
// ── parseVerdict ──────────────────────────────────────────────────────────────
|
||
|
||
console.log("\nparseVerdict()");
|
||
|
||
expect(parseVerdict("Urteil: PASS"), "PASS",
|
||
"erkennt PASS");
|
||
|
||
expect(parseVerdict("Urteil: PASS WITH CONCERNS"), "PASS WITH CONCERNS",
|
||
"erkennt PASS WITH CONCERNS (vor PASS gematcht)");
|
||
|
||
expect(parseVerdict("Urteil: FAIL"), "FAIL",
|
||
"erkennt FAIL");
|
||
|
||
expect(parseVerdict("kein Urteil hier"), "UNREADABLE",
|
||
"gibt UNREADABLE zurück wenn kein Urteil");
|
||
|
||
expect(parseVerdict("urteil: pass"), "PASS",
|
||
"case-insensitiv: 'urteil: pass'");
|
||
|
||
expect(parseVerdict("urteil: Pass With Concerns"), "PASS WITH CONCERNS",
|
||
"case-insensitiv: gemischte Groß-/Kleinschreibung");
|
||
|
||
expect(parseVerdict("Das ist mein Urteil: PASS — und mehr Text dahinter"), "PASS",
|
||
"ignoriert Text nach dem Urteil");
|
||
|
||
expect(parseVerdict("Urteil:PASS"), "PASS",
|
||
"toleriert fehlenden Leerzeichen nach Doppelpunkt");
|
||
|
||
expect(parseVerdict(""), "UNREADABLE",
|
||
"leerer String → UNREADABLE");
|
||
|
||
// ── parseBlockers ─────────────────────────────────────────────────────────────
|
||
|
||
console.log("\nparseBlockers()");
|
||
|
||
expect(
|
||
parseBlockers("**Blocker**:\n- fehlende Validierung\n**Major**:\n- anderes Problem"),
|
||
"- fehlende Validierung",
|
||
"erkennt **Blocker** mit Bold-Syntax"
|
||
);
|
||
|
||
expect(
|
||
parseBlockers("## Blocker\nNull-Check fehlt\n## Major\nanderes"),
|
||
"Null-Check fehlt",
|
||
"erkennt ## Blocker mit Heading-Syntax"
|
||
);
|
||
|
||
expect(
|
||
parseBlockers("- Blocker:\n- fehlender Import\n- Minor:\n- Stil"),
|
||
"- fehlender Import",
|
||
"erkennt - Blocker mit Bullet-Syntax"
|
||
);
|
||
|
||
expect(
|
||
parseBlockers("– Blocker\nKein Logging\n- Minor\nKleinigkeit"),
|
||
"Kein Logging",
|
||
"erkennt – Blocker (Gedankenstrich)"
|
||
);
|
||
|
||
expect(
|
||
parseBlockers("Urteil: PASS\n\nAlles ok."),
|
||
"",
|
||
"gibt leeren String zurück wenn kein Blocker-Abschnitt"
|
||
);
|
||
|
||
expect(
|
||
parseBlockers("**Blocker**:\nkeine\n**Minor**:\n- Stil"),
|
||
"keine",
|
||
"extrahiert 'keine' als Blocker-Text"
|
||
);
|
||
|
||
// Mehrzeiliger Blocker
|
||
const multilineInput = `**Blocker**:
|
||
- Import fehlt
|
||
- Funktion nicht definiert
|
||
**Major**:
|
||
- weitere Sache`;
|
||
const multilineResult = parseBlockers(multilineInput);
|
||
expect(
|
||
multilineResult.includes("Import fehlt") && multilineResult.includes("Funktion nicht definiert"),
|
||
true,
|
||
"extrahiert mehrzeiligen Blocker vollständig"
|
||
);
|
||
|
||
// ── toolExecutionLabel ────────────────────────────────────────────────────────
|
||
|
||
function toolExecutionLabel(toolName, args) {
|
||
switch (toolName) {
|
||
case "edit": return `Editiere ${args.path ?? "Datei"}…`;
|
||
case "write": return `Schreibe ${args.path ?? "Datei"} neu…`;
|
||
case "read": return `Lese ${args.path ?? "Datei"}…`;
|
||
case "grep": return `Suche in ${args.path ?? args.pattern ?? "Dateien"}…`;
|
||
case "find": return `Suche Dateien: ${args.pattern ?? ""}…`;
|
||
case "ls": return `Verzeichnis: ${args.path ?? "."}…`;
|
||
case "bash": {
|
||
const cmd = String(args.command ?? "").trim().replace(/\n[\s\S]*/s, "");
|
||
if (/git\s+commit/.test(cmd)) return "Git-Commit…";
|
||
if (/git\s+add/.test(cmd)) return "Stage Änderungen…";
|
||
if (/git\s+tag/.test(cmd)) return "Git-Tag setzen…";
|
||
if (/pytest|npm test|cargo test|go test|make test/.test(cmd)) return "Tests laufen…";
|
||
if (/git\s+(diff|log|show|tag -l)/.test(cmd)) return "Git-History lesen…";
|
||
if (/patch\s+-p1/.test(cmd)) return "Wende Patch an…";
|
||
if (/curl/.test(cmd)) return "HTTP-Request…";
|
||
return `Shell: ${cmd.slice(0, 55)}${cmd.length > 55 ? "…" : ""}`;
|
||
}
|
||
case "apply_patch": return "Wende Patch an…";
|
||
default: return "";
|
||
}
|
||
}
|
||
|
||
console.log("\ntoolExecutionLabel()");
|
||
|
||
expect(toolExecutionLabel("edit", { path: "src/main.ts" }), "Editiere src/main.ts…",
|
||
"edit: gibt Pfad zurück");
|
||
expect(toolExecutionLabel("edit", {}), "Editiere Datei…",
|
||
"edit: Fallback 'Datei' wenn kein Pfad");
|
||
expect(toolExecutionLabel("write", { path: "README.md" }), "Schreibe README.md neu…",
|
||
"write: gibt Pfad zurück");
|
||
expect(toolExecutionLabel("read", { path: "foo.py" }), "Lese foo.py…",
|
||
"read: gibt Pfad zurück");
|
||
expect(toolExecutionLabel("bash", { command: "git commit -m 'fix'" }), "Git-Commit…",
|
||
"bash: git commit → Git-Commit");
|
||
expect(toolExecutionLabel("bash", { command: "git add -A" }), "Stage Änderungen…",
|
||
"bash: git add → Stage Änderungen");
|
||
expect(toolExecutionLabel("bash", { command: "git tag v1.0.0" }), "Git-Tag setzen…",
|
||
"bash: git tag → Git-Tag setzen");
|
||
expect(toolExecutionLabel("bash", { command: "pytest tests/" }), "Tests laufen…",
|
||
"bash: pytest → Tests laufen");
|
||
expect(toolExecutionLabel("bash", { command: "cargo test" }), "Tests laufen…",
|
||
"bash: cargo test → Tests laufen");
|
||
expect(toolExecutionLabel("bash", { command: "git log --oneline" }), "Git-History lesen…",
|
||
"bash: git log → Git-History lesen");
|
||
expect(toolExecutionLabel("bash", { command: "patch -p1 < foo.patch" }), "Wende Patch an…",
|
||
"bash: patch -p1 → Wende Patch an");
|
||
expect(toolExecutionLabel("bash", { command: "curl https://api.example.com" }), "HTTP-Request…",
|
||
"bash: curl → HTTP-Request");
|
||
expect(toolExecutionLabel("bash", { command: "ls ." }), "Shell: ls .",
|
||
"bash: unbekannter Befehl kurz → kein abschließendes …");
|
||
expect(toolExecutionLabel("bash", { command: "a".repeat(60) }), `Shell: ${"a".repeat(55)}…`,
|
||
"bash: Befehl > 55 Zeichen → abgeschnitten mit …");
|
||
expect(toolExecutionLabel("apply_patch", {}), "Wende Patch an…",
|
||
"apply_patch → Wende Patch an");
|
||
expect(toolExecutionLabel("unknown_tool", {}), "",
|
||
"unbekanntes Tool → leerer String");
|
||
|
||
// ── getLastAssistantText ──────────────────────────────────────────────────────
|
||
|
||
function getLastAssistantText(ctx) {
|
||
const entries = ctx.sessionManager.getBranch();
|
||
for (let i = entries.length - 1; i >= 0; i--) {
|
||
const entry = entries[i];
|
||
if (entry.type === "message") {
|
||
const msg = entry.message;
|
||
if (msg?.role === "assistant" && Array.isArray(msg.content)) {
|
||
return msg.content
|
||
.filter((c) => c.type === "text")
|
||
.map((c) => c.text)
|
||
.join("\n");
|
||
}
|
||
}
|
||
}
|
||
return "";
|
||
}
|
||
|
||
function makeCtx(entries) {
|
||
return { sessionManager: { getBranch: () => entries } };
|
||
}
|
||
function makeMsg(role, content) {
|
||
return { type: "message", message: { role, content } };
|
||
}
|
||
|
||
console.log("\ngetLastAssistantText()");
|
||
|
||
expect(
|
||
getLastAssistantText(makeCtx([])),
|
||
"",
|
||
"leere Session → leerer String"
|
||
);
|
||
expect(
|
||
getLastAssistantText(makeCtx([
|
||
makeMsg("assistant", [{ type: "text", text: "Hallo" }]),
|
||
])),
|
||
"Hallo",
|
||
"eine assistant-Nachricht → deren Text"
|
||
);
|
||
expect(
|
||
getLastAssistantText(makeCtx([
|
||
makeMsg("user", [{ type: "text", text: "Frage" }]),
|
||
makeMsg("assistant", [{ type: "text", text: "Antwort" }]),
|
||
])),
|
||
"Antwort",
|
||
"user + assistant → gibt assistant-Text zurück"
|
||
);
|
||
expect(
|
||
getLastAssistantText(makeCtx([
|
||
makeMsg("assistant", [{ type: "text", text: "Erste" }]),
|
||
makeMsg("assistant", [{ type: "text", text: "Letzte" }]),
|
||
])),
|
||
"Letzte",
|
||
"mehrere assistant-Nachrichten → gibt die letzte zurück"
|
||
);
|
||
expect(
|
||
getLastAssistantText(makeCtx([
|
||
makeMsg("assistant", [{ type: "tool_use", id: "x", name: "bash", input: {} }]),
|
||
])),
|
||
"",
|
||
"assistant-Nachricht ohne text-Content → leerer String"
|
||
);
|
||
expect(
|
||
getLastAssistantText(makeCtx([
|
||
makeMsg("assistant", [
|
||
{ type: "text", text: "Teil 1" },
|
||
{ type: "tool_use", id: "x", name: "edit", input: {} },
|
||
{ type: "text", text: "Teil 2" },
|
||
]),
|
||
])),
|
||
"Teil 1\nTeil 2",
|
||
"gemischte text/tool_use-Inhalte → nur text-Teile mit \\n verbunden"
|
||
);
|
||
|
||
// ── detectBumpType ────────────────────────────────────────────────────────────
|
||
|
||
function detectBumpType(lines) {
|
||
if (lines.some(l => /^feat!:|BREAKING CHANGE/.test(l))) return "major";
|
||
if (lines.some(l => /^feat(\(.+\))?:/.test(l))) return "minor";
|
||
return "patch";
|
||
}
|
||
|
||
console.log("\ndetectBumpType()");
|
||
|
||
expect(detectBumpType(["feat!: neue API"]), "major",
|
||
"feat!: → major");
|
||
expect(detectBumpType(["BREAKING CHANGE: auth umgebaut"]), "major",
|
||
"BREAKING CHANGE → major");
|
||
expect(detectBumpType(["feat: CSV-Export"]), "minor",
|
||
"feat: → minor");
|
||
expect(detectBumpType(["feat(parser): neues Feature"]), "minor",
|
||
"feat(scope): → minor");
|
||
expect(detectBumpType(["fix: Crash behoben"]), "patch",
|
||
"fix: → patch");
|
||
expect(detectBumpType(["chore: cleanup"]), "patch",
|
||
"chore: → patch");
|
||
expect(detectBumpType(["feat: kleine Änderung", "feat!: breaking"]), "major",
|
||
"major hat Vorrang vor minor in gemischter Liste");
|
||
expect(detectBumpType([]), "patch",
|
||
"leere Commit-Liste → patch");
|
||
|
||
// ── parseSemVer ───────────────────────────────────────────────────────────────
|
||
|
||
function parseSemVer(tag) {
|
||
const m = tag.match(/^v?(\d+)\.(\d+)\.(\d+)$/);
|
||
return m ? [+m[1], +m[2], +m[3]] : null;
|
||
}
|
||
|
||
function expectDeep(actual, expected, label) {
|
||
expect(JSON.stringify(actual), JSON.stringify(expected), label);
|
||
}
|
||
|
||
console.log("\nparseSemVer()");
|
||
|
||
expectDeep(parseSemVer("v1.2.3"), [1, 2, 3], "v1.2.3 → [1, 2, 3]");
|
||
expectDeep(parseSemVer("1.2.3"), [1, 2, 3], "1.2.3 ohne v → [1, 2, 3]");
|
||
expectDeep(parseSemVer("v0.0.1"), [0, 0, 1], "v0.0.1 → [0, 0, 1]");
|
||
expectDeep(parseSemVer("v10.20.300"), [10, 20, 300], "v10.20.300 → dreistellige Zahlen");
|
||
expectDeep(parseSemVer("nicht-semver"), null, "ungültiger Tag → null");
|
||
expectDeep(parseSemVer("v1.2"), null, "unvollständig v1.2 → null");
|
||
expectDeep(parseSemVer(""), null, "leerer String → null");
|
||
|
||
// ── stripOptimizeFlags ────────────────────────────────────────────────────────
|
||
|
||
function stripOptimizeFlags(args) {
|
||
return (args || "")
|
||
.replace(/--rounds\s+\d+/, "")
|
||
.replace(/--test-timeout\s+\d+/, "")
|
||
.replace(/--with-doku/, "")
|
||
.replace(/--continue/, "")
|
||
.replace(/--interactive/, "")
|
||
.replace(/--no-tests/, "")
|
||
.replace(/--approve-concerns/, "")
|
||
.replace(/--test-cmd\s+"[^"]*"/, "")
|
||
.replace(/--test-cmd\s+\S+/, "")
|
||
.trim();
|
||
}
|
||
|
||
console.log("\nstripOptimizeFlags()");
|
||
|
||
expect(stripOptimizeFlags("mein Auftrag --rounds 3"), "mein Auftrag",
|
||
"--rounds N wird entfernt");
|
||
expect(stripOptimizeFlags("--with-doku Auftrag"), "Auftrag",
|
||
"--with-doku wird entfernt");
|
||
expect(stripOptimizeFlags("Auftrag --no-tests --approve-concerns"), "Auftrag",
|
||
"--no-tests und --approve-concerns werden entfernt");
|
||
expect(stripOptimizeFlags('Auftrag --test-cmd "pytest tests/"'), "Auftrag",
|
||
'--test-cmd "..." wird entfernt');
|
||
expect(stripOptimizeFlags("Auftrag --test-timeout 60"), "Auftrag",
|
||
"--test-timeout N wird entfernt");
|
||
expect(stripOptimizeFlags("--continue --interactive Auftrag"), "Auftrag",
|
||
"--continue und --interactive werden entfernt");
|
||
expect(stripOptimizeFlags("Nur ein Auftrag"), "Nur ein Auftrag",
|
||
"keine Flags → Auftrag unverändert");
|
||
|
||
// ── parseOptimizeOptions ─────────────────────────────────────────────────────
|
||
|
||
function parseOptimizeOptions(args) {
|
||
const roundsMatch = (args || "").match(/--rounds\s+(\d+)/);
|
||
const maxRounds = roundsMatch ? Math.max(1, parseInt(roundsMatch[1], 10)) : 2;
|
||
const testCmdMatch = (args || "").match(/--test-cmd\s+"([^"]+)"|--test-cmd\s+'([^']+)'|--test-cmd\s+(\S+)/);
|
||
const testCmd = testCmdMatch ? (testCmdMatch[1] ?? testCmdMatch[2] ?? testCmdMatch[3]) : null;
|
||
const testTimeoutMatch = (args || "").match(/--test-timeout\s+(\d+)/);
|
||
const testTimeout = testTimeoutMatch ? Math.max(1, parseInt(testTimeoutMatch[1], 10)) : 120;
|
||
return { maxRounds, testCmd, testTimeout };
|
||
}
|
||
|
||
console.log("\nparseOptimizeOptions()");
|
||
|
||
expect(parseOptimizeOptions("--rounds 5").maxRounds, 5,
|
||
"--rounds 5 → maxRounds = 5");
|
||
expect(parseOptimizeOptions("--rounds 0").maxRounds, 1,
|
||
"--rounds 0 → maxRounds = 1 (Math.max-Clamp)");
|
||
expect(parseOptimizeOptions("--rounds abc").maxRounds, 2,
|
||
"--rounds abc → Regex matcht nicht → Default 2");
|
||
expect(parseOptimizeOptions("").maxRounds, 2,
|
||
"kein --rounds → Default 2");
|
||
expect(parseOptimizeOptions("--test-timeout 30").testTimeout, 30,
|
||
"--test-timeout 30 → testTimeout = 30");
|
||
expect(parseOptimizeOptions("--test-timeout 0").testTimeout, 1,
|
||
"--test-timeout 0 → testTimeout = 1 (Math.max-Clamp)");
|
||
expect(parseOptimizeOptions("").testTimeout, 120,
|
||
"kein --test-timeout → Default 120");
|
||
expect(parseOptimizeOptions('--test-cmd "pytest -v"').testCmd, "pytest -v",
|
||
'--test-cmd "..." → testCmd ohne Anführungszeichen');
|
||
expect(parseOptimizeOptions("--test-cmd pytest").testCmd, "pytest",
|
||
"--test-cmd ohne Anführungszeichen → testCmd = 'pytest'");
|
||
expect(parseOptimizeOptions("auftrag --rounds 3 --test-timeout 60").maxRounds, 3,
|
||
"mehrere Flags kombiniert: maxRounds korrekt");
|
||
|
||
// ── calcVersionStrings ────────────────────────────────────────────────────────
|
||
|
||
function calcVersionStrings(current, bump) {
|
||
const [maj, min, pat] = current ?? [0, 0, 0];
|
||
const initial = !current;
|
||
const versions = initial
|
||
? { patch: "v0.0.1", minor: "v0.1.0", major: "v1.0.0" }
|
||
: { patch: `v${maj}.${min}.${pat + 1}`, minor: `v${maj}.${min + 1}.0`, major: `v${maj + 1}.0.0` };
|
||
const recommended = initial ? "minor" : bump;
|
||
const labels = ["patch", "minor", "major"].map(
|
||
t => `${t} → ${versions[t]}${t === recommended ? " (empfohlen)" : ""}`
|
||
);
|
||
return { versions, recommended, labels };
|
||
}
|
||
|
||
console.log("\ncalcVersionStrings()");
|
||
|
||
expectDeep(
|
||
calcVersionStrings(null, "patch").versions,
|
||
{ patch: "v0.0.1", minor: "v0.1.0", major: "v1.0.0" },
|
||
"initial (null): alle drei Standard-Startwerte"
|
||
);
|
||
expect(calcVersionStrings(null, "patch").recommended, "minor",
|
||
"initial: recommended ist immer minor (unabhängig vom Bump)");
|
||
expectDeep(
|
||
calcVersionStrings([1, 2, 3], "patch").versions,
|
||
{ patch: "v1.2.4", minor: "v1.3.0", major: "v2.0.0" },
|
||
"[1,2,3]: patch/minor/major korrekt hochgezählt"
|
||
);
|
||
expect(calcVersionStrings([1, 2, 3], "patch").recommended, "patch",
|
||
"[1,2,3] patch-Bump → recommended = patch");
|
||
expect(calcVersionStrings([1, 2, 3], "minor").recommended, "minor",
|
||
"[1,2,3] minor-Bump → recommended = minor");
|
||
expect(calcVersionStrings([1, 2, 3], "major").recommended, "major",
|
||
"[1,2,3] major-Bump → recommended = major");
|
||
|
||
{
|
||
const labels = calcVersionStrings([1, 2, 3], "minor").labels;
|
||
expect(labels[1], "minor → v1.3.0 (empfohlen)",
|
||
"empfohlenes Label trägt Suffix '(empfohlen)'");
|
||
expect(labels[0], "patch → v1.2.4",
|
||
"nicht-empfohlenes Label hat keinen Suffix");
|
||
}
|
||
|
||
expectDeep(
|
||
calcVersionStrings([0, 9, 9], "major").versions,
|
||
{ patch: "v0.9.10", minor: "v0.10.0", major: "v1.0.0" },
|
||
"[0,9,9]: zweistellige Zahlen korrekt hochgezählt"
|
||
);
|
||
|
||
// ── parseShipVerdict ──────────────────────────────────────────────────────────
|
||
|
||
function parseShipVerdict(text) {
|
||
return text.match(/Urteil:\s*(SHIP|NO-SHIP)/i)?.[1]?.toUpperCase() ?? "";
|
||
}
|
||
|
||
console.log("\nparseShipVerdict()");
|
||
|
||
expect(parseShipVerdict("Urteil: SHIP"), "SHIP",
|
||
"erkennt SHIP");
|
||
expect(parseShipVerdict("Urteil: NO-SHIP"), "NO-SHIP",
|
||
"erkennt NO-SHIP");
|
||
expect(parseShipVerdict("urteil: ship"), "SHIP",
|
||
"case-insensitiv: 'urteil: ship'");
|
||
expect(parseShipVerdict("kein Urteil hier"), "",
|
||
"kein Urteil → leerer String");
|
||
expect(parseShipVerdict("Urteil: PASS"), "",
|
||
"PASS ist kein gültiges SHIP-Token → leerer String");
|
||
expect(parseShipVerdict(""), "",
|
||
"leerer String → leerer String");
|
||
|
||
// ── Ergebnis ──────────────────────────────────────────────────────────────────
|
||
|
||
console.log(`\n${"─".repeat(50)}`);
|
||
console.log(`Gesamt: ${passed + failed} Tests — ${passed} bestanden, ${failed} fehlgeschlagen`);
|
||
|
||
if (failed > 0) {
|
||
process.exit(1);
|
||
}
|