| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "os" |
| 6 | "path/filepath" |
| 7 | "reasonix/internal/config" |
| 8 | "strings" |
| 9 | "testing" |
| 10 | ) |
| 11 | |
| 12 | func TestWebSearchModelSettingsCandidatesAndStaleRef(t *testing.T) { |
| 13 | isolateDesktopUserDirs(t) |
| 14 | on := true |
| 15 | cfg := &config.Config{Providers: []config.ProviderEntry{ |
| 16 | {Name: "search", Kind: "responses", BaseURL: "http://localhost:1234", Models: []string{"m", "org/fast"}, Default: "m", WebSearch: &on}, |
| 17 | {Name: "hidden", Kind: "responses", BaseURL: "http://localhost:1234", Model: "m", WebSearch: &on}, |
| 18 | }, Desktop: config.DesktopConfig{ProviderAccess: []string{"search"}}, Agent: config.AgentConfig{WebSearchModel: "missing/m"}} |
| 19 | a := NewApp() |
| 20 | var v SettingsView |
| 21 | a.populateWebSearchSettings(&v, cfg, t.TempDir()) |
| 22 | if len(v.WebSearchModels) != 2 || v.WebSearchModelStatus != "invalid" { |
| 23 | t.Fatalf("view: %+v", v) |
| 24 | } |
| 25 | if cfg.Agent.WebSearchModel != "missing/m" { |
| 26 | t.Fatal("invalid ref cleared") |
| 27 | } |
| 28 | raw, _ := json.Marshal(a.defaultSettingsView()) |
| 29 | if !strings.Contains(string(raw), `"webSearchModels":[]`) { |
| 30 | t.Fatal("fallback array is null") |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | func TestWebSearchModelProjectOverrideView(t *testing.T) { |
| 35 | isolateDesktopUserDirs(t) |
| 36 | root := t.TempDir() |
| 37 | if err := os.WriteFile(filepath.Join(root, "reasonix.toml"), []byte("[agent]\nweb_search_model = \"project/m\"\n"), 0600); err != nil { |
| 38 | t.Fatal(err) |
| 39 | } |
| 40 | cfg := config.Default() |
| 41 | cfg.Agent.WebSearchModel = "global/m" |
| 42 | v := SettingsView{WebSearchModel: cfg.Agent.WebSearchModel} |
| 43 | NewApp().populateWebSearchSettings(&v, cfg, root) |
| 44 | if !v.WebSearchModelOverridden || v.EffectiveWebSearchModel != "project/m" || v.WebSearchModel != "global/m" { |
| 45 | t.Fatalf("override not explained: %+v", v) |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | func TestSetWebSearchModelPersistsAndRebuilds(t *testing.T) { |
| 50 | isolateDesktopUserDirs(t) |
| 51 | on := true |
| 52 | cfg := config.Default() |
| 53 | cfg.Providers = []config.ProviderEntry{{Name: "local", Kind: "responses", BaseURL: "http://localhost:12345", Models: []string{"main", "search"}, Default: "main", WebSearch: &on}} |
| 54 | cfg.DefaultModel = "local/main" |
| 55 | cfg.Desktop.ProviderAccess = []string{"local"} |
| 56 | if err := cfg.SaveTo(config.UserConfigPath()); err != nil { |
| 57 | t.Fatal(err) |
| 58 | } |
| 59 | a := NewApp() |
| 60 | tab := testTab("search-assignment", globalWorkspaceRoot()) |
| 61 | tab.Scope = "global" |
| 62 | tab.model = "local/main" |
| 63 | tab.sink = &tabEventSink{tabID: tab.ID, app: a} |
| 64 | a.tabs = map[string]*WorkspaceTab{tab.ID: tab} |
| 65 | a.tabOrder = []string{tab.ID} |
| 66 | a.activeTabID = tab.ID |
| 67 | t.Cleanup(func() { |
| 68 | if tab.Ctrl != nil { |
| 69 | tab.Ctrl.Close() |
| 70 | } |
| 71 | }) |
| 72 | if err := a.SetWebSearchModel("local/search"); err != nil { |
| 73 | t.Fatal(err) |
| 74 | } |
| 75 | if tab.Ctrl == nil { |
| 76 | t.Fatal("controller not rebuilt") |
| 77 | } |
| 78 | if tab.model != "local/main" { |
| 79 | t.Fatal("search changed conversation model") |
| 80 | } |
| 81 | raw, err := os.ReadFile(config.UserConfigPath()) |
| 82 | if err != nil { |
| 83 | t.Fatal(err) |
| 84 | } |
| 85 | if !strings.Contains(string(raw), `web_search_model = "local/search"`) { |
| 86 | t.Fatal("assignment not saved") |
| 87 | } |
| 88 | if err := a.SetWebSearchModel("missing/model"); err == nil { |
| 89 | t.Fatal("invalid assignment saved") |
| 90 | } |
| 91 | after, _ := os.ReadFile(config.UserConfigPath()) |
| 92 | if string(raw) != string(after) { |
| 93 | t.Fatal("rejected assignment changed config") |
| 94 | } |
| 95 | if err := renameProviderConnections(cfg, []string{"local"}, "Renamed connection"); err != nil { |
| 96 | t.Fatal(err) |
| 97 | } |
| 98 | if _, err := cfg.ResolveWebSearchModel("local/search"); err != nil { |
| 99 | t.Fatal("display rename invalidated assignment") |
| 100 | } |
| 101 | } |
| 102 |