返回 DeepSeek-Reasonix
web_search_test.go
根目录 / internal / config / web_search_test.go
1 package config
2
3 import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8
9 "github.com/BurntSushi/toml"
10 )
11
12 func TestEffectiveWebSearchDefaultsOnlySupportedOfficialDeepSeekAPIs(t *testing.T) {
13 explicitTrue := true
14 explicitFalse := false
15 tests := []struct {
16 name string
17 entry ProviderEntry
18 want bool
19 }{
20 {
21 name: "official responses omitted defaults on",
22 entry: ProviderEntry{Kind: "responses", BaseURL: "https://api.deepseek.com"},
23 want: true,
24 },
25 {
26 name: "official anthropic omitted defaults on",
27 entry: ProviderEntry{Kind: "anthropic", BaseURL: "https://api.deepseek.com/anthropic"},
28 want: true,
29 },
30 {
31 name: "official responses explicit off wins",
32 entry: ProviderEntry{Kind: "responses", BaseURL: "https://api.deepseek.com", WebSearch: &explicitFalse},
33 want: false,
34 },
35 {
36 name: "compatible third party omitted stays off",
37 entry: ProviderEntry{Kind: "responses", BaseURL: "https://gateway.example/v1"},
38 want: false,
39 },
40 {
41 name: "compatible third party explicit on wins",
42 entry: ProviderEntry{Kind: "anthropic", BaseURL: "https://gateway.example/anthropic", WebSearch: &explicitTrue},
43 want: true,
44 },
45 {
46 name: "deepseek subdomain is not treated as official",
47 entry: ProviderEntry{Kind: "responses", BaseURL: "https://relay.deepseek.com"},
48 want: false,
49 },
50 {
51 name: "responses rejects anthropic path",
52 entry: ProviderEntry{Kind: "responses", BaseURL: "https://api.deepseek.com/anthropic"},
53 want: false,
54 },
55 {
56 name: "anthropic requires documented path",
57 entry: ProviderEntry{Kind: "anthropic", BaseURL: "https://api.deepseek.com"},
58 want: false,
59 },
60 {
61 name: "official trailing slash accepted",
62 entry: ProviderEntry{Kind: "anthropic", BaseURL: "https://api.deepseek.com/anthropic/"},
63 want: true,
64 },
65 {
66 name: "insecure scheme is not official",
67 entry: ProviderEntry{Kind: "responses", BaseURL: "http://api.deepseek.com"},
68 want: false,
69 },
70 {
71 name: "official chat completions has no server tool wire",
72 entry: ProviderEntry{Kind: "openai", BaseURL: "https://api.deepseek.com", WebSearch: &explicitTrue},
73 want: false,
74 },
75 }
76 for _, tt := range tests {
77 t.Run(tt.name, func(t *testing.T) {
78 if got := EffectiveWebSearch(&tt.entry); got != tt.want {
79 t.Fatalf("EffectiveWebSearch(%+v) = %t, want %t", tt.entry, got, tt.want)
80 }
81 })
82 }
83 }
84
85 func TestOpenCodeGoDeepSeekWebSearchCapabilityIsModelScoped(t *testing.T) {
86 explicitTrue := true
87 tests := []struct {
88 name string
89 entry ProviderEntry
90 want bool
91 }{
92 {
93 name: "responses flash",
94 entry: ProviderEntry{Kind: "responses", BaseURL: "https://opencode.ai/zen/go/v1", Models: []string{"deepseek-v4-flash"}, WebSearch: &explicitTrue},
95 want: true,
96 },
97 {
98 name: "anthropic flash trailing slash",
99 entry: ProviderEntry{Kind: "anthropic", BaseURL: "https://opencode.ai/zen/go/", Model: "deepseek-v4-flash", WebSearch: &explicitTrue},
100 want: true,
101 },
102 {
103 name: "mixed anthropic catalog remains unverified",
104 entry: ProviderEntry{Kind: "anthropic", BaseURL: "https://opencode.ai/zen/go", Models: []string{"qwen3.7-plus", "deepseek-v4-flash"}, WebSearch: &explicitTrue},
105 want: false,
106 },
107 {
108 name: "pro remains unsupported",
109 entry: ProviderEntry{Kind: "responses", BaseURL: "https://opencode.ai/zen/go/v1", Model: "deepseek-v4-pro", WebSearch: &explicitTrue},
110 want: false,
111 },
112 {
113 name: "wrong responses path",
114 entry: ProviderEntry{Kind: "responses", BaseURL: "https://opencode.ai/zen/go", Model: "deepseek-v4-flash", WebSearch: &explicitTrue},
115 want: false,
116 },
117 {
118 name: "lookalike host",
119 entry: ProviderEntry{Kind: "anthropic", BaseURL: "https://opencode.ai.attacker.example/zen/go", Model: "deepseek-v4-flash", WebSearch: &explicitTrue},
120 want: false,
121 },
122 }
123 for _, tt := range tests {
124 t.Run(tt.name, func(t *testing.T) {
125 if got := HasServerWebSearchCapability(&tt.entry); got != tt.want {
126 t.Fatalf("HasServerWebSearchCapability(%+v) = %t, want %t", tt.entry, got, tt.want)
127 }
128 })
129 }
130 }
131
132 func TestWebSearchTOMLRoundTripPreservesExplicitOff(t *testing.T) {
133 explicitFalse := false
134 cfg := &Config{Providers: []ProviderEntry{{
135 Name: "deepseek-responses",
136 Kind: "responses",
137 BaseURL: "https://api.deepseek.com",
138 Model: "deepseek-v4-flash",
139 WebSearch: &explicitFalse,
140 }}}
141 rendered := RenderTOML(cfg)
142 if !strings.Contains(rendered, "web_search = false") {
143 t.Fatalf("rendered TOML lost explicit web_search=false:\n%s", rendered)
144 }
145
146 var roundTrip Config
147 if _, err := toml.Decode(rendered, &roundTrip); err != nil {
148 t.Fatalf("decode rendered TOML: %v", err)
149 }
150 entry, ok := roundTrip.Provider("deepseek-responses")
151 if !ok {
152 t.Fatal("round-tripped provider missing")
153 }
154 if entry.WebSearch == nil || *entry.WebSearch || EffectiveWebSearch(entry) {
155 t.Fatalf("round-tripped web search = pointer:%v effective:%t, want explicit false", entry.WebSearch, EffectiveWebSearch(entry))
156 }
157 }
158
159 func TestLegacyOfficialWebSearchOmissionDefaultsOnAcrossLoaders(t *testing.T) {
160 const legacy = `config_version = 1
161 default_model = "deepseek-responses/deepseek-v4-flash"
162
163 [[providers]]
164 name = "deepseek-responses"
165 kind = "responses"
166 base_url = "https://api.deepseek.com"
167 model = "deepseek-v4-flash"
168 `
169 home := t.TempDir()
170 workspace := t.TempDir()
171 t.Setenv("REASONIX_HOME", home)
172 path := filepath.Join(home, "config.toml")
173 if err := os.WriteFile(path, []byte(legacy), 0o600); err != nil {
174 t.Fatal(err)
175 }
176
177 runtimeConfig, err := LoadForRootReadOnly(workspace)
178 if err != nil {
179 t.Fatalf("runtime load: %v", err)
180 }
181 editConfig := LoadForEditWithoutCredentials(path)
182 for name, cfg := range map[string]*Config{"runtime": runtimeConfig, "edit": editConfig} {
183 entry, ok := cfg.Provider("deepseek-responses")
184 if !ok {
185 t.Fatalf("%s legacy provider missing", name)
186 }
187 if entry.WebSearch != nil || !EffectiveWebSearch(entry) {
188 t.Fatalf("%s legacy omitted web search = pointer:%v effective:%t, want nil/true", name, entry.WebSearch, EffectiveWebSearch(entry))
189 }
190 }
191 }
192
192 lines GO