| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "os" |
| 5 | "path/filepath" |
| 6 | "strings" |
| 7 | "testing" |
| 8 | |
| 9 | "reasonix/internal/config" |
| 10 | ) |
| 11 | |
| 12 | func TestAddOfficialProviderAccessUsesDesktopLanguagePricing(t *testing.T) { |
| 13 | // Desktop language is display-only; newly installed official DeepSeek |
| 14 | // templates freeze the default USD list-price table (billing_currency). |
| 15 | isolateDesktopUserDirs(t) |
| 16 | if err := os.MkdirAll(filepath.Dir(config.UserConfigPath()), 0o755); err != nil { |
| 17 | t.Fatalf("mkdir config dir: %v", err) |
| 18 | } |
| 19 | if err := os.WriteFile(config.UserConfigPath(), []byte(` |
| 20 | [desktop] |
| 21 | language = "zh" |
| 22 | `), 0o644); err != nil { |
| 23 | t.Fatalf("write config: %v", err) |
| 24 | } |
| 25 | |
| 26 | if _, err := NewApp().AddOfficialProviderAccess("deepseek", ""); err != nil { |
| 27 | t.Fatalf("AddOfficialProviderAccess: %v", err) |
| 28 | } |
| 29 | cfg := config.LoadForEdit(config.UserConfigPath()) |
| 30 | p, ok := cfg.Provider("deepseek") |
| 31 | if !ok { |
| 32 | t.Fatal("deepseek provider not saved") |
| 33 | } |
| 34 | flash := p.Prices["deepseek-v4-flash"] |
| 35 | pro := p.Prices["deepseek-v4-pro"] |
| 36 | if flash == nil || flash.Output != 1.2 || flash.Currency != "$" { |
| 37 | t.Fatalf("flash price = %+v, want frozen USD official table", flash) |
| 38 | } |
| 39 | if pro == nil || pro.Output != 3.96 || pro.Currency != "$" { |
| 40 | t.Fatalf("pro price = %+v, want frozen USD official table", pro) |
| 41 | } |
| 42 | if got := cfg.ResolveDisplayCurrency(); got != "" { |
| 43 | t.Fatalf("display currency = %q, want unresolved auto", got) |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | func TestAddOfficialProviderAccessPreservesExistingOfficialCustomization(t *testing.T) { |
| 48 | isolateDesktopUserDirs(t) |
| 49 | if err := os.MkdirAll(filepath.Dir(config.UserConfigPath()), 0o755); err != nil { |
| 50 | t.Fatalf("mkdir config dir: %v", err) |
| 51 | } |
| 52 | if err := os.WriteFile(config.UserConfigPath(), []byte(` |
| 53 | [[providers]] |
| 54 | name = "deepseek" |
| 55 | kind = "openai" |
| 56 | base_url = "https://api.deepseek.com/v1" |
| 57 | models = ["deepseek-v4-flash"] |
| 58 | default = "deepseek-v4-flash" |
| 59 | api_key_env = "MY_DEEPSEEK_KEY" |
| 60 | headers = { X-Route = "official-custom" } |
| 61 | `), 0o644); err != nil { |
| 62 | t.Fatalf("write config: %v", err) |
| 63 | } |
| 64 | |
| 65 | if _, err := NewApp().AddOfficialProviderAccess("deepseek", ""); err != nil { |
| 66 | t.Fatalf("AddOfficialProviderAccess: %v", err) |
| 67 | } |
| 68 | cfg := config.LoadForEditWithoutCredentials(config.UserConfigPath()) |
| 69 | p, ok := cfg.Provider("deepseek") |
| 70 | if !ok { |
| 71 | t.Fatal("deepseek provider not saved") |
| 72 | } |
| 73 | if p.Kind != "openai" || p.BaseURL != "https://api.deepseek.com/v1" || p.APIKeyEnv != "MY_DEEPSEEK_KEY" || p.Headers["X-Route"] != "official-custom" { |
| 74 | t.Fatalf("existing official customization was overwritten: %+v", p) |
| 75 | } |
| 76 | if !providerAccessSet(cfg.Desktop.ProviderAccess)["deepseek"] { |
| 77 | t.Fatalf("provider_access missing preserved DeepSeek provider: %+v", cfg.Desktop.ProviderAccess) |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | func TestAddOfficialProviderAccessRepairsCatalogWithoutResettingOfficialTransport(t *testing.T) { |
| 82 | isolateDesktopUserDirs(t) |
| 83 | if err := os.MkdirAll(filepath.Dir(config.UserConfigPath()), 0o755); err != nil { |
| 84 | t.Fatalf("mkdir config dir: %v", err) |
| 85 | } |
| 86 | if err := os.WriteFile(config.UserConfigPath(), []byte(` |
| 87 | [[providers]] |
| 88 | name = "deepseek" |
| 89 | kind = "openai" |
| 90 | base_url = "https://api.deepseek.com/v1" |
| 91 | api_key_env = "MY_DEEPSEEK_KEY" |
| 92 | headers = { X-Route = "official-custom" } |
| 93 | `), 0o644); err != nil { |
| 94 | t.Fatalf("write config: %v", err) |
| 95 | } |
| 96 | |
| 97 | if _, err := NewApp().AddOfficialProviderAccess("deepseek", "test-key"); err != nil { |
| 98 | t.Fatalf("AddOfficialProviderAccess: %v", err) |
| 99 | } |
| 100 | cfg := config.LoadForEditWithoutCredentials(config.UserConfigPath()) |
| 101 | p, ok := cfg.Provider("deepseek") |
| 102 | if !ok { |
| 103 | t.Fatal("deepseek provider not saved") |
| 104 | } |
| 105 | if p.Kind != "openai" || p.BaseURL != "https://api.deepseek.com/v1" || p.APIKeyEnv == "MY_DEEPSEEK_KEY" || p.Headers["X-Route"] != "official-custom" { |
| 106 | t.Fatalf("official transport customization was overwritten: %+v", p) |
| 107 | } |
| 108 | if got := p.ModelList(); len(got) != 3 || got[0] != "deepseek-v4-flash" || got[1] != "deepseek-v4-pro" || got[2] != "deepseek-v4-flash-vision-exp" { |
| 109 | t.Fatalf("repaired model catalog = %v, want official Flash/Pro/vision models", got) |
| 110 | } |
| 111 | if !config.CredentialStored(p.APIKeyEnv) || config.CredentialStored("MY_DEEPSEEK_KEY") || config.CredentialStored("DEEPSEEK_API_KEY") { |
| 112 | t.Fatal("credential was not saved exclusively under the new connection reference") |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | func TestAddOfficialProviderAccessRetargetsLegacyDeepSeekReferences(t *testing.T) { |
| 117 | isolateDesktopUserDirs(t) |
| 118 | if err := os.MkdirAll(filepath.Dir(config.UserConfigPath()), 0o755); err != nil { |
| 119 | t.Fatalf("mkdir config dir: %v", err) |
| 120 | } |
| 121 | if err := os.WriteFile(config.UserConfigPath(), []byte(` |
| 122 | default_model = "deepseek-flash" |
| 123 | |
| 124 | [agent] |
| 125 | planner_model = "deepseek-pro" |
| 126 | subagent_model = "deepseek-flash/custom-flash" |
| 127 | subagent_models = { vision = "deepseek-pro/custom-pro", keep = "other/model" } |
| 128 | |
| 129 | [[providers]] |
| 130 | name = "deepseek-flash" |
| 131 | kind = "openai" |
| 132 | base_url = "https://api.deepseek.com" |
| 133 | api_key_env = "DEEPSEEK_API_KEY" |
| 134 | `), 0o644); err != nil { |
| 135 | t.Fatalf("write config: %v", err) |
| 136 | } |
| 137 | |
| 138 | if _, err := NewApp().AddOfficialProviderAccess("deepseek", ""); err != nil { |
| 139 | t.Fatalf("AddOfficialProviderAccess: %v", err) |
| 140 | } |
| 141 | cfg := config.LoadForEditWithoutCredentials(config.UserConfigPath()) |
| 142 | if cfg.DefaultModel != "deepseek/deepseek-v4-flash" || cfg.Agent.PlannerModel != "deepseek/deepseek-v4-pro" || |
| 143 | cfg.Agent.SubagentModel != "deepseek/custom-flash" || cfg.Agent.SubagentModels["vision"] != "deepseek/custom-pro" || |
| 144 | cfg.Agent.SubagentModels["keep"] != "other/model" { |
| 145 | t.Fatalf("legacy DeepSeek references were not retargeted: default=%q planner=%q subagent=%q skills=%v", |
| 146 | cfg.DefaultModel, cfg.Agent.PlannerModel, cfg.Agent.SubagentModel, cfg.Agent.SubagentModels) |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | func TestAddOfficialProviderAccessRejectsSameNameCustomEndpointBeforeSavingKey(t *testing.T) { |
| 151 | isolateDesktopUserDirs(t) |
| 152 | if err := os.MkdirAll(filepath.Dir(config.UserConfigPath()), 0o755); err != nil { |
| 153 | t.Fatalf("mkdir config dir: %v", err) |
| 154 | } |
| 155 | if err := os.WriteFile(config.UserConfigPath(), []byte(` |
| 156 | [[providers]] |
| 157 | name = "deepseek" |
| 158 | kind = "openai" |
| 159 | base_url = "https://proxy.example.invalid/v1" |
| 160 | models = ["deepseek-v4-flash"] |
| 161 | default = "deepseek-v4-flash" |
| 162 | api_key_env = "DEEPSEEK_API_KEY" |
| 163 | `), 0o644); err != nil { |
| 164 | t.Fatalf("write config: %v", err) |
| 165 | } |
| 166 | |
| 167 | _, err := NewApp().AddOfficialProviderAccess("deepseek", "test-key") |
| 168 | if err == nil || !strings.Contains(err.Error(), "already belongs to a custom endpoint") { |
| 169 | t.Fatalf("AddOfficialProviderAccess error = %v, want same-name custom endpoint rejection", err) |
| 170 | } |
| 171 | if data, readErr := os.ReadFile(config.UserCredentialsPath()); readErr == nil && strings.Contains(string(data), "DEEPSEEK_API_KEY") { |
| 172 | t.Fatalf("credential was saved before provider name conflict rejection:\n%s", data) |
| 173 | } |
| 174 | cfg := config.LoadForEditWithoutCredentials(config.UserConfigPath()) |
| 175 | p, ok := cfg.Provider("deepseek") |
| 176 | if !ok || p.BaseURL != "https://proxy.example.invalid/v1" { |
| 177 | t.Fatalf("same-name custom provider changed after rejection: %+v", p) |
| 178 | } |
| 179 | } |
| 180 |