2026-04-13 18:53:36 +00:00
|
|
|
package cli
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"context"
|
|
|
|
|
"errors"
|
2026-04-20 07:39:05 +00:00
|
|
|
"fmt"
|
2026-04-13 18:53:36 +00:00
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
|
2026-05-05 10:23:14 +00:00
|
|
|
"forge.lclr.dev/AI/mcp-framework/config"
|
|
|
|
|
"forge.lclr.dev/AI/mcp-framework/manifest"
|
|
|
|
|
"forge.lclr.dev/AI/mcp-framework/secretstore"
|
2026-04-13 18:53:36 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 14:56:00 +00:00
|
|
|
func TestRunDoctorUsesCustomManifestCheckWhenProvided(t *testing.T) {
|
|
|
|
|
report := RunDoctor(context.Background(), DoctorOptions{
|
|
|
|
|
ManifestDir: t.TempDir(),
|
|
|
|
|
ManifestCheck: func(context.Context) DoctorResult {
|
|
|
|
|
return DoctorResult{
|
|
|
|
|
Name: "manifest",
|
|
|
|
|
Status: DoctorStatusOK,
|
|
|
|
|
Summary: "manifest is embedded",
|
|
|
|
|
Detail: "embedded:mcp.toml",
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if len(report.Results) != 1 {
|
|
|
|
|
t.Fatalf("result count = %d, want 1", len(report.Results))
|
|
|
|
|
}
|
|
|
|
|
result := report.Results[0]
|
|
|
|
|
if result.Summary != "manifest is embedded" {
|
|
|
|
|
t.Fatalf("summary = %q", result.Summary)
|
|
|
|
|
}
|
|
|
|
|
if result.Detail != "embedded:mcp.toml" {
|
|
|
|
|
t.Fatalf("detail = %q", result.Detail)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-13 18:53:36 +00:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-14 14:53:26 +00:00
|
|
|
|
2026-04-20 07:39:05 +00:00
|
|
|
func TestBitwardenReadyCheckMapsTypedErrorsToActionableDiagnostics(t *testing.T) {
|
|
|
|
|
prev := checkBitwardenReady
|
|
|
|
|
t.Cleanup(func() {
|
|
|
|
|
checkBitwardenReady = prev
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("not logged in", func(t *testing.T) {
|
|
|
|
|
checkBitwardenReady = func(options secretstore.Options) error {
|
|
|
|
|
return fmt.Errorf("%w: run `bw login`", secretstore.ErrBWNotLoggedIn)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result := BitwardenReadyCheck(BitwardenDoctorOptions{})(context.Background())
|
|
|
|
|
if result.Status != DoctorStatusFail {
|
|
|
|
|
t.Fatalf("status = %q, want fail", result.Status)
|
|
|
|
|
}
|
|
|
|
|
if result.Summary != "bitwarden login is required" {
|
|
|
|
|
t.Fatalf("summary = %q", result.Summary)
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(result.Detail, "bw login") {
|
|
|
|
|
t.Fatalf("detail = %q, want login remediation", result.Detail)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("locked", func(t *testing.T) {
|
|
|
|
|
checkBitwardenReady = func(options secretstore.Options) error {
|
|
|
|
|
return fmt.Errorf("%w: run `bw unlock --raw`", secretstore.ErrBWLocked)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result := BitwardenReadyCheck(BitwardenDoctorOptions{})(context.Background())
|
|
|
|
|
if result.Status != DoctorStatusFail {
|
|
|
|
|
t.Fatalf("status = %q, want fail", result.Status)
|
|
|
|
|
}
|
|
|
|
|
if result.Summary != "bitwarden vault is locked or BW_SESSION is missing" {
|
|
|
|
|
t.Fatalf("summary = %q", result.Summary)
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(result.Detail, "bw unlock --raw") {
|
|
|
|
|
t.Fatalf("detail = %q, want unlock remediation", result.Detail)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("ready", func(t *testing.T) {
|
|
|
|
|
checkBitwardenReady = func(options secretstore.Options) error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result := BitwardenReadyCheck(BitwardenDoctorOptions{})(context.Background())
|
|
|
|
|
if result.Status != DoctorStatusOK {
|
|
|
|
|
t.Fatalf("status = %q, want ok", result.Status)
|
|
|
|
|
}
|
|
|
|
|
if result.Summary != "bitwarden CLI is ready" {
|
|
|
|
|
t.Fatalf("summary = %q", result.Summary)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 08:56:15 +00:00
|
|
|
func TestRunDoctorAutoInjectsBitwardenCheckForBitwardenPolicy(t *testing.T) {
|
|
|
|
|
prev := checkBitwardenReady
|
|
|
|
|
t.Cleanup(func() {
|
|
|
|
|
checkBitwardenReady = prev
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
lookupCalled := false
|
|
|
|
|
checkBitwardenReady = func(options secretstore.Options) error {
|
|
|
|
|
if options.Shell != "fish" {
|
|
|
|
|
t.Fatalf("Shell = %q, want fish", options.Shell)
|
|
|
|
|
}
|
|
|
|
|
if options.BitwardenCommand != "bw" {
|
|
|
|
|
t.Fatalf("BitwardenCommand = %q, want bw", options.BitwardenCommand)
|
|
|
|
|
}
|
|
|
|
|
if options.LookupEnv == nil {
|
|
|
|
|
t.Fatal("LookupEnv should be forwarded")
|
|
|
|
|
}
|
|
|
|
|
_, _ = options.LookupEnv("BW_SESSION")
|
|
|
|
|
return fmt.Errorf("%w: run unlock", secretstore.ErrBWLocked)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
report := RunDoctor(context.Background(), DoctorOptions{
|
|
|
|
|
SecretBackendPolicy: secretstore.BackendBitwardenCLI,
|
|
|
|
|
BitwardenOptions: BitwardenDoctorOptions{
|
|
|
|
|
Command: "bw",
|
|
|
|
|
Shell: "fish",
|
|
|
|
|
LookupEnv: func(name string) (string, bool) {
|
|
|
|
|
lookupCalled = true
|
|
|
|
|
return "", false
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
ManifestCheck: func(context.Context) DoctorResult {
|
|
|
|
|
return DoctorResult{Name: "manifest", Status: DoctorStatusOK, Summary: "manifest ok"}
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if !lookupCalled {
|
|
|
|
|
t.Fatal("LookupEnv should be used by auto bitwarden check")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var found *DoctorResult
|
|
|
|
|
for i := range report.Results {
|
|
|
|
|
if report.Results[i].Name == "bitwarden" {
|
|
|
|
|
found = &report.Results[i]
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if found == nil {
|
|
|
|
|
t.Fatalf("report results = %#v, want auto bitwarden check", report.Results)
|
|
|
|
|
}
|
|
|
|
|
if found.Status != DoctorStatusFail {
|
|
|
|
|
t.Fatalf("bitwarden status = %q, want fail", found.Status)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRunDoctorCanDisableAutoBitwardenCheck(t *testing.T) {
|
|
|
|
|
prev := checkBitwardenReady
|
|
|
|
|
t.Cleanup(func() {
|
|
|
|
|
checkBitwardenReady = prev
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
checkBitwardenReady = func(options secretstore.Options) error {
|
|
|
|
|
t.Fatal("checkBitwardenReady should not be called when auto check is disabled")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
report := RunDoctor(context.Background(), DoctorOptions{
|
|
|
|
|
DisableAutoBitwardenCheck: true,
|
|
|
|
|
SecretBackendPolicy: secretstore.BackendBitwardenCLI,
|
|
|
|
|
ManifestCheck: func(context.Context) DoctorResult {
|
|
|
|
|
return DoctorResult{Name: "manifest", Status: DoctorStatusOK, Summary: "manifest ok"}
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if len(report.Results) != 1 {
|
|
|
|
|
t.Fatalf("result count = %d, want 1", len(report.Results))
|
|
|
|
|
}
|
|
|
|
|
if report.Results[0].Name != "manifest" {
|
|
|
|
|
t.Fatalf("result name = %q, want manifest", report.Results[0].Name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 14:53:26 +00:00
|
|
|
func TestRequiredResolvedFieldsCheckReportsSources(t *testing.T) {
|
|
|
|
|
check := RequiredResolvedFieldsCheck(ResolveOptions{
|
|
|
|
|
Fields: []FieldSpec{
|
|
|
|
|
{Name: "base_url", Required: true, ConfigKey: "base_url"},
|
|
|
|
|
{
|
|
|
|
|
Name: "api_token",
|
|
|
|
|
Required: true,
|
|
|
|
|
EnvKey: "MY_API_TOKEN",
|
|
|
|
|
SecretKey: "my-api-token",
|
|
|
|
|
Sources: []ValueSource{SourceEnv, SourceSecret},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Lookup: ResolveLookup(ResolveLookupOptions{
|
|
|
|
|
Env: MapLookup(map[string]string{"MY_API_TOKEN": "env-token"}),
|
|
|
|
|
Config: ConfigMap(map[string]string{"base_url": "https://api.example.com"}),
|
|
|
|
|
Secret: MapLookup(map[string]string{"my-api-token": "secret-token"}),
|
|
|
|
|
}),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
result := check(context.Background())
|
|
|
|
|
if result.Status != DoctorStatusOK {
|
|
|
|
|
t.Fatalf("status = %q, want ok", result.Status)
|
|
|
|
|
}
|
|
|
|
|
for _, needle := range []string{"base_url=config", "api_token=env"} {
|
|
|
|
|
if !strings.Contains(result.Detail, needle) {
|
|
|
|
|
t.Fatalf("detail = %q, want substring %q", result.Detail, needle)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRequiredResolvedFieldsCheckUsesSecretAsFallback(t *testing.T) {
|
|
|
|
|
check := RequiredResolvedFieldsCheck(ResolveOptions{
|
|
|
|
|
Fields: []FieldSpec{
|
|
|
|
|
{
|
|
|
|
|
Name: "api_token",
|
|
|
|
|
Required: true,
|
|
|
|
|
EnvKey: "MY_API_TOKEN",
|
|
|
|
|
SecretKey: "my-api-token",
|
|
|
|
|
Sources: []ValueSource{SourceEnv, SourceSecret},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Lookup: ResolveLookup(ResolveLookupOptions{
|
|
|
|
|
Env: MapLookup(map[string]string{}),
|
|
|
|
|
Secret: MapLookup(map[string]string{"my-api-token": "secret-token"}),
|
|
|
|
|
}),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
result := check(context.Background())
|
|
|
|
|
if result.Status != DoctorStatusOK {
|
|
|
|
|
t.Fatalf("status = %q, want ok", result.Status)
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(result.Detail, "api_token=secret") {
|
|
|
|
|
t.Fatalf("detail = %q, want secret provenance", result.Detail)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRequiredResolvedFieldsCheckFailsWithMissingRequiredField(t *testing.T) {
|
|
|
|
|
check := RequiredResolvedFieldsCheck(ResolveOptions{
|
|
|
|
|
Fields: []FieldSpec{
|
|
|
|
|
{Name: "base_url", Required: true},
|
|
|
|
|
},
|
|
|
|
|
Lookup: ResolveLookup(ResolveLookupOptions{
|
|
|
|
|
Env: MapLookup(map[string]string{}),
|
|
|
|
|
}),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
result := check(context.Background())
|
|
|
|
|
if result.Status != DoctorStatusFail {
|
|
|
|
|
t.Fatalf("status = %q, want fail", result.Status)
|
|
|
|
|
}
|
|
|
|
|
if result.Summary != "required profile values are missing" {
|
|
|
|
|
t.Fatalf("summary = %q", result.Summary)
|
|
|
|
|
}
|
|
|
|
|
if result.Detail != "base_url" {
|
|
|
|
|
t.Fatalf("detail = %q, want base_url", result.Detail)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRequiredResolvedFieldsCheckFormatsLookupErrors(t *testing.T) {
|
|
|
|
|
lookupErr := errors.New("vault unavailable")
|
|
|
|
|
check := RequiredResolvedFieldsCheck(ResolveOptions{
|
|
|
|
|
Fields: []FieldSpec{
|
|
|
|
|
{Name: "api_token", Required: true, Sources: []ValueSource{SourceSecret}},
|
|
|
|
|
},
|
|
|
|
|
Lookup: func(source ValueSource, key string) (string, bool, error) {
|
|
|
|
|
if source == SourceSecret {
|
|
|
|
|
return "", false, lookupErr
|
|
|
|
|
}
|
|
|
|
|
return "", false, nil
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
result := check(context.Background())
|
|
|
|
|
if result.Status != DoctorStatusFail {
|
|
|
|
|
t.Fatalf("status = %q, want fail", result.Status)
|
|
|
|
|
}
|
|
|
|
|
if result.Summary != "cannot resolve profile value \"api_token\"" {
|
|
|
|
|
t.Fatalf("summary = %q", result.Summary)
|
|
|
|
|
}
|
|
|
|
|
for _, needle := range []string{"api_token", "source \"secret\"", "key \"api_token\"", "vault unavailable"} {
|
|
|
|
|
if !strings.Contains(result.Detail, needle) {
|
|
|
|
|
t.Fatalf("detail = %q, want substring %q", result.Detail, needle)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestFormatResolveFieldsErrorWithNilError(t *testing.T) {
|
|
|
|
|
if got := FormatResolveFieldsError(nil); got != "" {
|
|
|
|
|
t.Fatalf("FormatResolveFieldsError(nil) = %q, want empty string", got)
|
|
|
|
|
}
|
|
|
|
|
}
|