TestVerboseLogCapturesTurnRecords drives a realistic two-round turn (reasoning → bash tool call → final answer) with logging on, and asserts the verbose records that make a session reconstructable for later debugging actually land in log.txt: the session header, the per-round request/packing summary
(t *testing.T)
| 699 | t.Cleanup(CloseDebugLog) |
| 700 | m := newTestModel(t, func(http.ResponseWriter, *http.Request) {}) |
| 701 | m.cfg.ActiveProfile().ContextSize = 10000 |
| 702 | |
| 703 | m.applyDone(llm.Event{PromptTokens: 9499}) // below threshold: silent |
| 704 | m.applyDone(llm.Event{}) // server reported nothing: silent |
| 705 | m.applyDone(llm.Event{PromptTokens: 9500}) // 95%: fire |
| 706 | CloseDebugLog() |
| 707 | |
| 708 | raw, err := os.ReadFile(filepath.Join(dir, "log.txt")) |
| 709 | if err != nil { |
| 710 | t.Fatal(err) |
| 711 | } |
| 712 | logStr := string(raw) |
| 713 | if got := strings.Count(logStr, "] ctx_pressure"); got != 1 { |
| 714 | t.Fatalf("tripwire must fire exactly once (at >=95%% of the window), got %d\n%s", got, logStr) |
| 715 | } |
| 716 | if !strings.Contains(logStr, "prompt_tokens=9500 at >=95% of ctx=10000") { |
| 717 | t.Fatalf("tripwire record must name the offending count and window:\n%s", logStr) |
| 718 | } |
| 719 | } |
| 720 | |
| 721 | // TestSlashModelSwitchDropsStickyFallbackState: llm.Client's noReasoning flag |
| 722 | // ("this server 400'd on the reasoning effort, stop sending it") is correct for |
| 723 | // one Client but wrong across a profile switch to a different endpoint. rebuildClient swaps in a fresh Client; this asserts the pointer |
| 724 | // changed so the sticky bit can't survive. |
| 725 | func TestSlashModelSwitchDropsStickyFallbackState(t *testing.T) { |
| 726 | m := newTestModel(t, func(http.ResponseWriter, *http.Request) {}) |
| 727 | m.cfg.Models["remote"] = &config.Profile{ |
| 728 | LLM: "cloud-model", URL: "http://remote:9000", Key: "sk-r", ContextSize: 200000, |
| 729 | } |
| 730 | if err := m.cfg.Save(); err != nil { |
| 731 | t.Fatal(err) |
| 732 | } |
| 733 | before := m.cli |
| 734 | out, _ := m.runSlash("/models remote") |
| 735 | final := out.(Model) |
| 736 | if final.cli == before { |
| 737 | t.Fatal("rebuildClient must replace the *llm.Client pointer to drop sticky reasoning fallback state") |
| 738 | } |
| 739 | if final.cli.BaseURL != "http://remote:9000" || final.cli.Model != "cloud-model" || final.cli.Token != "sk-r" { |
| 740 | t.Fatalf("fresh client missing one of the new profile's fields: %+v", final.cli) |
| 741 | } |
| 742 | } |
| 743 | |
| 744 | // TestSlashModelRejectsUnknown: unknown name is a quiet warn, not a switch. |
| 745 | func TestSlashModelRejectsUnknown(t *testing.T) { |
| 746 | m := newTestModel(t, func(http.ResponseWriter, *http.Request) {}) |
| 747 | before := m.cfg.Active |
| 748 | m2, _ := m.runSlash("/models ghost") |
| 749 | if m2.(Model).cfg.Active != before { |
| 750 | t.Fatal("unknown name must not change Active") |
| 751 | } |
| 752 | } |
| 753 | |
| 754 | func TestSlashClearResetsHistory(t *testing.T) { |
| 755 | m := newTestModel(t, func(http.ResponseWriter, *http.Request) {}) |
| 756 | m.history = append(m.history, |
| 757 | chmctx.Message{Role: chmctx.RoleUser, Content: "hi"}, |
nothing calls this directly
no test coverage detected