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