返回 DeepSeek-Reasonix
deepseek_official_chat_upgrade_test.go
根目录 / internal / config / deepseek_official_chat_upgrade_test.go
1 package config
2
3 import (
4 "fmt"
5 "os"
6 "path/filepath"
7 "reflect"
8 "runtime"
9 "strings"
10 "testing"
11
12 "github.com/BurntSushi/toml"
13 )
14
15 func TestOfficialDeepSeekV9MigrationAndManualChoice(t *testing.T) {
16 for _, kind := range []string{"anthropic", "responses"} {
17 for _, inline := range []bool{false, true} {
18 t.Run(fmt.Sprintf("%s/inline=%v", kind, inline), func(t *testing.T) {
19 base, endpoint := "https://api.deepseek.com", "https://api.deepseek.com/responses"
20 if kind == "anthropic" {
21 base, endpoint = deepSeekAnthropicBaseURL, deepSeekAnthropicBaseURL+"/v1/messages"
22 }
23 fields := []string{`name="Deepseek2"`, `preset_id="deepseek-anthropic"`, `kind="` + kind + `"`, `base_url="` + base + `"`, `request_url="` + endpoint + `"`, `api_key_env="MY_KEY"`, `models=["DeepSeek-V4.1-Flash-Expires-On-0910","custom-ID"]`, `default="custom-ID"`, `headers={X-Test="keep"}`, `future={value="keep"}`}
24 raw := "config_version = 8 # preserve\n# comment\n[[providers]]\n" + strings.Join(fields, "\n") + "\n"
25 if inline {
26 raw = "config_version = 8 # preserve\n# comment\nproviders=[{" + strings.Join(fields, ",") + "}]\n"
27 }
28 path := filepath.Join(t.TempDir(), "config.toml")
29 if err := os.WriteFile(path, []byte(raw), 0600); err != nil {
30 t.Fatal(err)
31 }
32 if changed, err := ApplyUserConfigUpgradesOnStartup(path); err != nil || !changed {
33 t.Fatalf("migration: %v %v", changed, err)
34 }
35 got, err := os.ReadFile(path)
36 if err != nil {
37 t.Fatal(err)
38 }
39 want := strings.Replace(raw, "config_version = 8", fmt.Sprintf("config_version = %d", Default().ConfigVersion), 1)
40 want = strings.Replace(want, `kind="`+kind+`"`, `kind="openai"`, 1)
41 want = strings.Replace(want, `base_url="`+base+`"`, `base_url="https://api.deepseek.com"`, 1)
42 want = strings.Replace(want, `request_url="`+endpoint+`"`, `request_url=""`, 1)
43 want = strings.Replace(want, `models=["DeepSeek-V4.1-Flash-Expires-On-0910","custom-ID"]`, `models=["DeepSeek-V4.1-Flash-Expires-On-0910", "custom-ID", "deepseek-flash"]`, 1)
44 var actualFields, expectedFields map[string]any
45 if _, err := toml.Decode(string(got), &actualFields); err != nil {
46 t.Fatal(err)
47 }
48 if _, err := toml.Decode(want, &expectedFields); err != nil {
49 t.Fatal(err)
50 }
51 if !reflect.DeepEqual(deepSeekCatalogDocumentValue(actualFields), deepSeekCatalogDocumentValue(expectedFields)) ||
52 !strings.Contains(string(got), "# preserve") || !strings.Contains(string(got), "# comment") {
53 t.Fatalf("unexpected edit:\n%s\nwant:\n%s", got, want)
54 }
55 var c Config
56 if _, err := toml.Decode(string(got), &c); err != nil {
57 t.Fatal(err)
58 }
59 if c.Providers[0].Default != "custom-ID" || c.Providers[0].Models[0] != "DeepSeek-V4.1-Flash-Expires-On-0910" {
60 t.Fatal("model identity changed")
61 }
62 loaded := LoadForEdit(path)
63 p, ok := loaded.Provider("Deepseek2")
64 if !ok || p.Kind != "openai" || p.RequestURL != "" {
65 t.Fatal("preset identity restored the old protocol on load")
66 }
67 // Clearing the standard override is what keeps the account visible
68 // to IsOfficialDeepSeekSearchEndpoint.
69 if !EffectiveIndependentWebSearch(p) {
70 t.Fatal("migration disabled independent web search")
71 }
72 // Persist through the ordinary writer, then restart twice.
73 c.Providers[0].Kind, c.Providers[0].BaseURL, c.Providers[0].RequestURL = kind, base, endpoint
74 if inline {
75 // Keep this fixture inline, as a user editing TOML would.
76 if err := os.WriteFile(path, []byte(strings.Replace(raw, "config_version = 8", fmt.Sprintf("config_version = %d", Default().ConfigVersion), 1)), 0600); err != nil {
77 t.Fatal(err)
78 }
79 } else if err := c.SaveTo(path); err != nil {
80 t.Fatal(err)
81 }
82 before, _ := os.ReadFile(path)
83 for range 2 {
84 if changed, err := ApplyUserConfigUpgradesOnStartup(path); err != nil || changed {
85 t.Fatalf("manual choice reset: %v %v", changed, err)
86 }
87 }
88 after, _ := os.ReadFile(path)
89 if string(after) != string(before) {
90 t.Fatal("changed after restart")
91 }
92 })
93 }
94 }
95 }
96
97 func TestOfficialDeepSeekV9EndpointBoundary(t *testing.T) {
98 for _, endpoint := range []string{"https://relay.example/anthropic", "https://api.deepseek.com/custom/messages", "https://api.deepseek.com/anthropic/v1/messages?route=custom", "https://api.deepseek.com.evil.test/anthropic", "http://api.deepseek.com/anthropic"} {
99 p := ProviderEntry{Kind: "anthropic", BaseURL: deepSeekAnthropicBaseURL, RequestURL: endpoint}
100 if isOfficialDeepSeekChatUpgrade(&p) {
101 t.Errorf("accepted %s", endpoint)
102 }
103 }
104 }
105
106 func TestCurrentConfigRepairsExactProviderEndpointContract(t *testing.T) {
107 raw := fmt.Sprintf(`config_version = %d # preserve current version
108 # preserve comment
109 [[providers]]
110 name = "deepseek-anthropic"
111 display_name = "Deepseek2"
112 preset_id = "deepseek-anthropic"
113 kind = "responses"
114 base_url = "https://api.deepseek.com"
115 request_url = "https://api.deepseek.com/anthropic/v1/messages"
116 chat_url = "https://stale.example/chat/completions"
117 api_key_env = "MY_KEY"
118 models = ["deepseek-v4-flash"]
119 default = "deepseek-v4-flash"
120 responses_mode = "stateful" # preserve mode comment
121 responses_stateful = true # preserve legacy comment
122 future = { value = "keep" }
123 `, Default().ConfigVersion)
124 path := filepath.Join(t.TempDir(), "config.toml")
125 if err := os.WriteFile(path, []byte(raw), 0o640); err != nil {
126 t.Fatal(err)
127 }
128 if changed, err := ApplyUserConfigUpgradesOnStartup(path); err != nil || !changed {
129 t.Fatalf("repair: changed=%v err=%v", changed, err)
130 }
131 got, err := os.ReadFile(path)
132 if err != nil {
133 t.Fatal(err)
134 }
135 text := string(got)
136 for _, want := range []string{
137 "# preserve current version",
138 "# preserve comment",
139 `kind = "anthropic"`,
140 `base_url = "https://api.deepseek.com/anthropic"`,
141 `request_url = ""`,
142 `chat_url = ""`,
143 `api_key_env = "MY_KEY"`,
144 `future = { value = "keep" }`,
145 "# preserve mode comment",
146 "# preserve legacy comment",
147 } {
148 if !strings.Contains(text, want) {
149 t.Fatalf("repaired config missing %q:\n%s", want, text)
150 }
151 }
152 if strings.Contains(text, "responses_mode") || strings.Contains(text, "responses_stateful") {
153 t.Fatalf("responses-only fields survived repair:\n%s", text)
154 }
155 info, err := os.Stat(path)
156 if err != nil {
157 t.Fatal(err)
158 }
159 if runtime.GOOS != "windows" && info.Mode().Perm() != 0o640 {
160 t.Fatalf("config mode = %o, want 640", info.Mode().Perm())
161 }
162 loaded := LoadForEdit(path)
163 p, ok := loaded.Provider("deepseek-anthropic")
164 if !ok || p.Kind != "anthropic" || ProviderEffectiveRequestURL(p) != "https://api.deepseek.com/anthropic/v1/messages" ||
165 p.APIKeyEnv != "MY_KEY" || p.DefaultModel() != "deepseek-v4-flash" {
166 t.Fatalf("reloaded provider = %+v found=%v", p, ok)
167 }
168 if changed, err := ApplyUserConfigUpgradesOnStartup(path); err != nil || changed {
169 t.Fatalf("second startup rewrote config: changed=%v err=%v", changed, err)
170 }
171 }
172
173 func TestCurrentConfigRepairAddsMissingOfficialBaseURL(t *testing.T) {
174 for _, inline := range []bool{false, true} {
175 t.Run(fmt.Sprintf("inline=%v", inline), func(t *testing.T) {
176 provider := `[[providers]]
177 name = "deepseek-anthropic"
178 preset_id = "deepseek-anthropic"
179 kind = "responses"
180 request_url = "https://api.deepseek.com/anthropic/v1/messages"
181 `
182 if inline {
183 provider = `providers = [{ name = "deepseek-anthropic", preset_id = "deepseek-anthropic", kind = "responses", request_url = "https://api.deepseek.com/anthropic/v1/messages" }]
184 `
185 }
186 raw := fmt.Sprintf("config_version = %d\n%s", Default().ConfigVersion, provider)
187 path := filepath.Join(t.TempDir(), "config.toml")
188 if err := os.WriteFile(path, []byte(raw), 0o600); err != nil {
189 t.Fatal(err)
190 }
191 if changed, err := ApplyUserConfigUpgradesOnStartup(path); err != nil || !changed {
192 t.Fatalf("repair: changed=%v err=%v", changed, err)
193 }
194 got, err := os.ReadFile(path)
195 if err != nil {
196 t.Fatal(err)
197 }
198 if !strings.Contains(string(got), `kind = "anthropic"`) ||
199 !strings.Contains(string(got), `base_url = "https://api.deepseek.com/anthropic"`) {
200 t.Fatalf("missing repaired protocol fields:\n%s", got)
201 }
202 loaded := LoadForEdit(path)
203 entry, ok := loaded.Provider("deepseek-anthropic")
204 if !ok || entry.Kind != "anthropic" ||
205 ProviderEffectiveRequestURL(entry) != "https://api.deepseek.com/anthropic/v1/messages" {
206 t.Fatalf("reloaded provider = %+v found=%v", entry, ok)
207 }
208 })
209 }
210 }
211
211 lines GO