返回 DeepSeek-Reasonix
web_search_model_test.go
根目录 / internal / config / web_search_model_test.go
1 package config
2
3 import (
4 "github.com/BurntSushi/toml"
5 "os"
6 "path/filepath"
7 "strings"
8 "testing"
9 )
10
11 func searchAssignmentConfig() *Config {
12 return &Config{Providers: []ProviderEntry{
13 {Name: "first", Kind: "responses", BaseURL: "http://localhost:8000", Models: []string{"m", "org/fast"}, Default: "m", WebSearch: boolPointer(true)},
14 {Name: "second", Kind: "anthropic", BaseURL: "http://localhost:8001", Model: "m", WebSearch: boolPointer(true)},
15 }}
16 }
17
18 func TestWebSearchModelAssignment(t *testing.T) {
19 c := searchAssignmentConfig()
20 current := c.Providers[0]
21 current.WebSearch = boolPointer(false)
22 for _, automatic := range []string{"", "auto", "AUTO"} {
23 c.Agent.WebSearchModel = automatic
24 if got := c.ResolveWebSearch(&current); got.Status != "disabled" {
25 t.Fatalf("automatic disable: %+v", got)
26 }
27 }
28 if err := c.SetWebSearchModel(" first/org/fast "); err != nil {
29 t.Fatal(err)
30 }
31 got := c.ResolveWebSearch(&current)
32 if got.Entry == nil || got.Entry.Name != "first" || got.Entry.Model != "org/fast" {
33 t.Fatalf("explicit assignment: %+v", got)
34 }
35 c.Agent.WebSearchModel = "second/m"
36 got = c.ResolveWebSearch(&current)
37 if got.Entry == nil || got.Entry.Name != "second" {
38 t.Fatal("main disable incorrectly overrides explicit account")
39 }
40 got.Entry.BaseURL = "changed"
41 if c.Providers[1].BaseURL == "changed" {
42 t.Fatal("route not detached")
43 }
44 c.Providers[1].WebSearch = boolPointer(false)
45 if got = c.ResolveWebSearch(nil); got.Status != "invalid" || got.Entry != nil {
46 t.Fatal("disabled assignment fell back")
47 }
48 c.Providers = c.Providers[:1]
49 if got = c.ResolveWebSearch(nil); got.Status != "invalid" {
50 t.Fatal("removed assignment fell back")
51 }
52 if c.Agent.WebSearchModel != "second/m" {
53 t.Fatal("lost invalid reference")
54 }
55 for _, ref := range []string{"first/missing", "missing/m", "first"} {
56 if err := c.SetWebSearchModel(ref); err == nil {
57 t.Fatalf("accepted %q", ref)
58 }
59 }
60 c.Agent.WebSearchModel = "first/m"
61 c.Desktop.ProviderAccess = []string{}
62 if c.ResolveWebSearch(nil).Status != "invalid" {
63 t.Fatal("access restriction bypassed")
64 }
65 c.Desktop.ProviderAccess = nil
66 c.Environment.Offline = true
67 if c.ResolveWebSearch(nil).Status != "disabled" {
68 t.Fatal("offline bypassed")
69 }
70 c.Environment.Offline = false
71 c.Tools.Enabled = []string{"read_file"}
72 if c.ResolveWebSearch(nil).Status != "disabled" {
73 t.Fatal("allowlist bypassed")
74 }
75 }
76
77 func TestWebSearchModelCredentialsAndProtocol(t *testing.T) {
78 c := searchAssignmentConfig()
79 c.Agent.WebSearchModel = "first/m"
80 c.Providers[0].BaseURL = "https://search.example"
81 c.Providers[0].APIKeyEnv = "REASONIX_SEARCH_TEST_MISSING_KEY"
82 t.Setenv("REASONIX_SEARCH_TEST_MISSING_KEY", "")
83 if c.ResolveWebSearch(nil).Status != "invalid" {
84 t.Fatal("missing credentials accepted")
85 }
86 c.Providers[0].resolvedAPIKey = "test"
87 if c.ResolveWebSearch(nil).Status != "ready" {
88 t.Fatal("configured provider rejected")
89 }
90 c.Providers[0].Kind = "openai"
91 if c.ResolveWebSearch(nil).Status != "invalid" {
92 t.Fatal("unsupported protocol accepted")
93 }
94 c.Providers[0].BaseURL = "https://api.deepseek.com"
95 got := c.ResolveWebSearch(nil)
96 if got.Entry == nil || got.Entry.Kind != "anthropic" || got.Entry.APIKey() != "test" {
97 t.Fatal("official conversion lost credentials")
98 }
99 }
100
101 func TestWebSearchModelRoundTripAndPreservation(t *testing.T) {
102 c := Default()
103 c.Agent.WebSearchModel = "search/org/fast"
104 var decoded Config
105 if _, err := toml.Decode(RenderTOML(c), &decoded); err != nil {
106 t.Fatal(err)
107 }
108 if decoded.Agent.WebSearchModel != c.Agent.WebSearchModel {
109 t.Fatal("full render lost assignment")
110 }
111 if !strings.Contains(RenderTOMLProjectDelta(c), `web_search_model = "search/org/fast"`) {
112 t.Fatal("project delta lost assignment")
113 }
114 path := filepath.Join(t.TempDir(), "config.toml")
115 original := "# user comment\nfuture_option = true\n[agent]\n# preserved\nweb_search_model = \"auto\"\nfuture_agent_option = 42\n"
116 if err := os.WriteFile(path, []byte(original), 0600); err != nil {
117 t.Fatal(err)
118 }
119 unlock := LockUserConfigEdits()
120 defer unlock()
121 if err := c.SaveWebSearchModelTo(path); err != nil {
122 t.Fatal(err)
123 }
124 raw, err := os.ReadFile(path)
125 if err != nil {
126 t.Fatal(err)
127 }
128 for _, want := range []string{"# user comment", "# preserved", "future_option = true", "future_agent_option = 42", `web_search_model = "search/org/fast"`} {
129 if !strings.Contains(string(raw), want) {
130 t.Fatalf("lost %s: %s", want, raw)
131 }
132 }
133 // Previous schema ignores the added optional field, rather than rejecting TOML.
134 var previous struct {
135 Agent struct {
136 VisionModel string `toml:"vision_model"`
137 }
138 }
139 meta, err := toml.Decode(string(raw), &previous)
140 if err != nil {
141 t.Fatal(err)
142 }
143 found := false
144 for _, key := range meta.Undecoded() {
145 if key.String() == "agent.web_search_model" {
146 found = true
147 }
148 }
149 if !found {
150 t.Fatal("expected previous reader to ignore assignment")
151 }
152 }
153
153 lines GO