mcp-framework/cli/doctor_test.go

174 lines
5.1 KiB
Go
Raw Normal View History

2026-04-13 18:53:36 +00:00
package cli
import (
"bytes"
"context"
"errors"
"os"
"path/filepath"
"strings"
"testing"
"gitea.lclr.dev/AI/mcp-framework/config"
"gitea.lclr.dev/AI/mcp-framework/manifest"
"gitea.lclr.dev/AI/mcp-framework/secretstore"
)
type doctorProfile struct {
BaseURL string `json:"base_url"`
}
func TestRunDoctorAggregatesCommonChecksAndExtras(t *testing.T) {
tempHome := t.TempDir()
t.Setenv("XDG_CONFIG_HOME", tempHome)
t.Setenv("HOME", tempHome)
store := config.NewStore[doctorProfile]("doctor-test")
path, err := store.ConfigPath()
if err != nil {
t.Fatalf("ConfigPath returned error: %v", err)
}
cfg := config.FileConfig[doctorProfile]{
Version: config.CurrentVersion,
CurrentProfile: "default",
Profiles: map[string]doctorProfile{
"default": {BaseURL: "https://api.example.com"},
},
}
if err := store.Save(path, cfg); err != nil {
t.Fatalf("Save returned error: %v", err)
}
manifestDir := t.TempDir()
if err := os.WriteFile(filepath.Join(manifestDir, "mcp.toml"), []byte("[update]\nlatest_release_url = \"https://example.com/latest\"\n"), 0o600); err != nil {
t.Fatalf("WriteFile manifest: %v", err)
}
secretFactory := func() (secretstore.Store, error) {
return secretstore.Open(secretstore.Options{
BackendPolicy: secretstore.BackendEnvOnly,
LookupEnv: func(name string) (string, bool) {
if name == "api-token" {
return "secret", true
}
return "", false
},
})
}
report := RunDoctor(context.Background(), DoctorOptions{
ConfigCheck: NewConfigCheck(store),
SecretStoreCheck: SecretStoreAvailabilityCheck(secretFactory),
RequiredSecrets: []DoctorSecret{
{Name: "api-token", Label: "API token"},
},
SecretStoreFactory: secretFactory,
ManifestDir: manifestDir,
ConnectivityCheck: func(context.Context) DoctorResult {
return DoctorResult{Name: "connectivity", Status: DoctorStatusOK, Summary: "service is reachable"}
},
ExtraChecks: []DoctorCheck{
func(context.Context) DoctorResult {
return DoctorResult{Name: "custom", Status: DoctorStatusWarn, Summary: "non blocking drift"}
},
},
})
summary := report.Summary()
if summary.OK != 5 || summary.Warn != 1 || summary.Fail != 0 || summary.Total != 6 {
t.Fatalf("summary = %#v", summary)
}
}
func TestNewConfigCheckWarnsWhenConfigIsMissing(t *testing.T) {
tempHome := t.TempDir()
t.Setenv("XDG_CONFIG_HOME", tempHome)
t.Setenv("HOME", tempHome)
store := config.NewStore[doctorProfile]("doctor-test")
result := NewConfigCheck(store)(context.Background())
if result.Status != DoctorStatusWarn {
t.Fatalf("status = %q, want warn", result.Status)
}
if !strings.Contains(result.Detail, "config.json") {
t.Fatalf("detail = %q, want config path", result.Detail)
}
}
func TestRequiredSecretsCheckFailsWhenSecretIsMissing(t *testing.T) {
check := RequiredSecretsCheck(func() (secretstore.Store, error) {
return secretstore.Open(secretstore.Options{
BackendPolicy: secretstore.BackendEnvOnly,
LookupEnv: func(name string) (string, bool) {
return "", false
},
})
}, []DoctorSecret{{Name: "API_TOKEN", Label: "API token"}})
result := check(context.Background())
if result.Status != DoctorStatusFail {
t.Fatalf("status = %q, want fail", result.Status)
}
if !strings.Contains(result.Detail, "API_TOKEN") {
t.Fatalf("detail = %q, want secret name", result.Detail)
}
}
func TestManifestCheckUsesValidator(t *testing.T) {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "mcp.toml"), []byte("[update]\nlatest_release_url = \"https://example.com/latest\"\n"), 0o600); err != nil {
t.Fatalf("WriteFile manifest: %v", err)
}
result := ManifestCheck(dir, func(_ manifest.File, path string) []string {
_ = path
return []string{"missing business section"}
})(context.Background())
if result.Status != DoctorStatusFail {
t.Fatalf("status = %q, want fail", result.Status)
}
}
func TestRenderDoctorReportFormatsStatusesAndSummary(t *testing.T) {
var out bytes.Buffer
report := DoctorReport{
Results: []DoctorResult{
{Name: "config", Status: DoctorStatusOK, Summary: "config file is readable", Detail: "/tmp/config.json"},
{Name: "custom", Status: DoctorStatusWarn, Summary: "optional check skipped"},
{Name: "manifest", Status: DoctorStatusFail, Summary: "manifest is invalid", Detail: "parse error"},
},
}
if err := RenderDoctorReport(&out, report); err != nil {
t.Fatalf("RenderDoctorReport returned error: %v", err)
}
text := out.String()
for _, needle := range []string{
"[OK] config: config file is readable",
"[WARN] custom: optional check skipped",
"[FAIL] manifest: manifest is invalid",
"Summary: 1 ok, 1 warning(s), 1 failure(s), 3 total",
} {
if !strings.Contains(text, needle) {
t.Fatalf("output = %q, want substring %q", text, needle)
}
}
}
func TestSecretStoreAvailabilityCheckFailsWhenFactoryFails(t *testing.T) {
result := SecretStoreAvailabilityCheck(func() (secretstore.Store, error) {
return nil, errors.New("backend missing")
})(context.Background())
if result.Status != DoctorStatusFail {
t.Fatalf("status = %q, want fail", result.Status)
}
if !strings.Contains(result.Detail, "backend missing") {
t.Fatalf("detail = %q", result.Detail)
}
}