mcp-framework/bootstrap/configtest_test.go
thibaud-lclr d23d79b6c1 feat(bootstrap): ajouter DefaultLoginHandler et StandardConfigTestHandler
- DefaultLoginHandler(binaryName) : handler de login Bitwarden prêt à
  l'emploi avec confirmation. Remplace les réimplémentations identiques
  dans chaque MCP.
- StandardConfigTestHandler(opts) : handler de config test standard sans
  ManifestCheck. Accepte ConfigCheck, OpenStore, ConnectivityCheck et
  ExtraChecks.
- ManifestCheck dans RunDoctor devient opt-in : inclus uniquement si
  ManifestDir est fourni (artefact de build, pas de contrainte runtime).
- Supprime le handler mort CommandLogin dans bootstrap.Run, désormais
  remplacé par l'auto-disable et DefaultLoginHandler.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 10:45:51 +02:00

155 lines
4.6 KiB
Go

package bootstrap
import (
"bytes"
"context"
"errors"
"strings"
"testing"
fwcli "forge.lclr.dev/AI/mcp-framework/cli"
"forge.lclr.dev/AI/mcp-framework/secretstore"
)
func TestStandardConfigTestHandlerRendersChecks(t *testing.T) {
var stdout bytes.Buffer
handler := StandardConfigTestHandler(StandardConfigTestOptions{
ConfigCheck: func(context.Context) fwcli.DoctorResult {
return fwcli.DoctorResult{Name: "config", Status: fwcli.DoctorStatusOK, Summary: "config ok"}
},
ConnectivityCheck: func(context.Context) fwcli.DoctorResult {
return fwcli.DoctorResult{Name: "connectivity", Status: fwcli.DoctorStatusOK, Summary: "reachable"}
},
})
err := handler(context.Background(), Invocation{Command: CommandConfig, Stdout: &stdout})
if err != nil {
t.Fatalf("handler returned error: %v", err)
}
out := stdout.String()
if !strings.Contains(out, "[OK] config") {
t.Fatalf("stdout = %q, want [OK] config", out)
}
if !strings.Contains(out, "[OK] connectivity") {
t.Fatalf("stdout = %q, want [OK] connectivity", out)
}
if strings.Contains(out, "manifest") {
t.Fatalf("stdout should not contain manifest check, got:\n%s", out)
}
}
func TestStandardConfigTestHandlerIncludesSecretStoreCheck(t *testing.T) {
var stdout bytes.Buffer
handler := StandardConfigTestHandler(StandardConfigTestOptions{
OpenStore: func() (secretstore.Store, error) {
return secretstore.Open(secretstore.Options{
BackendPolicy: secretstore.BackendEnvOnly,
LookupEnv: func(string) (string, bool) { return "", false },
})
},
})
err := handler(context.Background(), Invocation{Command: CommandConfig, Stdout: &stdout})
if err != nil {
t.Fatalf("handler returned error: %v", err)
}
if !strings.Contains(stdout.String(), "secret-store") {
t.Fatalf("stdout = %q, want secret-store check", stdout.String())
}
}
func TestStandardConfigTestHandlerReturnsErrorOnFailure(t *testing.T) {
var stdout bytes.Buffer
handler := StandardConfigTestHandler(StandardConfigTestOptions{
ConnectivityCheck: func(context.Context) fwcli.DoctorResult {
return fwcli.DoctorResult{Name: "connectivity", Status: fwcli.DoctorStatusFail, Summary: "unreachable"}
},
})
err := handler(context.Background(), Invocation{Command: CommandConfig, Stdout: &stdout})
if err == nil {
t.Fatal("handler should return error when checks fail")
}
if !strings.Contains(err.Error(), "config checks failed") {
t.Fatalf("err = %q, want 'config checks failed'", err.Error())
}
if !strings.Contains(stdout.String(), "[FAIL] connectivity") {
t.Fatalf("stdout = %q, want [FAIL] connectivity", stdout.String())
}
}
func TestStandardConfigTestHandlerOmitsManifestCheck(t *testing.T) {
var stdout bytes.Buffer
handler := StandardConfigTestHandler(StandardConfigTestOptions{
ExtraChecks: []fwcli.DoctorCheck{
func(context.Context) fwcli.DoctorResult {
return fwcli.DoctorResult{Name: "custom", Status: fwcli.DoctorStatusOK, Summary: "ok"}
},
},
})
err := handler(context.Background(), Invocation{Command: CommandConfig, Stdout: &stdout})
if err != nil {
t.Fatalf("handler returned error: %v", err)
}
if strings.Contains(stdout.String(), "manifest") {
t.Fatalf("manifest check should not appear, got:\n%s", stdout.String())
}
}
func TestStandardConfigTestHandlerRunsViaBootstrap(t *testing.T) {
var stdout bytes.Buffer
openCalled := false
err := Run(context.Background(), Options{
BinaryName: "my-mcp",
Args: []string{"config", "test"},
Stdout: &stdout,
Hooks: Hooks{
ConfigTest: StandardConfigTestHandler(StandardConfigTestOptions{
OpenStore: func() (secretstore.Store, error) {
openCalled = true
return secretstore.Open(secretstore.Options{
BackendPolicy: secretstore.BackendEnvOnly,
LookupEnv: func(string) (string, bool) { return "", false },
})
},
}),
},
})
if err != nil {
t.Fatalf("Run error = %v", err)
}
if !openCalled {
t.Fatal("OpenStore should have been called")
}
if !strings.Contains(stdout.String(), "secret-store") {
t.Fatalf("stdout = %q, want secret-store in output", stdout.String())
}
}
func TestStandardConfigTestHandlerSecretStoreFailurePropagates(t *testing.T) {
var stdout bytes.Buffer
storeErr := errors.New("bitwarden unavailable")
handler := StandardConfigTestHandler(StandardConfigTestOptions{
OpenStore: func() (secretstore.Store, error) {
return nil, storeErr
},
})
err := handler(context.Background(), Invocation{Command: CommandConfig, Stdout: &stdout})
if err == nil {
t.Fatal("handler should return error when store fails")
}
if !strings.Contains(stdout.String(), "[FAIL] secret-store") {
t.Fatalf("stdout = %q, want [FAIL] secret-store", stdout.String())
}
}