返回 DeepSeek-Reasonix
deepseek_chat_default_upgrade_test.go
根目录 / internal / config / deepseek_chat_default_upgrade_test.go
1 package config
2
3 import (
4 "os"
5 "path/filepath"
6 "strings"
7 "sync"
8 "testing"
9
10 "github.com/BurntSushi/toml"
11 )
12
13 func TestDeepSeekChatDefaultStartupUpgradePreservesData(t *testing.T) {
14 path := filepath.Join(t.TempDir(), "config.toml")
15 raw := `# user comment
16 config_version = 7 # schema
17 future_field = "keep me"
18 default_model = "deepseek/deepseek-v4-pro"
19 [[providers]]
20 name = "deepseek"
21 kind = "anthropic" # transport
22 base_url = "https://api.deepseek.com/anthropic"
23 models = ["deepseek-v4-flash", "deepseek-v4-pro"]
24 default = "deepseek-v4-pro"
25 api_key_env = "DEEPSEEK_API_KEY"
26 web_search = false
27 default_effort = "max"
28 future_provider_field = { value = "untouched" }
29 [providers.prices.deepseek-v4-pro]
30 input = 123
31 [desktop]
32 future_desktop_field = true
33 `
34 if err := os.WriteFile(path, []byte(raw), 0600); err != nil {
35 t.Fatal(err)
36 }
37 changed, err := ApplyUserConfigUpgradesOnStartup(path)
38 if err != nil || !changed {
39 t.Fatalf("upgrade=%v err=%v", changed, err)
40 }
41 got, _ := os.ReadFile(path)
42 want := strings.ReplaceAll(raw, `config_version = 7`, `config_version = 10`)
43 want = strings.ReplaceAll(want, `kind = "anthropic"`, `kind = "openai"`)
44 want = strings.ReplaceAll(want, `base_url = "https://api.deepseek.com/anthropic"`, `base_url = "https://api.deepseek.com"`)
45 if string(got) != want {
46 t.Fatalf("unexpected rewrite:\n%s", got)
47 }
48 // After the one-time upgrade, an explicit Messages choice must survive.
49 manual := strings.ReplaceAll(want, `kind = "openai"`, `kind = "anthropic"`)
50 manual = strings.ReplaceAll(manual, `base_url = "https://api.deepseek.com"`, `base_url = "https://api.deepseek.com/anthropic"`)
51 if err := os.WriteFile(path, []byte(manual), 0600); err != nil {
52 t.Fatal(err)
53 }
54 if changed, err := ApplyUserConfigUpgradesOnStartup(path); err != nil || changed {
55 t.Fatalf("overrode post-upgrade choice: %v %v", changed, err)
56 }
57 got, _ = os.ReadFile(path)
58 if string(got) != manual {
59 t.Fatal("post-upgrade config changed")
60 }
61 }
62
63 func TestDeepSeekChatDefaultMigrationScope(t *testing.T) {
64 base := ProviderEntry{Name: "deepseek-flash", Kind: "anthropic", BaseURL: deepSeekAnthropicBaseURL, Model: "deepseek-v4-flash", APIKeyEnv: "DEEPSEEK_API_KEY"}
65 for _, tc := range []struct {
66 name string
67 edit func(*ProviderEntry)
68 want bool
69 }{
70 {"stock", func(*ProviderEntry) {}, true},
71 {"key and search preference", func(p *ProviderEntry) { p.APIKeyEnv = "MY_KEY"; p.WebSearch = boolPointer(false) }, true},
72 {"explicit preset", func(p *ProviderEntry) { p.PresetID = "deepseek-anthropic" }, false},
73 {"separate name", func(p *ProviderEntry) { p.Name = "deepseek-anthropic" }, false},
74 {"responses choice", func(p *ProviderEntry) { p.Kind = "responses"; p.BaseURL = "https://api.deepseek.com" }, false},
75 {"proxy", func(p *ProviderEntry) { p.BaseURL = "https://relay.example/anthropic" }, false},
76 {"request override", func(p *ProviderEntry) { p.RequestURL = "https://api.deepseek.com/anthropic/v1/messages" }, false},
77 {"headers", func(p *ProviderEntry) { p.Headers = map[string]string{"X-Route": "custom"} }, false},
78 {"body override", func(p *ProviderEntry) { p.ExtraBody = map[string]any{"thinking": false} }, false},
79 {"unknown model", func(p *ProviderEntry) { p.Model = "future-model" }, false},
80 } {
81 t.Run(tc.name, func(t *testing.T) {
82 p := base
83 tc.edit(&p)
84 if got := isLegacyDeepSeekMessagesDefault(&p); got != tc.want {
85 t.Fatalf("eligible=%v want %v", got, tc.want)
86 }
87 })
88 }
89 }
90
91 func TestDeepSeekChatDefaultUpgradeInlineAndConcurrent(t *testing.T) {
92 path := filepath.Join(t.TempDir(), "config.toml")
93 raw := `config_version = 7
94 providers = [{name="deepseek-pro",kind="anthropic",base_url="https://api.deepseek.com/anthropic",model="deepseek-v4-pro",api_key_env="DEEPSEEK_API_KEY",web_search=false,future="kept"}]
95 `
96 if err := os.WriteFile(path, []byte(raw), 0600); err != nil {
97 t.Fatal(err)
98 }
99 var wg sync.WaitGroup
100 for range 4 {
101 wg.Go(func() {
102 if _, err := ApplyUserConfigUpgradesOnStartup(path); err != nil {
103 t.Error(err)
104 }
105 })
106 }
107 wg.Wait()
108 got, _ := os.ReadFile(path)
109 var parsed Config
110 if _, err := toml.Decode(string(got), &parsed); err != nil {
111 t.Fatal(err)
112 }
113 if parsed.ConfigVersion != 10 || len(parsed.Providers) != 1 || parsed.Providers[0].Kind != "openai" || *parsed.Providers[0].WebSearch {
114 t.Fatalf("bad migration: %s", got)
115 }
116 if !strings.Contains(string(got), `future="kept"`) {
117 t.Fatal("lost unknown field")
118 }
119 }
120
121 func TestDeepSeekChatDefaultUpgradeFutureConfigUntouched(t *testing.T) {
122 path := filepath.Join(t.TempDir(), "config.toml")
123 raw := `config_version = 999
124 providers = [{name="deepseek-flash",kind="anthropic",base_url="https://api.deepseek.com/anthropic",model="deepseek-v4-flash",api_key_env="DEEPSEEK_API_KEY"}]
125 `
126 if err := os.WriteFile(path, []byte(raw), 0600); err != nil {
127 t.Fatal(err)
128 }
129 if changed, err := ApplyUserConfigUpgradesOnStartup(path); err != nil || changed {
130 t.Fatalf("future changed: %v %v", changed, err)
131 }
132 got, _ := os.ReadFile(path)
133 if string(got) != raw {
134 t.Fatal("future config rewritten")
135 }
136 }
137
138 func TestOldAutomaticProtocolMigrationDoesNotUndoChatDefault(t *testing.T) {
139 raw := `config_version = 8
140 [[providers]]
141 name = "deepseek-flash"
142 kind = "openai"
143 base_url = "https://api.deepseek.com"
144 model = "deepseek-v4-flash"
145 api_key_env = "DEEPSEEK_API_KEY"
146 `
147 next, changed, err := rewriteLegacyDeepSeekProtocol(raw, "", true)
148 if err != nil || changed || next != raw {
149 t.Fatalf("old migration undid v8 default: changed=%v err=%v", changed, err)
150 }
151 }
152
152 lines GO