302 lines
9.5 KiB
Go
302 lines
9.5 KiB
Go
package config
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestResolveModel(t *testing.T) {
|
|
p := &Provider{
|
|
Models: Models{
|
|
Default: "ds-chat",
|
|
Sonnet: "ds-chat",
|
|
Haiku: "ds-haiku",
|
|
Opus: "ds-opus",
|
|
Extra: map[string]string{"claude-3-5-haiku-20241022": "ds-fast"},
|
|
},
|
|
}
|
|
cases := []struct {
|
|
requested string
|
|
want string
|
|
}{
|
|
{"claude-sonnet-4-5-20250929", "ds-chat"},
|
|
{"claude-3-5-haiku-20241022", "ds-fast"}, // exact Extra match wins
|
|
{"some-haiku-model", "ds-haiku"}, // keyword match
|
|
{"claude-opus-4-1", "ds-opus"},
|
|
{"ds-chat", "ds-chat"}, // already an upstream name
|
|
{"unknown-model", "ds-chat"},
|
|
{"", "ds-chat"},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := p.ResolveModel(tc.requested); got != tc.want {
|
|
t.Errorf("ResolveModel(%q) = %q, want %q", tc.requested, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResolveModelWithoutDefaults(t *testing.T) {
|
|
p := &Provider{Models: Models{Sonnet: "kimi-k2"}}
|
|
if got := p.ResolveModel("claude-3-7-sonnet"); got != "kimi-k2" {
|
|
t.Errorf("sonnet request = %q, want kimi-k2", got)
|
|
}
|
|
// No default configured: unknown models pass through unchanged.
|
|
if got := p.ResolveModel("weird-model"); got != "weird-model" {
|
|
t.Errorf("unknown request = %q, want passthrough", got)
|
|
}
|
|
}
|
|
|
|
func TestStoreRoundTrip(t *testing.T) {
|
|
t.Setenv("CROSSRELAY_HOME", t.TempDir())
|
|
store, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
if !strings.HasPrefix(store.Config().RelayToken, "crl_") {
|
|
t.Fatalf("relay token = %q, want crl_ prefix", store.Config().RelayToken)
|
|
}
|
|
if store.Config().Language != LanguageChinese {
|
|
t.Fatalf("default language = %q", store.Config().Language)
|
|
}
|
|
p, err := store.AddProvider("DeepSeek", "deepseek", "https://api.deepseek.com/v1", "sk-test",
|
|
Models{Default: "deepseek-chat", Sonnet: "deepseek-chat", Haiku: "deepseek-chat", Opus: "deepseek-chat"}, TargetClaude)
|
|
if err != nil {
|
|
t.Fatalf("AddProvider: %v", err)
|
|
}
|
|
if _, err := store.ActivateClaude(p.ID); err != nil {
|
|
t.Fatalf("ActivateClaude: %v", err)
|
|
}
|
|
|
|
reloaded, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("reload: %v", err)
|
|
}
|
|
if got := reloaded.ActiveClaude(); got == nil || got.Name != "DeepSeek" {
|
|
t.Fatalf("active provider after reload = %+v", got)
|
|
}
|
|
if len(reloaded.Config().Providers) != 1 {
|
|
t.Fatalf("providers after reload = %d", len(reloaded.Config().Providers))
|
|
}
|
|
}
|
|
|
|
func TestAddProviderValidation(t *testing.T) {
|
|
t.Setenv("CROSSRELAY_HOME", t.TempDir())
|
|
store, _ := Load()
|
|
cases := []struct {
|
|
name, baseURL, apiKey string
|
|
}{
|
|
{"", "https://x.com/v1", "sk"},
|
|
{"p", "", "sk"},
|
|
{"p", "ftp://x.com", "sk"},
|
|
{"p", "https://x.com/v1", ""},
|
|
}
|
|
for _, tc := range cases {
|
|
if _, err := store.AddProvider(tc.name, "", tc.baseURL, tc.apiKey, Models{}, TargetClaude); err == nil {
|
|
t.Errorf("AddProvider(%q, %q, %q) succeeded, want error", tc.name, tc.baseURL, tc.apiKey)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUpdateProviderKeepsKeyWhenEmpty(t *testing.T) {
|
|
t.Setenv("CROSSRELAY_HOME", t.TempDir())
|
|
store, _ := Load()
|
|
p, _ := store.AddProvider("p", "", "https://x.com/v1", "sk-secret", Models{Default: "m1"}, TargetClaude)
|
|
updated, err := store.UpdateProvider(p.ID, "p2", "", "https://x.com/v1/", "", Models{Default: "m2"}, TargetClaude)
|
|
if err != nil {
|
|
t.Fatalf("UpdateProvider: %v", err)
|
|
}
|
|
if updated.APIKey != "sk-secret" {
|
|
t.Errorf("api key = %q, want sk-secret", updated.APIKey)
|
|
}
|
|
if updated.Name != "p2" || updated.Models.Default != "m2" {
|
|
t.Errorf("update lost fields: %+v", updated)
|
|
}
|
|
if updated.BaseURL != "https://x.com/v1" {
|
|
t.Errorf("base url = %q, want trailing slash trimmed", updated.BaseURL)
|
|
}
|
|
}
|
|
|
|
func TestDeleteProviderClearsActive(t *testing.T) {
|
|
t.Setenv("CROSSRELAY_HOME", t.TempDir())
|
|
store, _ := Load()
|
|
p, _ := store.AddProvider("p", "", "https://x.com/v1", "sk", Models{}, TargetClaude)
|
|
store.ActivateClaude(p.ID)
|
|
if err := store.DeleteProvider(p.ID); err != nil {
|
|
t.Fatalf("DeleteProvider: %v", err)
|
|
}
|
|
if store.ActiveClaude() != nil {
|
|
t.Error("active provider survived deletion")
|
|
}
|
|
}
|
|
|
|
func TestActivateCodexRoundTrip(t *testing.T) {
|
|
t.Setenv("CROSSRELAY_HOME", t.TempDir())
|
|
store, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
p, err := store.AddProvider("DeepSeek", "deepseek", "https://api.deepseek.com/v1", "sk-test",
|
|
Models{Default: "deepseek-chat"}, TargetCodex)
|
|
if err != nil {
|
|
t.Fatalf("AddProvider: %v", err)
|
|
}
|
|
if _, err := store.ActivateCodex(p.ID); err != nil {
|
|
t.Fatalf("ActivateCodex: %v", err)
|
|
}
|
|
if err := store.SetCodexPriorProvider("custom"); err != nil {
|
|
t.Fatalf("SetCodexPriorProvider: %v", err)
|
|
}
|
|
|
|
reloaded, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("reload: %v", err)
|
|
}
|
|
active := reloaded.ActiveCodex()
|
|
if active == nil || active.Name != "DeepSeek" {
|
|
t.Fatalf("active codex provider after reload = %+v", active)
|
|
}
|
|
if !active.EnabledForCodex {
|
|
t.Error("enabled_for_codex not persisted")
|
|
}
|
|
if reloaded.Config().CodexPriorProvider != "custom" {
|
|
t.Errorf("prior provider = %q, want custom", reloaded.Config().CodexPriorProvider)
|
|
}
|
|
|
|
if err := reloaded.ClearActiveCodex(); err != nil {
|
|
t.Fatalf("ClearActiveCodex: %v", err)
|
|
}
|
|
if reloaded.ActiveCodex() != nil {
|
|
t.Error("active codex provider survived clear")
|
|
}
|
|
if _, err := reloaded.ActivateCodex("p_missing"); err == nil {
|
|
t.Error("ActivateCodex accepted an unknown id")
|
|
}
|
|
}
|
|
|
|
func TestUpdateProviderKeepsEnabledFlags(t *testing.T) {
|
|
t.Setenv("CROSSRELAY_HOME", t.TempDir())
|
|
store, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
claude, err := store.AddProvider("DeepSeek", "deepseek", "https://api.deepseek.com/v1", "sk-test",
|
|
Models{Default: "deepseek-chat"}, TargetClaude)
|
|
if err != nil {
|
|
t.Fatalf("AddProvider: %v", err)
|
|
}
|
|
codex, err := store.AddProvider("Kimi", "moonshot", "https://api.moonshot.cn/v1", "sk-kimi",
|
|
Models{Default: "kimi-k2-0905-preview"}, TargetCodex)
|
|
if err != nil {
|
|
t.Fatalf("AddProvider: %v", err)
|
|
}
|
|
if _, err := store.ActivateClaude(claude.ID); err != nil {
|
|
t.Fatalf("ActivateClaude: %v", err)
|
|
}
|
|
if _, err := store.ActivateCodex(codex.ID); err != nil {
|
|
t.Fatalf("ActivateCodex: %v", err)
|
|
}
|
|
updated, err := store.UpdateProvider(claude.ID, "DeepSeek 2", "deepseek", "https://api.deepseek.com/v1", "",
|
|
Models{Default: "deepseek-reasoner"}, "")
|
|
if err != nil {
|
|
t.Fatalf("UpdateProvider: %v", err)
|
|
}
|
|
if !updated.EnabledForClaude {
|
|
t.Error("enabled_for_claude lost across update")
|
|
}
|
|
if updated.EnabledForCodex {
|
|
t.Error("a Claude provider must not carry the Codex flag")
|
|
}
|
|
if updated.Target != TargetClaude {
|
|
t.Errorf("target = %q after an update without target, want claude", updated.Target)
|
|
}
|
|
if updated.APIKey != "sk-test" {
|
|
t.Errorf("api key = %q, want the stored key kept", updated.APIKey)
|
|
}
|
|
updatedCodex, err := store.UpdateProvider(codex.ID, "Kimi 2", "moonshot", "https://api.moonshot.cn/v1", "",
|
|
Models{Default: "kimi-k2-0905-preview"}, "")
|
|
if err != nil {
|
|
t.Fatalf("UpdateProvider codex: %v", err)
|
|
}
|
|
if !updatedCodex.EnabledForCodex || updatedCodex.Target != TargetCodex {
|
|
t.Errorf("codex provider after update = %+v", updatedCodex)
|
|
}
|
|
}
|
|
|
|
func TestProviderTargetDefaultsAndValidation(t *testing.T) {
|
|
t.Setenv("CROSSRELAY_HOME", t.TempDir())
|
|
store, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
p, err := store.AddProvider("p", "", "https://x.com/v1", "sk", Models{}, "")
|
|
if err != nil {
|
|
t.Fatalf("AddProvider: %v", err)
|
|
}
|
|
if p.Target != TargetClaude {
|
|
t.Errorf("default target = %q, want claude", p.Target)
|
|
}
|
|
if _, err := store.AddProvider("p", "", "https://x.com/v1", "sk", Models{}, "bogus"); err == nil {
|
|
t.Error("AddProvider accepted an unknown target")
|
|
}
|
|
}
|
|
|
|
func TestActivateRejectsWrongTarget(t *testing.T) {
|
|
t.Setenv("CROSSRELAY_HOME", t.TempDir())
|
|
store, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
claude, _ := store.AddProvider("c", "", "https://x.com/v1", "sk", Models{}, TargetClaude)
|
|
codex, _ := store.AddProvider("x", "", "https://x.com/v1", "sk", Models{}, TargetCodex)
|
|
if _, err := store.ActivateClaude(codex.ID); err == nil {
|
|
t.Error("ActivateClaude accepted a Codex provider")
|
|
}
|
|
if _, err := store.ActivateCodex(claude.ID); err == nil {
|
|
t.Error("ActivateCodex accepted a Claude provider")
|
|
}
|
|
if _, err := store.ActivateClaude(claude.ID); err != nil {
|
|
t.Errorf("ActivateClaude on a Claude provider: %v", err)
|
|
}
|
|
if _, err := store.ActivateCodex(codex.ID); err != nil {
|
|
t.Errorf("ActivateCodex on a Codex provider: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDeleteProviderClearsCodexActive(t *testing.T) {
|
|
t.Setenv("CROSSRELAY_HOME", t.TempDir())
|
|
store, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
p, err := store.AddProvider("DeepSeek", "deepseek", "https://api.deepseek.com/v1", "sk-test",
|
|
Models{Default: "deepseek-chat"}, TargetCodex)
|
|
if err != nil {
|
|
t.Fatalf("AddProvider: %v", err)
|
|
}
|
|
if _, err := store.ActivateCodex(p.ID); err != nil {
|
|
t.Fatalf("ActivateCodex: %v", err)
|
|
}
|
|
if err := store.DeleteProvider(p.ID); err != nil {
|
|
t.Fatalf("DeleteProvider: %v", err)
|
|
}
|
|
if store.ActiveCodex() != nil {
|
|
t.Error("deleted provider stayed active for codex")
|
|
}
|
|
}
|
|
|
|
func TestSetGatewayValidation(t *testing.T) {
|
|
t.Setenv("CROSSRELAY_HOME", t.TempDir())
|
|
store, _ := Load()
|
|
if err := store.SetGateway(Gateway{PortMode: "fixed", FixedPort: 8787}); err != nil {
|
|
t.Fatalf("SetGateway: %v", err)
|
|
}
|
|
if err := store.SetGateway(Gateway{PortMode: "fixed", FixedPort: 0}); err == nil {
|
|
t.Error("fixed port 0 accepted")
|
|
}
|
|
if err := store.SetGateway(Gateway{PortMode: "bogus"}); err == nil {
|
|
t.Error("bogus port mode accepted")
|
|
}
|
|
if err := store.SetLanguage("fr"); err == nil {
|
|
t.Error("unknown language accepted")
|
|
}
|
|
}
|