| 1 | package config |
| 2 | |
| 3 | import ( |
| 4 | "os" |
| 5 | "path/filepath" |
| 6 | "reflect" |
| 7 | "testing" |
| 8 | |
| 9 | "reasonix/internal/provider" |
| 10 | ) |
| 11 | |
| 12 | func TestDeepSeekCurrentCatalogSurvivesSaveAndReload(t *testing.T) { |
| 13 | t.Setenv("REASONIX_HOME", t.TempDir()) |
| 14 | for _, id := range []string{"deepseek-chat", "deepseek-anthropic", "deepseek-responses"} { |
| 15 | t.Run(id, func(t *testing.T) { |
| 16 | preset, _ := CuratedProviderPreset(id) |
| 17 | cfg := &Config{Providers: preset.Entries, DefaultModel: id + "/deepseek-flash"} |
| 18 | path := filepath.Join(t.TempDir(), "config.toml") |
| 19 | for range 2 { |
| 20 | if err := cfg.SaveTo(path); err != nil { |
| 21 | t.Fatal(err) |
| 22 | } |
| 23 | cfg = LoadForEditWithoutCredentials(path) |
| 24 | p, ok := cfg.Provider(id) |
| 25 | if !ok || !reflect.DeepEqual(p.ModelList(), []string{"deepseek-flash", "deepseek-v4-pro"}) || p.DefaultModel() != "deepseek-flash" { |
| 26 | t.Fatalf("saved preset catalog changed: %+v", p) |
| 27 | } |
| 28 | for _, model := range []string{"deepseek-flash", "deepseek-v4-flash", "deepseek-v4-flash-vision-exp"} { |
| 29 | resolved, ok := cfg.ResolveModel(id + "/" + model) |
| 30 | if !ok || resolved.Model != model || !EffectiveVision(resolved) { |
| 31 | t.Fatalf("model reference %q lost wire identity or vision: %+v", model, resolved) |
| 32 | } |
| 33 | } |
| 34 | } |
| 35 | }) |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | func TestDeepSeekCuratedCatalogSurvivesRuntimeAndEditLoad(t *testing.T) { |
| 40 | for _, models := range []string{`["deepseek-flash"]`, `["deepseek-v4-flash"]`, `["deepseek-v4-flash", "deepseek-v4-pro"]`, `["deepseek-flash", "Custom-Case-ID"]`} { |
| 41 | t.Run(models, func(t *testing.T) { |
| 42 | home := t.TempDir() |
| 43 | t.Setenv("REASONIX_HOME", home) |
| 44 | path := filepath.Join(home, "config.toml") |
| 45 | raw := "config_version = 10\n[[providers]]\nname = \"deepseek-responses\"\nkind = \"responses\"\nbase_url = \"https://api.deepseek.com\"\nmodels = " + models + "\nvision_models = []\nfuture_field = \"preserve\"\n" |
| 46 | if err := os.WriteFile(path, []byte(raw), 0600); err != nil { |
| 47 | t.Fatal(err) |
| 48 | } |
| 49 | var original Config |
| 50 | if _, err := decodeTOMLFile(path, &original); err != nil { |
| 51 | t.Fatal(err) |
| 52 | } |
| 53 | runtime, err := LoadForRootReadOnly(t.TempDir()) |
| 54 | if err != nil { |
| 55 | t.Fatal(err) |
| 56 | } |
| 57 | for _, cfg := range []*Config{runtime, LoadForEditWithoutCredentials(path)} { |
| 58 | p, _ := cfg.Provider("deepseek-responses") |
| 59 | if !reflect.DeepEqual(p.ModelList(), original.Providers[0].ModelList()) || p.VisionModels == nil || len(p.VisionModels) != 0 { |
| 60 | t.Fatalf("loader overwrote a curated catalog or explicit vision opt-out: %+v", p) |
| 61 | } |
| 62 | } |
| 63 | if after, err := os.ReadFile(path); err != nil || string(after) != raw { |
| 64 | t.Fatal("loading changed the user's file or unknown fields") |
| 65 | } |
| 66 | }) |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | func TestDeepSeekLegacyReferenceCompatibilityIsScoped(t *testing.T) { |
| 71 | for _, tc := range []struct { |
| 72 | name, kind, base, request string |
| 73 | models []string |
| 74 | want bool |
| 75 | }{ |
| 76 | {"chat", "openai", "https://api.deepseek.com/v1", "", []string{"deepseek-flash"}, true}, |
| 77 | {"exact override", "openai", "https://api.deepseek.com", "https://api.deepseek.com/v1/chat/completions", []string{"deepseek-flash"}, true}, |
| 78 | {"responses", "responses", "https://api.deepseek.com", "", []string{"deepseek-flash"}, true}, |
| 79 | {"anthropic", "anthropic", "https://api.deepseek.com/anthropic", "", []string{"deepseek-flash"}, true}, |
| 80 | {"third party", "openai", "https://gateway.example/v1", "", []string{"deepseek-flash"}, false}, |
| 81 | {"custom route", "openai", "https://api.deepseek.com/custom", "", []string{"deepseek-flash"}, false}, |
| 82 | {"custom override", "openai", "https://api.deepseek.com", "https://gateway.example/chat/completions", []string{"deepseek-flash"}, false}, |
| 83 | {"Flash removed", "openai", "https://api.deepseek.com", "", []string{"deepseek-v4-pro"}, false}, |
| 84 | } { |
| 85 | t.Run(tc.name, func(t *testing.T) { |
| 86 | const old = "deepseek-v4-flash" |
| 87 | price := &provider.Pricing{Input: 5, Output: 10, Currency: "$"} |
| 88 | cfg := &Config{Providers: []ProviderEntry{{Name: "test", Kind: tc.kind, BaseURL: tc.base, RequestURL: tc.request, Models: tc.models, |
| 89 | Prices: map[string]*provider.Pricing{old: price}, ModelOverrides: map[string]ProviderModelOverride{old: {SupportedEfforts: []string{"high"}, DefaultEffort: "high"}}, |
| 90 | }}} |
| 91 | for _, ref := range []string{"test/" + old, old} { |
| 92 | got, ok := cfg.ResolveModel(ref) |
| 93 | if ok != tc.want { |
| 94 | t.Fatalf("ResolveModel(%q) = %v, want %v", ref, ok, tc.want) |
| 95 | } |
| 96 | if ok && (got.Model != old || !reflect.DeepEqual(got.Price, price) || !reflect.DeepEqual(got.SupportedEfforts, []string{"high"})) { |
| 97 | t.Fatalf("legacy reference lost its model-specific configuration: %+v", got) |
| 98 | } |
| 99 | } |
| 100 | for _, unknown := range []string{"DeepSeek-V4-Flash", "deepseek-v4.1-flash-expires-on-0910", "deepseek-future"} { |
| 101 | if _, ok := cfg.ResolveModel("test/" + unknown); ok { |
| 102 | t.Fatalf("invented compatibility for %q", unknown) |
| 103 | } |
| 104 | } |
| 105 | }) |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | func TestDeepSeekEmptyEntriesUseCurrentCatalog(t *testing.T) { |
| 110 | for _, id := range []string{"deepseek-chat", "deepseek-anthropic", "deepseek-responses"} { |
| 111 | preset, _ := CuratedProviderPreset(id) |
| 112 | entry := preset.Entries[0] |
| 113 | entry.Models, entry.Model, entry.Default = nil, "", "" |
| 114 | cfg := &Config{Providers: []ProviderEntry{entry}} |
| 115 | normalizeOfficialDeepSeekModels(cfg) |
| 116 | if p := &cfg.Providers[0]; !reflect.DeepEqual(p.ModelList(), []string{"deepseek-flash", "deepseek-v4-pro"}) || p.DefaultModel() != "deepseek-flash" { |
| 117 | t.Fatalf("%s empty entry uses stale defaults: %+v", id, p) |
| 118 | } |
| 119 | } |
| 120 | cfg := Default() |
| 121 | if p, ok := cfg.ResolveModel(cfg.DefaultModel); !ok || p.Model != "deepseek-flash" { |
| 122 | t.Fatalf("new CLI default = %+v", p) |
| 123 | } |
| 124 | cfg.Desktop.ProviderAccess = []string{"deepseek"} |
| 125 | normalizeDesktopOfficialProviderAccess(cfg) |
| 126 | normalizeOfficialDeepSeekModels(cfg) |
| 127 | if p, ok := cfg.Provider("deepseek"); !ok || !reflect.DeepEqual(p.ModelList(), []string{"deepseek-flash", "deepseek-v4-pro"}) { |
| 128 | t.Fatalf("new desktop catalog reintroduced retired aliases: %+v", p) |
| 129 | } |
| 130 | } |
| 131 |