diff --git a/run-tests.sh b/run-tests.sh new file mode 100755 index 0000000..344b718 --- /dev/null +++ b/run-tests.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Unit-Tests für pi-coder-judge-extension.ts +# Keine Abhängigkeiten außer node — entfernt TypeScript-Annotationen on-the-fly. + +set -euo pipefail + +TS_FILE="$(dirname "$0")/test-utils.ts" + +if ! command -v node &>/dev/null; then + echo "❌ node nicht gefunden" >&2 + exit 1 +fi + +# TypeScript-Annotationen entfernen: `: string`, `: unknown`, `: void`, `: boolean` +# und `function f(a: T, b: T)` → `function f(a, b)` (einfache Parameterlisten) +node --input-type=module < <( + sed \ + -e 's/: string\b//g' \ + -e 's/: unknown\b//g' \ + -e 's/: void\b//g' \ + -e 's/: boolean\b//g' \ + -e 's/: number\b//g' \ + -e 's/(s: )/(s)/g' \ + -e 's/(text: )/(text)/g' \ + -e 's/(actual, expected: unknown, label: string)/( actual, expected, label)/g' \ + "$TS_FILE" +) diff --git a/test-utils.ts b/test-utils.ts new file mode 100644 index 0000000..cf3f369 --- /dev/null +++ b/test-utils.ts @@ -0,0 +1,193 @@ +// 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" +); + +// ── Ergebnis ────────────────────────────────────────────────────────────────── + +console.log(`\n${"─".repeat(50)}`); +console.log(`Gesamt: ${passed + failed} Tests — ${passed} bestanden, ${failed} fehlgeschlagen`); + +if (failed > 0) { + process.exit(1); +}