| 1 | package config |
| 2 | |
| 3 | import ( |
| 4 | "os" |
| 5 | "path/filepath" |
| 6 | "reflect" |
| 7 | "strings" |
| 8 | "testing" |
| 9 | ) |
| 10 | |
| 11 | func TestDeepSeekEndpointContract(t *testing.T) { |
| 12 | preset, ok := CuratedProviderPreset("deepseek-anthropic") |
| 13 | if !ok { |
| 14 | t.Fatal("hidden DeepSeek preset missing") |
| 15 | } |
| 16 | catalog := CatalogForProviderPreset(preset) |
| 17 | want := map[string]string{ |
| 18 | "openai": "https://api.deepseek.com/v1/chat/completions", |
| 19 | "responses": "https://api.deepseek.com/responses", |
| 20 | "anthropic": "https://api.deepseek.com/anthropic/v1/messages", |
| 21 | } |
| 22 | for kind, requestURL := range want { |
| 23 | route, exists := catalog.Protocols[kind] |
| 24 | if !exists { |
| 25 | t.Fatalf("DeepSeek %s route missing", kind) |
| 26 | } |
| 27 | if got := ProviderRequestURL(kind, route.BaseURL); got != requestURL { |
| 28 | t.Fatalf("DeepSeek %s request URL = %q, want %q", kind, got, requestURL) |
| 29 | } |
| 30 | } |
| 31 | } |
| 32 | |
| 33 | func TestProviderEndpointMismatchIsConservative(t *testing.T) { |
| 34 | cases := []struct { |
| 35 | name string |
| 36 | entry ProviderEntry |
| 37 | mismatch bool |
| 38 | }{ |
| 39 | {"foreign standard suffix", ProviderEntry{Kind: "openai", RequestURL: "https://gateway.test/v1/messages"}, true}, |
| 40 | {"deepseek hybrid path", ProviderEntry{Name: "deepseek-anthropic", Kind: "openai", RequestURL: "https://api.deepseek.com/anthropic/v1/chat/completions"}, true}, |
| 41 | {"deepseek official chat", ProviderEntry{Name: "deepseek-anthropic", Kind: "openai", RequestURL: "https://api.deepseek.com/v1/chat/completions"}, false}, |
| 42 | {"mimo shared openai and responses root", ProviderEntry{Name: "mimo-api", Kind: "openai", BaseURL: "https://api.xiaomimimo.com/v1"}, false}, |
| 43 | {"deepseek accepted root alias", ProviderEntry{Name: "deepseek-anthropic", Kind: "openai", RequestURL: "https://api.deepseek.com/chat/completions"}, false}, |
| 44 | {"custom host", ProviderEntry{Name: "deepseek-anthropic", Kind: "openai", RequestURL: "https://gateway.test/anthropic/v1/chat/completions"}, false}, |
| 45 | {"query override", ProviderEntry{Name: "deepseek-anthropic", Kind: "openai", RequestURL: "https://api.deepseek.com/anthropic/v1/chat/completions?token=x"}, false}, |
| 46 | {"unknown path", ProviderEntry{Name: "deepseek-anthropic", Kind: "openai", RequestURL: "https://api.deepseek.com/custom/route"}, false}, |
| 47 | {"custom path in foreign namespace", ProviderEntry{Name: "deepseek-anthropic", Kind: "openai", RequestURL: "https://api.deepseek.com/anthropic/custom/chat/completions"}, false}, |
| 48 | } |
| 49 | for _, tc := range cases { |
| 50 | t.Run(tc.name, func(t *testing.T) { |
| 51 | got := ProviderEndpointMismatchForEntry(&tc.entry) |
| 52 | if (got != nil) != tc.mismatch { |
| 53 | t.Fatalf("mismatch = %+v, want %v", got, tc.mismatch) |
| 54 | } |
| 55 | if got != nil && tc.entry.Name == "deepseek-anthropic" && got.Recommended != "https://api.deepseek.com/v1/chat/completions" { |
| 56 | t.Fatalf("recommendation = %q", got.Recommended) |
| 57 | } |
| 58 | }) |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | func TestCatalogForProviderEntryUsesHiddenLegacyPreset(t *testing.T) { |
| 63 | id, catalog, ok := CatalogForProviderEntry(&ProviderEntry{Name: "deepseek-anthropic"}) |
| 64 | if !ok || id != "deepseek-anthropic" || catalog.BrandID != "deepseek" || catalog.Region != "global" || catalog.Product != "api" { |
| 65 | t.Fatalf("catalog identity = %q %+v %v", id, catalog, ok) |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | func TestRepairProviderEndpointContractUsesExactCatalogRoute(t *testing.T) { |
| 70 | legacyStateful := true |
| 71 | entry := ProviderEntry{ |
| 72 | Name: "deepseek-anthropic", DisplayName: "Deepseek2", PresetID: "deepseek-anthropic", |
| 73 | Kind: "responses", BaseURL: "https://api.deepseek.com", RequestURL: "https://api.deepseek.com/anthropic/v1/messages", |
| 74 | ChatURL: "https://stale.example/chat/completions", Models: []string{"deepseek-v4-flash"}, Default: "deepseek-v4-flash", |
| 75 | APIKeyEnv: "MY_DEEPSEEK_KEY", Headers: map[string]string{"X-Test": "keep"}, ExtraBody: map[string]any{"future": "keep"}, |
| 76 | ContextWindow: 1_000_000, ResponsesMode: "stateful", ResponsesStateful: &legacyStateful, |
| 77 | } |
| 78 | repair, changed := RepairProviderEndpointContract(&entry) |
| 79 | if !changed || repair == nil { |
| 80 | t.Fatal("expected exact DeepSeek route to be repaired") |
| 81 | } |
| 82 | if repair.ProviderName != "Deepseek2" || repair.FromProtocol != "responses" || repair.ToProtocol != "anthropic" || |
| 83 | repair.RequestURL != "https://api.deepseek.com/anthropic/v1/messages" { |
| 84 | t.Fatalf("repair = %+v", repair) |
| 85 | } |
| 86 | if entry.Kind != "anthropic" || entry.BaseURL != "https://api.deepseek.com/anthropic" || |
| 87 | entry.RequestURL != "" || entry.ChatURL != "" || entry.ResponsesMode != "" || entry.ResponsesStateful != nil { |
| 88 | t.Fatalf("repaired entry = %+v", entry) |
| 89 | } |
| 90 | if got := ProviderEffectiveRequestURL(&entry); got != "https://api.deepseek.com/anthropic/v1/messages" { |
| 91 | t.Fatalf("effective request URL = %q", got) |
| 92 | } |
| 93 | if entry.Name != "deepseek-anthropic" || entry.PresetID != "deepseek-anthropic" || |
| 94 | entry.APIKeyEnv != "MY_DEEPSEEK_KEY" || entry.ContextWindow != 1_000_000 || |
| 95 | !reflect.DeepEqual(entry.Models, []string{"deepseek-v4-flash"}) || |
| 96 | !reflect.DeepEqual(entry.Headers, map[string]string{"X-Test": "keep"}) || |
| 97 | !reflect.DeepEqual(entry.ExtraBody, map[string]any{"future": "keep"}) { |
| 98 | t.Fatalf("repair discarded user-owned fields: %+v", entry) |
| 99 | } |
| 100 | if second, changed := RepairProviderEndpointContract(&entry); changed || second != nil { |
| 101 | t.Fatalf("repair is not idempotent: %+v changed=%v", second, changed) |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | func TestRepairProviderEndpointContractAppliesTargetRouteOptions(t *testing.T) { |
| 106 | tests := []struct { |
| 107 | name string |
| 108 | entry ProviderEntry |
| 109 | wantKind string |
| 110 | wantBaseURL string |
| 111 | wantMode string |
| 112 | wantAuth bool |
| 113 | wantRequest string |
| 114 | }{ |
| 115 | { |
| 116 | name: "responses", |
| 117 | entry: ProviderEntry{Name: "deepseek-anthropic", Kind: "anthropic", BaseURL: "https://api.deepseek.com/anthropic", |
| 118 | RequestURL: "https://api.deepseek.com/responses", AuthHeader: true}, |
| 119 | wantKind: "responses", wantBaseURL: "https://api.deepseek.com", wantMode: "stateless", |
| 120 | wantRequest: "https://api.deepseek.com/responses", |
| 121 | }, |
| 122 | { |
| 123 | name: "openai", |
| 124 | entry: ProviderEntry{Name: "deepseek-anthropic", Kind: "anthropic", BaseURL: "https://api.deepseek.com/anthropic", |
| 125 | RequestURL: "https://api.deepseek.com/v1/chat/completions", AuthHeader: true, ResponsesMode: "stateful"}, |
| 126 | wantKind: "openai", wantBaseURL: "https://api.deepseek.com/v1", |
| 127 | wantRequest: "https://api.deepseek.com/v1/chat/completions", |
| 128 | }, |
| 129 | } |
| 130 | for _, tc := range tests { |
| 131 | t.Run(tc.name, func(t *testing.T) { |
| 132 | if _, changed := RepairProviderEndpointContract(&tc.entry); !changed { |
| 133 | t.Fatal("expected repair") |
| 134 | } |
| 135 | if tc.entry.Kind != tc.wantKind || tc.entry.BaseURL != tc.wantBaseURL || |
| 136 | tc.entry.ResponsesMode != tc.wantMode || tc.entry.AuthHeader != tc.wantAuth || |
| 137 | ProviderEffectiveRequestURL(&tc.entry) != tc.wantRequest { |
| 138 | t.Fatalf("entry = %+v, request = %q", tc.entry, ProviderEffectiveRequestURL(&tc.entry)) |
| 139 | } |
| 140 | }) |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | func TestRepairProviderEndpointContractPreservesUnprovenRoutes(t *testing.T) { |
| 145 | tests := []ProviderEntry{ |
| 146 | {Name: "deepseek-anthropic", Kind: "responses", RequestURL: "https://gateway.test/v1/messages"}, |
| 147 | {Name: "deepseek-anthropic", Kind: "responses", RequestURL: "https://api.deepseek.com/anthropic/v1/messages?route=custom"}, |
| 148 | {Name: "deepseek-anthropic", Kind: "responses", RequestURL: "https://user@api.deepseek.com/anthropic/v1/messages"}, |
| 149 | {Name: "deepseek-anthropic", Kind: "responses", RequestURL: "https://api.deepseek.com/custom/messages"}, |
| 150 | {Name: "custom", Kind: "responses", RequestURL: "https://api.deepseek.com/anthropic/v1/messages"}, |
| 151 | } |
| 152 | for _, entry := range tests { |
| 153 | before := entry |
| 154 | if repair, changed := RepairProviderEndpointContract(&entry); changed || repair != nil { |
| 155 | t.Fatalf("unexpected repair for %+v: %+v", before, repair) |
| 156 | } |
| 157 | if !reflect.DeepEqual(entry, before) { |
| 158 | t.Fatalf("unproven route changed: before=%+v after=%+v", before, entry) |
| 159 | } |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | func TestProjectProviderEndpointRepairIsRuntimeOnly(t *testing.T) { |
| 164 | t.Setenv("REASONIX_HOME", t.TempDir()) |
| 165 | root := t.TempDir() |
| 166 | path := filepath.Join(root, "reasonix.toml") |
| 167 | raw := `[[providers]] |
| 168 | name = "deepseek-anthropic" |
| 169 | preset_id = "deepseek-anthropic" |
| 170 | kind = "responses" |
| 171 | base_url = "https://api.deepseek.com" |
| 172 | request_url = "https://api.deepseek.com/anthropic/v1/messages" |
| 173 | models = ["deepseek-v4-flash"] |
| 174 | default = "deepseek-v4-flash" |
| 175 | ` |
| 176 | if err := os.WriteFile(path, []byte(raw), 0o644); err != nil { |
| 177 | t.Fatal(err) |
| 178 | } |
| 179 | cfg, err := LoadForRootWithoutCredentialsReadOnly(root) |
| 180 | if err != nil { |
| 181 | t.Fatal(err) |
| 182 | } |
| 183 | p, ok := cfg.Provider("deepseek-anthropic") |
| 184 | if !ok || p.Kind != "anthropic" || p.BaseURL != "https://api.deepseek.com/anthropic" || |
| 185 | ProviderEffectiveRequestURL(p) != "https://api.deepseek.com/anthropic/v1/messages" { |
| 186 | t.Fatalf("runtime provider = %+v found=%v", p, ok) |
| 187 | } |
| 188 | after, err := os.ReadFile(path) |
| 189 | if err != nil { |
| 190 | t.Fatal(err) |
| 191 | } |
| 192 | if string(after) != raw { |
| 193 | t.Fatalf("project config was rewritten:\n%s", after) |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | func TestRewriteProviderEndpointContractsPreservesInlineUnknownFields(t *testing.T) { |
| 198 | raw := `config_version = 10 # keep |
| 199 | providers = [{name="deepseek-anthropic", preset_id="deepseek-anthropic", kind="anthropic", base_url="https://api.deepseek.com/anthropic", request_url="https://api.deepseek.com/responses", responses_stateful=true, api_key_env="MY_KEY", models=["deepseek-v4-flash"], future={value="keep"}}] # keep inline |
| 200 | ` |
| 201 | next, repairs, err := rewriteProviderEndpointContracts(raw) |
| 202 | if err != nil { |
| 203 | t.Fatal(err) |
| 204 | } |
| 205 | if len(repairs) != 1 || repairs[0].FromProtocol != "anthropic" || repairs[0].ToProtocol != "responses" { |
| 206 | t.Fatalf("repairs = %+v", repairs) |
| 207 | } |
| 208 | for _, want := range []string{ |
| 209 | "config_version = 10 # keep", |
| 210 | `kind="responses"`, |
| 211 | `base_url="https://api.deepseek.com"`, |
| 212 | `request_url=""`, |
| 213 | `responses_mode = "stateless"`, |
| 214 | `api_key_env="MY_KEY"`, |
| 215 | `future={value="keep"}`, |
| 216 | "# keep inline", |
| 217 | } { |
| 218 | if !strings.Contains(next, want) { |
| 219 | t.Fatalf("rewritten inline config missing %q:\n%s", want, next) |
| 220 | } |
| 221 | } |
| 222 | if strings.Contains(next, "responses_stateful") { |
| 223 | t.Fatalf("legacy responses state survived:\n%s", next) |
| 224 | } |
| 225 | path := filepath.Join(t.TempDir(), "config.toml") |
| 226 | if err := os.WriteFile(path, []byte(next), 0o600); err != nil { |
| 227 | t.Fatal(err) |
| 228 | } |
| 229 | loaded := LoadForEdit(path) |
| 230 | p, ok := loaded.Provider("deepseek-anthropic") |
| 231 | if !ok || p.Kind != "responses" || p.ResponsesMode != "stateless" || |
| 232 | ProviderEffectiveRequestURL(p) != "https://api.deepseek.com/responses" { |
| 233 | t.Fatalf("reloaded inline provider = %+v found=%v", p, ok) |
| 234 | } |
| 235 | } |
| 236 |