# Testing Für Endnutzer-Doku siehe die [README](../README.md) und [FEATURES.md](FEATURES.md). Diese Seite ist für die Weiterentwicklung von `nub` selbst: automatisierte Tests, manuelle Testrezepte pro Feature, und was aktuell (noch) nicht abgedeckt ist. ## Automatisierte Tests ```bash go build ./... go vet ./... go test ./... ``` Deckt ab: SSE-Fragmentierung/Tool-Call-Akkumulation (Golden File), Agent-Loop gegen einen Fake-Provider (Multi-Turn, Tool-Roundtrip, Stream-Fehler), Datei-Tools inkl. Pfad-Escape via `../` und Symlink, `bash`-Timeout mit Prozessgruppen-Kill, Architektur-Test für E-04 (kein Kern-Paket importiert `internal/ui`), Session-Baum (Resume-Roundtrip, Rewind+Branch, Dateirechte, Versions-Fehlerfall), Config-Layering-Präzedenz, Context Assembler (walk_up-Reihenfolge, Budget-Truncation+Warnung, Repomap-Staleness), Auto-Compaction (Trigger, Turn-Grenzen-Logik, Rewind auf einen Knoten vor der Compaction), Token-Kalibrierung, todo-Tool, Skill-Discovery (Index ohne Body, Präzedenz projekt-vor-global), MCP-Bridge (echter In-Process-Server- Roundtrip über `mcp.NewInMemoryTransports`, Allowlist, Laufzeitfehler ohne Panic), Profile-Filterung, TUI (Slash-Command-Dispatch ohne Backend-Kontakt, Steering- vs. Follow-up-Queue-Semantik, Tree-/Sessions-Modal-Auswahl inkl. Branch/Wechsel/Löschen mit Rückfrage, Diff-/Fehler-Rendering, /help- Formatierung, Skills-als-Slash-Commands, Zeilenumbruch, historische Token-Wiederherstellung), Permission-Policy (Modus-Auflösung, deny_paths/ deny_bash überstimmen auto/ask), Agent-Loop-Permission-Integration (deny/ask blockieren die Tool-Ausführung nachweislich, nil-Hook -> harter Fehler), TUI-Rückfrage-Modal (blockiert bis zur Antwort, Kontextabbruch löst sauber auf), Scaffold/`nub init` (legt an, überschreibt bestehende Dateien nie, generierte Config parst und rundtripped korrekt, Git-Exclude wird gesetzt), `question`-Tool (nil-Hook -> harter Fehler, leere Frage abgelehnt, Antwort-Roundtrip inkl. Options, Ask-Fehler landet als `IsError`), TUI-Rückfrage-Block für `question` (blockiert bis zur Antwort, Kontextabbruch löst sauber auf, Eingabe während offener Frage wird als Antwort statt als Chat-Nachricht/Slash-Command behandelt, kein Overlay). **Architektur-Nachweis M6** (TUI ist ein reiner Consumer, entfernbar): `TestArchitecture_NoUIImportBelowAgent` (`internal/agent/arch_test.go`) verbietet jedem Kern-Paket den Import von `internal/ui/*`, also auch `internal/ui/tui` — automatisiert bei jedem Testlauf geprüft. Zusätzlich manuell verifiziert: `internal/ui/tui` entfernt, `go build ./internal/...` bleibt grün, `cmd/nub` baut nach Entfernen der TUI-Verzweigung ebenfalls, `-p` verhält sich danach identisch. ## Manuelle Testrezepte Die Tests unten gehen von `nub -p` aus (die beschriebenen `→`/`←`-Zeilen auf stderr sind Print-Modus-Ausgabe). Dieselben Szenarien lassen sich genauso in der TUI (`nub`, ohne `-p`) durchspielen — dort erscheinen Tool-Aufrufe direkt im Transkript statt auf stderr, und Ctrl+C/Rewind laufen über die im README beschriebenen Tastenkombinationen/Kommandos. Aus dem Konzept, Abschnitt 6: > Ein mehrstufiger Task ("lies X, ändere Y, führe die Tests aus") läuft gegen > einen echten Endpoint durch. `Ctrl+C` bricht mitten in einem `bash`-Aufruf > sauber ab und tötet die Prozessgruppe. ### Test 1 — Mehrstufiger Task In einem kleinen Testrepo (z.B. mit ein paar `.go`-Dateien und `go test` lauffähig): ``` Lies main.go, füge einen Kommentar über der main-Funktion hinzu der "entry point" sagt, und führe danach go build ./... aus um zu prüfen dass es noch kompiliert. ``` Erwartet: Das Modell ruft nacheinander `read`, `edit` und `bash` auf (sichtbar an den `→`/`←`-Zeilen auf stderr), am Ende eine Textantwort mit dem Ergebnis. Danach `Ctrl+D`, dann `nub sessions` — die eben gelaufene Session sollte mit korrekter Node-Anzahl auftauchen. `nub resume ` und eine weitere Nachricht schicken sollte den Kontext (z.B. den Dateinamen) noch kennen, ohne dass du ihn wiederholst. ### Test 1b — Rewind/Branch Nach Test 1: die Node-ID kurz vor der letzten Antwort ermitteln (siehe README, Abschnitt „Sessions") und damit fortsetzen: ```bash nub resume --branch Mach stattdessen etwas anderes: ... ``` Erwartet: `nub` schreibt ab hier einen zweiten Ast. `cat .nub/sessions/.jsonl | wc -l` zeigt, dass die alten Zeilen erhalten bleiben (nur neue kommen dazu). Mit dem `--branch`-Trick lässt sich sowohl der alte als auch der neue Ast wieder aufrufen. ### Test 1c — Auto-Compaction Eine echte Session lang genug laufen zu lassen, um 75% des Modell-Fensters zu füllen, dauert. Stattdessen `max_context` künstlich klein setzen, um den Trigger nach wenigen Nachrichten auszulösen — in `.nub/config.toml`: ```toml version = 1 [[endpoint]] name = "openai" base_url = "https://api.openai.com/v1" api_key = "env:OPENAI_API_KEY" [endpoint.caps] max_context = 500 ``` Dann `nub` starten und 4-5 kurze Nachrichten hintereinander schicken. Erwartet: Nach ein paar Nachrichten erscheint auf stderr `compacted N nodes into a M-token summary`. `nub` läuft danach normal weiter (das Modell kennt den zusammengefassten Kontext noch grob). Dann `Ctrl+D`, Node-ID der allerersten Nachricht ermitteln (siehe README, „Sessions") und: ```bash nub resume --branch ``` Erwartet: Das funktioniert weiterhin, obwohl dieser Knoten längst wegkompaktiert wurde — die Original-Nodes bleiben im Baum erhalten. `.nub/config.toml` danach wieder auf einen realistischen `max_context` zurücksetzen (oder den Override entfernen). ### Test 2 — Ctrl+C mitten in bash ``` Führe "sleep 30" aus. ``` Während `→ bash {...}` läuft: `Ctrl+C` drücken. Erwartet: - `nub` beendet sich zügig (nicht erst nach 30s) - stderr zeigt `aborted`, Exit-Code ist `3` - Kein Zombie-Prozess bleibt übrig — prüfen mit `ps aux | grep sleep` direkt danach, sollte leer sein ### Test 3 — Mehrdeutiger edit-Treffer ``` Ersetze in main.go den Text "return" durch "return " (falls "return" mehrfach vorkommt) ``` Erwartet: Falls `return` mehrfach in der Datei steht, meldet das `edit`-Tool einen Fehler mit Trefferzahl statt zu raten — sichtbar als `← edit [error]` auf stderr, das Modell sollte daraufhin entweder `replace_all` nutzen oder den `old_string` präzisieren. ### Test 4 — Unbekanntes Tool / Fehlerpfad Kein manueller Test nötig, ist per Fake-Provider automatisiert abgedeckt (`go test ./...`). ### Test 5 — Kaputter MCP-Server verhindert den Start nicht ```toml version = 1 [[mcp]] name = "broken" command = "this-binary-does-not-exist" ``` ```bash nub mcp ``` Erwartet: `warning: mcp server "broken": ...` auf stderr, Exit-Code 0, kein Absturz. Danach echtes `nub` starten (mit derselben Config) — muss trotz des kaputten Servers normal hochkommen. ### Test 6 — Skill wird erst bei Bedarf geladen Ein Skill wie im README (Abschnitt „Skills") anlegen, dann im laufenden `nub` fragen: `Welche Skills hast du verfügbar, und was steht in "refactoring"?` — erwartet: Das Modell nennt den Skill aus dem Index, ruft danach sichtbar `→ read_skill {"name":"refactoring"}` auf, um den Body zu laden — nicht schon beim Start. ### Test 7 — Steering vs. Follow-up, Rewind über die TUI Nur in der TUI (`nub`, ohne `-p`): 1. Einen Task starten, der mehrere Tool-Aufrufe braucht (wie Test 1). Während `running: ...` in der Statuszeile steht, eine neue Nachricht eingeben und **Enter** drücken (Steering). Erwartet: Sie wird eingeschleust, sobald der laufende Tool-Aufruf fertig ist, ohne dass du warten musst. 2. Denselben Versuch mit **Alt+Enter**. Erwartet: Statuszeile zeigt `queued: 1`, die Nachricht erscheint im Transkript mit `⏳`-Präfix und wird erst gesendet, wenn der Turn ganz fertig ist (kein `tool_use` mehr offen). 3. `/tree` (oder `/branch`) öffnen — Liste aller Nodes erscheint, aktueller Head markiert. Mit ↑/↓ einen früheren Knoten wählen, Enter drücken. Erwartet: Head springt zurück, eine neue Nachricht hängt sich dort an (zweiter Ast, wie in Test 1b, nur ohne CLI-Flag). 4. `/export` ausführen, geschriebene Datei öffnen — Transkript sollte vollständig als Markdown lesbar sein, inklusive Diffs. 5. `/help` ausführen — jedes Kommando sollte in einer eigenen Zeile mit kurzer Beschreibung erscheinen, nicht als ein langer Fließtext. ### Test 8 — Prompt-Cache-Verifikation (M7) Nur mit echtem OpenAI-Endpoint sinnvoll (lokale Server cachen meist nicht). OpenAIs automatisches Prompt-Caching greift erst ab **>1024 Tokens** stabilem Prefix — ein `AGENTS.md`/`REPOMAP.md` im Kontext hilft, diese Schwelle zuverlässig zu überschreiten. 1. In einem Repo mit ein paar hundert Zeilen Kontext (`nub map` vorher laufen lassen, damit `REPOMAP.md` existiert) `nub -p` starten. 2. Zwei, drei kurze Nachrichten nacheinander schicken (z.B. "Was macht go.mod?", dann "Und main.go?"). 3. Auf stderr nach jedem Turn erscheint jetzt `usage: input=X output=Y cache_read=Z`. Erwartet: Beim **ersten** Turn ist `cache_read` meist `0` (nichts zum Cachen vorhanden). Ab dem **zweiten** Turn sollte `cache_read` deutlich über 0 liegen und nah an `input` herankommen, weil System-Prompt + bisherige Historie als stabiler Prefix erneut gesendet werden. In der TUI ist dasselbe in der Statuszeile sichtbar: `tokens: N (cached: M)`. Falls `cache_read` dauerhaft bei 0 bleibt, obwohl der Kontext >1024 Tokens hat: prüfen, ob `Caps.UsageInStream` für den aktiven Endpoint tatsächlich `true` ist (`nub context` bzw. die Modell-Registry in `internal/llm/registry`) — ohne `stream_options.include_usage` liefert OpenAI gar keine Usage-Daten, dann bleibt der Wert immer 0, unabhängig davon ob gecacht wurde. ### Test 9 — Permissions: ask, deny, deny_paths In `.nub/config.toml`: ```toml [permissions] edit = "ask" bash = "deny" deny_paths = ["**/secrets.txt"] ``` 1. **TUI**, eine Nachricht schicken, die `edit` auf einer normalen Datei auslöst. Erwartet: ein umrandeter `⚠ Rückfrage: edit`-Block erscheint im Transkript (kein Vollbild — Nachrichtenliste, Eingabefeld und Statuszeile bleiben sichtbar), Statuszeile zeigt zusätzlich `⚠ permission required`. Mit `n`/Esc ablehnen — Modell bekommt einen Fehler-`tool_result` und macht sinnvoll weiter (fragt nach, versucht etwas anderes). Erneut versuchen, mit `y` bestätigen — Edit läuft normal. 2. Eine Nachricht schicken, die `bash` auslösen würde. Erwartet: sofortiger Fehler `permission denied: bash is not allowed by policy`, kein Modal (deny fragt nie). 3. Eine Datei `secrets.txt` anlegen, Modell bitten sie zu lesen/editieren. Erwartet: Fehler durch `deny_paths`, unabhängig davon dass `read`/`edit` sonst auf `auto`/`ask` stehen — Deny-Pfade überstimmen immer. 4. Dasselbe **im Print-Modus** (`nub -p`) mit `edit = "ask"`: Erwartet sofortiger Fehler `requires confirmation ('ask'), which this mode does not support` statt eines hängenden Prozesses — `ask` ist dort laut Konzept (E-11) ein harter Fehler, keine Rückfrage über stdin. ### Test 10 — nub init / /init In einem leeren Testverzeichnis (kein `.nub/`, kein `AGENTS.md`): ```bash nub init ``` Erwartet: `.nub/config.toml` (mit `[permissions]`-Beispiel) und `AGENTS.md` werden angelegt, beide Pfade auf stdout gemeldet. `.git/info/exclude` enthält danach `.nub/` (git-Repo vorausgesetzt). Erneut `nub init` ausführen — erwartet: beide Dateien werden als `skipped (already exists)` gemeldet, Inhalt bleibt unverändert (vorher manuell etwas in `AGENTS.md` ändern und danach verifizieren, dass es erhalten bleibt). Dasselbe in der TUI mit `/init` — meldet dieselben zwei Pfade als Command-Output im Transkript statt auf stdout. ### Test 11 — question-Tool 1. **TUI**: Modell bitten, vor einer mehrdeutigen Aktion nachzufragen (z.B. "frag mich per question-Tool, welches Ausgabeformat ich will, mit den Optionen json und yaml"). Erwartet: ein umrandeter `❓ Rückfrage:`-Block erscheint im Transkript (kein Vollbild), Statuszeile zeigt `❓ question pending`, Eingabefeld-Placeholder wechselt auf den Antwort-Hinweis. Text eintippen und Enter drücken — landet **nicht** als neue Chat-Nachricht oder Slash-Kommando, sondern als Antwort des Tools; Transkript zeigt danach `Frage: … / Antwort: …`, Placeholder wechselt zurück, Modell macht mit der Antwort sinnvoll weiter. 2. **Print-Modus** (`nub -p`): Modell dasselbe fragen lassen. Erwartet sofortiger Fehler ("interactive questions are not supported in this mode…") statt eines hängenden Prozesses — analog zu `ask`-Permissions ist auch das `question`-Tool im Print-Modus bewusst kein Rückfrage- Mechanismus über stdin, da Print headless laufen soll (E-11). 3. Skill mit einer Anweisung testen, die das Modell explizit zum `question`-Tool greifen lässt (z.B. "frage vor dem Löschen von Dateien immer erst per question-Tool nach") — Verhalten identisch zu 1., da das Tool unabhängig davon nutzbar ist, ob Modell oder Skill-Anweisung den Aufruf veranlasst. ## Bekannte Grenzen (Entwicklungsstand) - Bilder werden nicht gemappt, auch wenn der Endpoint sie unterstützt - Permissions/`deny_paths`/`deny_bash` gelten nur für die sechs eingebauten Tools (`read`/`glob`/`grep`/`write`/`edit`/`bash`) — MCP- Tools, `todo` und `read_skill` laufen immer als `auto` und lassen sich aktuell nicht einzeln einschränken - Compaction nutzt immer dasselbe Modell für die Zusammenfassung — keine Möglichkeit, dafür ein günstigeres Modell zu konfigurieren - Token-Kalibrierung ist rein In-Process — kein persistenter Zustand über Sessions hinweg - MCP: kein Reconnect zur Laufzeit (E-06 — würde die Tool-Liste ändern und den Prompt-Cache brechen), kein OAuth-Login-Flow getestet, MCP-Prompts/ -Resources werden nicht angebunden, nur Tools - Skills: kein `nub skill init` o.ä., Skills müssen von Hand angelegt werden - TUI: kein Model-Auswahl-Picker (`/model ` setzt nur direkt, kein Modal mit Liste), `/tree` zeigt Nodes flach sortiert nach Erstellzeit statt als echten Baum mit Einrückung, `bash`-Streaming-Output läuft nur in die Statuszeile statt live ins Transkript (erst das fertige Ergebnis landet dort), Bildschirm-Resize während eines Modals kann die Breite nicht neu berechnen bis zum nächsten Tastendruck