mcp-framework/config/config_test.go

248 lines
6.2 KiB
Go
Raw Permalink Normal View History

2026-04-13 13:33:48 +00:00
package config
import (
2026-04-13 15:28:47 +00:00
"encoding/json"
"errors"
2026-04-13 13:33:48 +00:00
"os"
"path/filepath"
2026-04-13 15:28:47 +00:00
"strings"
2026-04-13 13:33:48 +00:00
"testing"
)
type testProfile struct {
BaseURL string `json:"base_url"`
StreamID string `json:"stream_id"`
}
func TestLoadMissingReturnsDefault(t *testing.T) {
store := NewStore[testProfile]("mcp-framework-test")
path := filepath.Join(t.TempDir(), "missing.json")
cfg, err := store.Load(path)
if err != nil {
t.Fatalf("Load returned error: %v", err)
}
if cfg.Version != CurrentVersion {
t.Fatalf("Version = %d, want %d", cfg.Version, CurrentVersion)
}
if len(cfg.Profiles) != 0 {
t.Fatalf("Profiles = %v, want empty", cfg.Profiles)
}
}
2026-04-13 15:28:47 +00:00
func TestLoadMigratesLegacyConfigAndPreservesProfiles(t *testing.T) {
store := NewStore[testProfile]("mcp-framework-test")
path := filepath.Join(t.TempDir(), "config.json")
data := []byte(`{
"current_profile": "prod",
"profiles": {
"prod": {
"base_url": "https://api.example.com",
"stream_id": "stream-1"
}
}
}
`)
if err := os.WriteFile(path, data, 0o600); err != nil {
t.Fatalf("WriteFile returned error: %v", err)
}
cfg, err := store.Load(path)
if err != nil {
t.Fatalf("Load returned error: %v", err)
}
if cfg.Version != CurrentVersion {
t.Fatalf("Version = %d, want %d", cfg.Version, CurrentVersion)
}
if cfg.CurrentProfile != "prod" {
t.Fatalf("CurrentProfile = %q, want prod", cfg.CurrentProfile)
}
if cfg.Profiles["prod"].StreamID != "stream-1" {
t.Fatalf("StreamID = %q, want stream-1", cfg.Profiles["prod"].StreamID)
}
}
2026-04-13 13:33:48 +00:00
func TestSaveAndLoadRoundTrip(t *testing.T) {
store := NewStore[testProfile]("mcp-framework-test")
dir := t.TempDir()
path := filepath.Join(dir, "mcp-framework", "config.json")
input := FileConfig[testProfile]{
Version: CurrentVersion,
CurrentProfile: "prod",
Profiles: map[string]testProfile{
"prod": {
BaseURL: "https://api.example.com",
2026-04-13 13:33:48 +00:00
StreamID: "stream-1",
},
},
}
if err := store.Save(path, input); err != nil {
t.Fatalf("Save returned error: %v", err)
}
info, err := os.Stat(path)
if err != nil {
t.Fatalf("Stat returned error: %v", err)
}
if info.Mode().Perm() != 0o600 {
t.Fatalf("file mode = %o, want 600", info.Mode().Perm())
}
dirInfo, err := os.Stat(filepath.Dir(path))
if err != nil {
t.Fatalf("Stat dir returned error: %v", err)
}
if dirInfo.Mode().Perm() != 0o700 {
t.Fatalf("dir mode = %o, want 700", dirInfo.Mode().Perm())
}
cfg, err := store.Load(path)
if err != nil {
t.Fatalf("Load returned error: %v", err)
}
if cfg.CurrentProfile != "prod" {
t.Fatalf("CurrentProfile = %q, want prod", cfg.CurrentProfile)
}
if cfg.Profiles["prod"].BaseURL != "https://api.example.com" {
2026-04-13 13:33:48 +00:00
t.Fatalf("BaseURL = %q", cfg.Profiles["prod"].BaseURL)
}
}
2026-04-13 15:28:47 +00:00
func TestLoadAppliesRegisteredMigrations(t *testing.T) {
store := NewStoreWithOptions[testProfile]("mcp-framework-test", Options[testProfile]{
Version: 2,
Migrations: map[int]Migration{
1: func(doc map[string]json.RawMessage) error {
doc["migrated"] = json.RawMessage(`true`)
return nil
},
},
})
path := filepath.Join(t.TempDir(), "config.json")
data := []byte(`{
"version": 1,
"current_profile": "prod",
"profiles": {
"prod": {
"base_url": "https://api.example.com",
"stream_id": "stream-1"
}
}
}
`)
if err := os.WriteFile(path, data, 0o600); err != nil {
t.Fatalf("WriteFile returned error: %v", err)
}
cfg, err := store.Load(path)
if err != nil {
t.Fatalf("Load returned error: %v", err)
}
if cfg.Version != 2 {
t.Fatalf("Version = %d, want 2", cfg.Version)
}
if cfg.CurrentProfile != "prod" {
t.Fatalf("CurrentProfile = %q, want prod", cfg.CurrentProfile)
}
if cfg.Profiles["prod"].BaseURL != "https://api.example.com" {
t.Fatalf("BaseURL = %q, want https://api.example.com", cfg.Profiles["prod"].BaseURL)
}
}
func TestLoadRejectsFutureVersion(t *testing.T) {
store := NewStore[testProfile]("mcp-framework-test")
path := filepath.Join(t.TempDir(), "config.json")
data := []byte(`{
"version": 2,
"profiles": {}
}
`)
if err := os.WriteFile(path, data, 0o600); err != nil {
t.Fatalf("WriteFile returned error: %v", err)
}
_, err := store.Load(path)
if !errors.Is(err, ErrFutureVersion) {
t.Fatalf("Load error = %v, want ErrFutureVersion", err)
}
}
func TestLoadRejectsMissingMigrationPath(t *testing.T) {
store := NewStoreWithOptions[testProfile]("mcp-framework-test", Options[testProfile]{Version: 2})
path := filepath.Join(t.TempDir(), "config.json")
data := []byte(`{
"version": 1,
"profiles": {}
}
`)
if err := os.WriteFile(path, data, 0o600); err != nil {
t.Fatalf("WriteFile returned error: %v", err)
}
_, err := store.Load(path)
if !errors.Is(err, ErrUnsupportedVersion) {
t.Fatalf("Load error = %v, want ErrUnsupportedVersion", err)
}
if !strings.Contains(err.Error(), "no migration registered from version 1 to 2") {
t.Fatalf("Load error = %v, want missing migration detail", err)
}
}
func TestLoadValidatesConfig(t *testing.T) {
store := NewStoreWithOptions[testProfile]("mcp-framework-test", Options[testProfile]{
Validator: func(cfg FileConfig[testProfile]) []ValidationIssue {
issues := make([]ValidationIssue, 0, len(cfg.Profiles))
for name, profile := range cfg.Profiles {
if profile.BaseURL == "" {
issues = append(issues, ValidationIssue{
Path: "profiles." + name + ".base_url",
Message: "must not be empty",
})
}
}
return issues
},
})
path := filepath.Join(t.TempDir(), "config.json")
data := []byte(`{
"version": 1,
"current_profile": "prod",
"profiles": {
"prod": {
"stream_id": "stream-1"
}
}
}
`)
if err := os.WriteFile(path, data, 0o600); err != nil {
t.Fatalf("WriteFile returned error: %v", err)
}
_, err := store.Load(path)
if !errors.Is(err, ErrInvalidConfig) {
t.Fatalf("Load error = %v, want ErrInvalidConfig", err)
}
var validationErr *ValidationError
if !errors.As(err, &validationErr) {
t.Fatalf("Load error = %v, want ValidationError", err)
}
if len(validationErr.Issues) != 1 {
t.Fatalf("Validation issues = %v, want 1 issue", validationErr.Issues)
}
if validationErr.Issues[0].Path != "profiles.prod.base_url" {
t.Fatalf("Validation issue path = %q, want profiles.prod.base_url", validationErr.Issues[0].Path)
}
}