123 lines
3.9 KiB
Go
123 lines
3.9 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestCCSwitchRowsToPlugins(t *testing.T) {
|
|
rows := []ccSwitchMCPRow{
|
|
{ID: "docs-id", Name: "docs", ServerConfig: `{"type":"http","url":"https://mcp.example.test","headers":{"Authorization":"Bearer ${TOKEN}"}}`},
|
|
{Name: "fs", ServerConfig: `{"command":"npx","args":["-y","@modelcontextprotocol/server-filesystem","."]}`},
|
|
}
|
|
got, err := ccSwitchRowsToPlugins(rows)
|
|
if err != nil {
|
|
t.Fatalf("ccSwitchRowsToPlugins: %v", err)
|
|
}
|
|
if got[0].Name != "docs-id" || got[0].Type != "http" || got[0].URL != "https://mcp.example.test" {
|
|
t.Fatalf("http entry = %+v", got[0])
|
|
}
|
|
if got[0].Headers["Authorization"] != "Bearer ${TOKEN}" {
|
|
t.Errorf("header was not preserved: %+v", got[0].Headers)
|
|
}
|
|
if got[1].Name != "fs" || got[1].Command != "npx" ||
|
|
!reflect.DeepEqual(got[1].Args, []string{"-y", "@modelcontextprotocol/server-filesystem", "."}) {
|
|
t.Fatalf("stdio entry = %+v", got[1])
|
|
}
|
|
}
|
|
|
|
func TestCCSwitchRowsPreferIDForDuplicateDisplayNames(t *testing.T) {
|
|
rows := []ccSwitchMCPRow{
|
|
{ID: "search-code", Name: "search", ServerConfig: `{"command":"node","args":["code.js"]}`},
|
|
{ID: "search-docs", Name: "search", ServerConfig: `{"command":"node","args":["docs.js"]}`},
|
|
}
|
|
got, err := ccSwitchRowsToPlugins(rows)
|
|
if err != nil {
|
|
t.Fatalf("ccSwitchRowsToPlugins: %v", err)
|
|
}
|
|
if got[0].Name != "search-code" || got[1].Name != "search-docs" {
|
|
t.Fatalf("names = %q, %q; want stable ids", got[0].Name, got[1].Name)
|
|
}
|
|
}
|
|
|
|
func TestCCSwitchImportClassifiesRiskyServers(t *testing.T) {
|
|
rows := []ccSwitchMCPRow{
|
|
{Name: "@modelcontextprotocol/server-chrome-devtools", ServerConfig: `{"command":"npx","args":["-y","chrome-devtools-mcp@latest"]}`},
|
|
{Name: "legacy", ServerConfig: `{"type":"sse","url":"https://example.test/sse"}`},
|
|
}
|
|
got, err := ccSwitchRowsToPlugins(rows)
|
|
if err != nil {
|
|
t.Fatalf("ccSwitchRowsToPlugins: %v", err)
|
|
}
|
|
for _, e := range got {
|
|
candidate := classifyMCPImportCandidate(e)
|
|
if candidate.Recommended {
|
|
t.Fatalf("%s should not be recommended: %+v", e.Name, candidate)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLoadCCSwitchLegacyConfig(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.json.migrated")
|
|
body := `{
|
|
"mcp": {
|
|
"servers": {
|
|
"off": {
|
|
"name": "off",
|
|
"server": {"command": "node", "args": ["off.js"]},
|
|
"apps": {"codex": false}
|
|
},
|
|
"time": {
|
|
"name": "@modelcontextprotocol/server-time",
|
|
"server": {"type":"stdio", "command": "uvx", "args": ["mcp-server-time"]},
|
|
"apps": {"codex": true}
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := loadCCSwitchLegacyConfig(path)
|
|
if err != nil {
|
|
t.Fatalf("loadCCSwitchLegacyConfig: %v", err)
|
|
}
|
|
if len(got) != 1 {
|
|
t.Fatalf("entries = %d, want 1: %+v", len(got), got)
|
|
}
|
|
if got[0].Name != "@modelcontextprotocol/server-time" || got[0].Command != "uvx" {
|
|
t.Fatalf("entry = %+v", got[0])
|
|
}
|
|
}
|
|
|
|
func TestLoadCCSwitchMCPEmptyDBDoesNotReadLegacyBackups(t *testing.T) {
|
|
if _, err := exec.LookPath("sqlite3"); err != nil {
|
|
t.Skip("sqlite3 not available")
|
|
}
|
|
root := t.TempDir()
|
|
dbPath := filepath.Join(root, "cc-switch.db")
|
|
if out, err := exec.Command("sqlite3", dbPath, `CREATE TABLE mcp_servers (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
server_config TEXT NOT NULL,
|
|
enabled_codex BOOLEAN NOT NULL DEFAULT 0
|
|
);`).CombinedOutput(); err != nil {
|
|
t.Fatalf("create sqlite db: %v\n%s", err, out)
|
|
}
|
|
stale := `{"mcp":{"servers":{"stale":{"name":"stale","server":{"command":"node","args":["stale.js"]},"apps":{"codex":true}}}}}`
|
|
if err := os.WriteFile(filepath.Join(root, "config.json.migrated"), []byte(stale), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := loadCCSwitchMCPFromRoot(root)
|
|
if err != nil {
|
|
t.Fatalf("loadCCSwitchMCPFromRoot: %v", err)
|
|
}
|
|
if len(got) != 0 {
|
|
t.Fatalf("empty sqlite db should be authoritative, got legacy entries: %+v", got)
|
|
}
|
|
}
|