| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "github.com/BurntSushi/toml" |
| 5 | "os" |
| 6 | "path/filepath" |
| 7 | "reasonix/internal/config" |
| 8 | "testing" |
| 9 | ) |
| 10 | |
| 11 | func TestProviderDisplayNamePreservesIdentity(t *testing.T) { |
| 12 | c := &config.Config{Providers: []config.ProviderEntry{{Name: "stable", Kind: "openai", BaseURL: "https://example.com/v1", Models: []string{"model"}, APIKeyEnv: "TEST_KEY"}}} |
| 13 | c.DefaultModel = "stable/model" |
| 14 | view := providerViewFromEntry(c.Providers[0], false, true) |
| 15 | label := "工作账号" |
| 16 | view.DisplayName = &label |
| 17 | if err := saveProviderConfig(c, view); err != nil { |
| 18 | t.Fatal(err) |
| 19 | } |
| 20 | if len(c.Providers) != 1 || c.Providers[0].Name != "stable" || c.DefaultModel != "stable/model" || c.Providers[0].DisplayName != label { |
| 21 | t.Fatalf("identity or label changed: %+v", c.Providers) |
| 22 | } |
| 23 | var decoded config.Config |
| 24 | if _, err := toml.Decode(config.RenderTOML(c), &decoded); err != nil { |
| 25 | t.Fatal(err) |
| 26 | } |
| 27 | if len(decoded.Providers) == 0 || decoded.Providers[0].DisplayName != label { |
| 28 | t.Fatal("display name lost in TOML") |
| 29 | } |
| 30 | view.DisplayName = nil // An older frontend sends no displayName. |
| 31 | if err := saveProviderConfig(c, view); err != nil { |
| 32 | t.Fatal(err) |
| 33 | } |
| 34 | if c.Providers[0].DisplayName != label { |
| 35 | t.Fatal("old client erased label") |
| 36 | } |
| 37 | empty := "" |
| 38 | view.DisplayName = &empty |
| 39 | if err := saveProviderConfig(c, view); err != nil { |
| 40 | t.Fatal(err) |
| 41 | } |
| 42 | if c.Providers[0].DisplayName != "" { |
| 43 | t.Fatal("explicit clear not saved") |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | func TestRenameConnectionOnlyChangesLabel(t *testing.T) { |
| 48 | c := &config.Config{Providers: []config.ProviderEntry{{Name: "stable", DisplayName: "old", Kind: "openai", BaseURL: "https://example.com/v1", Models: []string{"m"}, APIKeyEnv: "TEST_KEY"}}} |
| 49 | original := c.Providers[0] |
| 50 | if err := renameProviderConnections(c, []string{"stable", "missing"}, "new"); err == nil { |
| 51 | t.Fatal("missing connection accepted") |
| 52 | } |
| 53 | if c.Providers[0].DisplayName != "old" { |
| 54 | t.Fatal("partial mutation") |
| 55 | } |
| 56 | if err := renameProviderConnections(c, []string{"stable"}, " new "); err != nil { |
| 57 | t.Fatal(err) |
| 58 | } |
| 59 | renamed := c.Providers[0] |
| 60 | renamed.DisplayName = original.DisplayName |
| 61 | if !config.ProviderEntriesConfigEqual(renamed, original) { |
| 62 | t.Fatal("rename changed configuration") |
| 63 | } |
| 64 | if c.Providers[0].DisplayName != "new" { |
| 65 | t.Fatal("label not saved") |
| 66 | } |
| 67 | oldDraft := providerViewFromEntry(original, false, true) |
| 68 | oldDraft.DisplayName = nil |
| 69 | if err := saveProviderConfig(c, oldDraft); err != nil { |
| 70 | t.Fatal(err) |
| 71 | } |
| 72 | if c.Providers[0].DisplayName != "new" { |
| 73 | t.Fatal("configuration draft overwrote name") |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | func TestBuiltinProviderRemainsEditableAfterReload(t *testing.T) { |
| 78 | c := &config.Config{Providers: []config.ProviderEntry{{Name: "deepseek", Kind: "anthropic", BaseURL: "https://api.deepseek.com/anthropic", Models: []string{"deepseek-v4-flash"}, APIKeyEnv: "DEEPSEEK_API_KEY"}}} |
| 79 | view := providerViewFromEntry(c.Providers[0], true, true) |
| 80 | view.Kind = "responses" |
| 81 | view.BaseURL = "https://gateway.example/v1" |
| 82 | view.RequestURL = "https://gateway.example/v1/responses" |
| 83 | view.Models = []string{"custom-model"} |
| 84 | view.Default = "custom-model" |
| 85 | if err := saveProviderConfig(c, view); err != nil { |
| 86 | t.Fatal(err) |
| 87 | } |
| 88 | path := filepath.Join(t.TempDir(), "config.toml") |
| 89 | if err := os.WriteFile(path, []byte(config.RenderTOML(c)), 0600); err != nil { |
| 90 | t.Fatal(err) |
| 91 | } |
| 92 | got, err := config.LoadForEditWithoutCredentialsReadOnlyStrict(path) |
| 93 | if err != nil { |
| 94 | t.Fatal(err) |
| 95 | } |
| 96 | entry, ok := got.Provider("deepseek") |
| 97 | if !ok || entry.Kind != "responses" || entry.BaseURL != view.BaseURL || entry.RequestURL != view.RequestURL || len(entry.Models) != 1 || entry.Models[0] != "custom-model" { |
| 98 | t.Fatalf("customized builtin reset after reload: %+v", entry) |
| 99 | } |
| 100 | } |
| 101 |