model_profile_restoration_test.go RawHistoryFolder
1package agent
2
3import (
4 "testing"
5
6 "github.com/bnema/zut/packages/agent/modes"
7 "github.com/bnema/zut/packages/core"
8 "github.com/bnema/zut/packages/tui"
9)
10
11func TestResolveRepairsActiveModelProfileFallback(t *testing.T) {
12 for _, tc := range []struct {
13 name string
14 active int
15 profile QuickModelShortcut
16 }{
17 {"missing model", 2, QuickModelShortcut{Provider: "openai", Model: "removed-model", Reasoning: "high"}},
18 {"missing provider default slot", 0, QuickModelShortcut{Provider: "removed-provider", Model: "removed-model", Reasoning: "high"}},
19 } {
20 t.Run(tc.name, func(t *testing.T) {
21 t.Setenv("ZUT_HOME", t.TempDir())
22 slot := tc.active
23 if slot == 0 {
24 slot = 1
25 }
26 profiles := make([]QuickModelShortcut, slot)
27 profiles[slot-1] = tc.profile
28 if err := SaveConfig(Config{ActiveModelProfile: tc.active, QuickModelShortcuts: profiles}); err != nil {
29 t.Fatal(err)
30 }
31 args := Args{APIKey: "synthetic-test-key"}
32 resolved, err := Resolve(args, false)
33 if err != nil {
34 t.Fatal(err)
35 }
36 saved, err := LoadConfig()
37 if err != nil {
38 t.Fatal(err)
39 }
40 p := saved.QuickModelShortcuts[slot-1]
41 if p.Provider != resolved.Provider || p.Model != resolved.Model || p.Reasoning != resolved.Reasoning {
42 t.Fatalf("fallback left stale profile: %+v; resolved %s/%s (%s)", p, resolved.Provider, resolved.Model, resolved.Reasoning)
43 }
44 again, err := Resolve(args, false)
45 if err != nil {
46 t.Fatal(err)
47 }
48 if again.Provider != resolved.Provider || again.Model != resolved.Model || again.Reasoning != resolved.Reasoning {
49 t.Fatal("repaired selection changed on next resolution")
50 }
51 })
52 }
53}
54
55func TestResolveExplicitFallbackDoesNotRepairFavorite(t *testing.T) {
56 for _, args := range []Args{{Model: "removed-model"}, {Provider: "removed-provider"}} {
57 t.Setenv("ZUT_HOME", t.TempDir())
58 original := QuickModelShortcut{Provider: "openai", Model: "gpt-5.6-sol", Reasoning: "high"}
59 if err := SaveConfig(Config{ActiveModelProfile: 1, QuickModelShortcuts: []QuickModelShortcut{original}}); err != nil {
60 t.Fatal(err)
61 }
62 args.APIKey = "synthetic-test-key"
63 if _, err := Resolve(args, false); err != nil {
64 t.Fatal(err)
65 }
66 saved, err := LoadConfig()
67 if err != nil {
68 t.Fatal(err)
69 }
70 if saved.QuickModelShortcuts[0] != original {
71 t.Fatalf("explicit fallback overwrote favorite: %+v", saved.QuickModelShortcuts[0])
72 }
73 }
74}
75
76func TestModelProfileStartupClampsReasoningAndPreservesAutosave(t *testing.T) {
77 for _, tc := range []struct {
78 profile QuickModelShortcut
79 want string
80 }{
81 {QuickModelShortcut{Provider: "openai", Model: "gpt-5.6-sol", Reasoning: "minimum"}, "low"},
82 {QuickModelShortcut{Provider: "google", Model: "gemini-2.0-flash", Reasoning: "high"}, ""},
83 } {
84 t.Run(tc.profile.Model, func(t *testing.T) {
85 t.Setenv("ZUT_HOME", t.TempDir())
86 cfg := Config{ActiveModelProfile: 1, QuickModelShortcuts: []QuickModelShortcut{tc.profile}}
87 if err := SaveConfig(cfg); err != nil {
88 t.Fatal(err)
89 }
90 r, err := Resolve(Args{APIKey: "synthetic-test-key"}, false)
91 if err != nil {
92 t.Fatal(err)
93 }
94 if r.Reasoning != tc.want {
95 t.Fatalf("startup reasoning=%q, want %q", r.Reasoning, tc.want)
96 }
97 i := modes.NewInteractive(modes.InteractiveConfig{
98 Theme: tui.Dark, Provider: r.Provider, Model: r.Model, Reasoning: r.Reasoning,
99 Agent: core.NewAgent(nil, r.Model, "", nil), SettingsStore: configSettingsStore{},
100 QuickModelShortcuts: interactiveQuickModelShortcuts(cfg.QuickModelShortcuts), ActiveModelProfile: 1,
101 })
102 i.SubmitSlash("/model " + r.Model)
103 saved, err := LoadConfig()
104 if err != nil {
105 t.Fatal(err)
106 }
107 if saved.QuickModelShortcuts[0].Reasoning != tc.want {
108 t.Fatalf("clamped active profile failed to autosave: %+v", saved.QuickModelShortcuts[0])
109 }
110 })
111 }
112}