2026-04-14 06:24:24 +00:00
|
|
|
package bootstrap
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"context"
|
|
|
|
|
"errors"
|
2026-04-20 09:36:07 +00:00
|
|
|
"os"
|
2026-04-14 06:24:24 +00:00
|
|
|
"slices"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestRunRoutesSetupHook(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
|
|
|
|
|
var got Invocation
|
|
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Version: "v1.2.3",
|
|
|
|
|
Args: []string{"setup", "--profile", "prod"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
|
|
|
|
Hooks: Hooks{
|
|
|
|
|
Setup: func(_ context.Context, inv Invocation) error {
|
|
|
|
|
got = inv
|
|
|
|
|
return nil
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Run error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if got.Command != CommandSetup {
|
|
|
|
|
t.Fatalf("invocation command = %q, want %q", got.Command, CommandSetup)
|
|
|
|
|
}
|
|
|
|
|
wantArgs := []string{"--profile", "prod"}
|
|
|
|
|
if !slices.Equal(got.Args, wantArgs) {
|
|
|
|
|
t.Fatalf("invocation args = %v, want %v", got.Args, wantArgs)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 10:38:58 +00:00
|
|
|
func TestRunRoutesLoginHook(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
|
|
|
|
|
var got Invocation
|
|
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Version: "v1.2.3",
|
|
|
|
|
Args: []string{"login", "--force"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
|
|
|
|
Hooks: Hooks{
|
|
|
|
|
Login: func(_ context.Context, inv Invocation) error {
|
|
|
|
|
got = inv
|
|
|
|
|
return nil
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Run error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if got.Command != CommandLogin {
|
|
|
|
|
t.Fatalf("invocation command = %q, want %q", got.Command, CommandLogin)
|
|
|
|
|
}
|
|
|
|
|
wantArgs := []string{"--force"}
|
|
|
|
|
if !slices.Equal(got.Args, wantArgs) {
|
|
|
|
|
t.Fatalf("invocation args = %v, want %v", got.Args, wantArgs)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 06:24:24 +00:00
|
|
|
func TestRunReturnsUnknownCommand(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
|
|
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Args: []string{"boom"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
|
|
|
|
})
|
|
|
|
|
if !errors.Is(err, ErrUnknownCommand) {
|
|
|
|
|
t.Fatalf("Run error = %v, want ErrUnknownCommand", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRunReturnsCommandNotConfigured(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
|
2026-05-12 08:37:46 +00:00
|
|
|
noop := func(_ context.Context, _ Invocation) error { return nil }
|
2026-04-14 06:24:24 +00:00
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Args: []string{"config"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
2026-05-12 08:37:46 +00:00
|
|
|
Hooks: Hooks{ConfigShow: noop},
|
2026-04-14 06:24:24 +00:00
|
|
|
})
|
2026-04-14 08:52:36 +00:00
|
|
|
if !errors.Is(err, ErrSubcommandRequired) {
|
|
|
|
|
t.Fatalf("Run error = %v, want ErrSubcommandRequired", err)
|
2026-04-14 06:24:24 +00:00
|
|
|
}
|
2026-04-14 11:55:00 +00:00
|
|
|
if !strings.Contains(err.Error(), "show, test, delete") {
|
|
|
|
|
t.Fatalf("Run error = %q, want mention of show, test, delete", err)
|
|
|
|
|
}
|
2026-04-14 06:24:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRunPrintsVersionByDefault(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
|
|
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Version: "v1.2.3",
|
|
|
|
|
Args: []string{"version"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Run error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if stdout.String() != "v1.2.3\n" {
|
|
|
|
|
t.Fatalf("stdout = %q, want %q", stdout.String(), "v1.2.3\n")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRunVersionHookOverridesDefault(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
|
|
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Args: []string{"version"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
|
|
|
|
Hooks: Hooks{
|
|
|
|
|
Version: func(_ context.Context, inv Invocation) error {
|
|
|
|
|
_, err := inv.Stdout.Write([]byte("custom-version\n"))
|
|
|
|
|
return err
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Run error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if stdout.String() != "custom-version\n" {
|
|
|
|
|
t.Fatalf("stdout = %q, want %q", stdout.String(), "custom-version\n")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 08:37:46 +00:00
|
|
|
func TestRunVersionAutoHiddenWithoutHookOrVersionString(t *testing.T) {
|
2026-04-14 06:24:24 +00:00
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
|
|
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Args: []string{"version"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
|
|
|
|
})
|
2026-05-12 08:37:46 +00:00
|
|
|
if !errors.Is(err, ErrUnknownCommand) {
|
|
|
|
|
t.Fatalf("Run error = %v, want ErrUnknownCommand", err)
|
2026-04-14 06:24:24 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRunPrintsGlobalHelp(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
|
2026-05-12 08:37:46 +00:00
|
|
|
noop := func(_ context.Context, _ Invocation) error { return nil }
|
2026-04-14 06:24:24 +00:00
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Description: "Binaire MCP de test.",
|
2026-05-12 08:37:46 +00:00
|
|
|
Version: "v1.2.3",
|
2026-04-14 06:24:24 +00:00
|
|
|
Args: []string{"help"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
2026-05-12 08:37:46 +00:00
|
|
|
Hooks: Hooks{Setup: noop, MCP: noop, ConfigShow: noop, Update: noop},
|
2026-04-14 06:24:24 +00:00
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Run error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
text := stdout.String()
|
|
|
|
|
for _, snippet := range []string{
|
|
|
|
|
"Usage:",
|
|
|
|
|
"setup",
|
|
|
|
|
"mcp",
|
|
|
|
|
"config",
|
|
|
|
|
"update",
|
|
|
|
|
"version",
|
|
|
|
|
} {
|
|
|
|
|
if !strings.Contains(text, snippet) {
|
|
|
|
|
t.Fatalf("help output missing %q: %s", snippet, text)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 08:52:36 +00:00
|
|
|
func TestRunPrintsGlobalHelpWhenNoArgs(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
|
2026-05-12 08:37:46 +00:00
|
|
|
noop := func(_ context.Context, _ Invocation) error { return nil }
|
2026-04-14 08:52:36 +00:00
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Description: "Binaire MCP de test.",
|
2026-05-12 08:37:46 +00:00
|
|
|
Version: "v1.2.3",
|
2026-04-14 08:52:36 +00:00
|
|
|
Args: []string{},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
2026-05-12 08:37:46 +00:00
|
|
|
Hooks: Hooks{Setup: noop, MCP: noop, ConfigShow: noop, Update: noop},
|
2026-04-14 08:52:36 +00:00
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Run error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
text := stdout.String()
|
|
|
|
|
for _, snippet := range []string{
|
|
|
|
|
"Usage:",
|
|
|
|
|
"setup",
|
|
|
|
|
"mcp",
|
|
|
|
|
"config",
|
|
|
|
|
"update",
|
|
|
|
|
"version",
|
|
|
|
|
} {
|
|
|
|
|
if !strings.Contains(text, snippet) {
|
|
|
|
|
t.Fatalf("help output missing %q: %s", snippet, text)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 06:24:24 +00:00
|
|
|
func TestRunPrintsCommandHelp(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
|
2026-05-12 08:37:46 +00:00
|
|
|
noop := func(_ context.Context, _ Invocation) error { return nil }
|
2026-04-14 06:24:24 +00:00
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Args: []string{"help", "update"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
2026-05-12 08:37:46 +00:00
|
|
|
Hooks: Hooks{Update: noop},
|
2026-04-14 06:24:24 +00:00
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Run error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
text := stdout.String()
|
|
|
|
|
if !strings.Contains(text, "my-mcp update [args]") {
|
|
|
|
|
t.Fatalf("command help output = %q", text)
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(text, "auto-update") {
|
|
|
|
|
t.Fatalf("command help output missing update description: %q", text)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-14 08:52:36 +00:00
|
|
|
|
2026-04-20 10:38:58 +00:00
|
|
|
func TestRunPrintsLoginCommandHelp(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
|
2026-05-12 08:37:46 +00:00
|
|
|
noop := func(_ context.Context, _ Invocation) error { return nil }
|
2026-04-20 10:38:58 +00:00
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Args: []string{"help", "login"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
2026-05-12 08:37:46 +00:00
|
|
|
Hooks: Hooks{Login: noop},
|
2026-04-20 10:38:58 +00:00
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Run error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
text := stdout.String()
|
|
|
|
|
if !strings.Contains(text, "my-mcp login [args]") {
|
|
|
|
|
t.Fatalf("command help output = %q", text)
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(strings.ToLower(text), "bitwarden") {
|
|
|
|
|
t.Fatalf("command help output missing bitwarden description: %q", text)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 08:52:36 +00:00
|
|
|
func TestRunPrintsConfigHelp(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
|
2026-05-12 08:37:46 +00:00
|
|
|
noop := func(_ context.Context, _ Invocation) error { return nil }
|
2026-04-14 08:52:36 +00:00
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Args: []string{"help", "config"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
2026-05-12 08:37:46 +00:00
|
|
|
Hooks: Hooks{ConfigShow: noop},
|
2026-04-14 08:52:36 +00:00
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Run error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
text := stdout.String()
|
|
|
|
|
for _, snippet := range []string{
|
|
|
|
|
"my-mcp config <subcommand>",
|
|
|
|
|
"show",
|
|
|
|
|
"test",
|
|
|
|
|
} {
|
|
|
|
|
if !strings.Contains(text, snippet) {
|
|
|
|
|
t.Fatalf("config help output missing %q: %q", snippet, text)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRunPrintsConfigSubcommandHelp(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
|
2026-05-12 08:37:46 +00:00
|
|
|
noop := func(_ context.Context, _ Invocation) error { return nil }
|
2026-04-14 08:52:36 +00:00
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Args: []string{"help", "config", "show"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
2026-05-12 08:37:46 +00:00
|
|
|
Hooks: Hooks{ConfigShow: noop},
|
2026-04-14 08:52:36 +00:00
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Run error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
text := stdout.String()
|
|
|
|
|
if !strings.Contains(text, "my-mcp config show [args]") {
|
|
|
|
|
t.Fatalf("config subcommand help output = %q", text)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRunRoutesConfigShowHook(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
var got Invocation
|
|
|
|
|
|
|
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Args: []string{"config", "show", "--profile", "prod"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
|
|
|
|
Hooks: Hooks{
|
|
|
|
|
ConfigShow: func(_ context.Context, inv Invocation) error {
|
|
|
|
|
got = inv
|
|
|
|
|
return nil
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Run error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if got.Command != "config show" {
|
|
|
|
|
t.Fatalf("invocation command = %q, want %q", got.Command, "config show")
|
|
|
|
|
}
|
|
|
|
|
wantArgs := []string{"--profile", "prod"}
|
|
|
|
|
if !slices.Equal(got.Args, wantArgs) {
|
|
|
|
|
t.Fatalf("invocation args = %v, want %v", got.Args, wantArgs)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRunConfigShowReturnsCommandNotConfigured(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
|
2026-05-12 08:37:46 +00:00
|
|
|
noop := func(_ context.Context, _ Invocation) error { return nil }
|
2026-04-14 08:52:36 +00:00
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Args: []string{"config", "show"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
2026-05-12 08:37:46 +00:00
|
|
|
Hooks: Hooks{ConfigTest: noop},
|
2026-04-14 08:52:36 +00:00
|
|
|
})
|
|
|
|
|
if !errors.Is(err, ErrCommandNotConfigured) {
|
|
|
|
|
t.Fatalf("Run error = %v, want ErrCommandNotConfigured", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRunConfigReturnsUnknownSubcommand(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
|
2026-05-12 08:37:46 +00:00
|
|
|
noop := func(_ context.Context, _ Invocation) error { return nil }
|
2026-04-14 08:52:36 +00:00
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Args: []string{"config", "sync"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
2026-05-12 08:37:46 +00:00
|
|
|
Hooks: Hooks{ConfigShow: noop},
|
2026-04-14 08:52:36 +00:00
|
|
|
})
|
|
|
|
|
if !errors.Is(err, ErrUnknownSubcommand) {
|
|
|
|
|
t.Fatalf("Run error = %v, want ErrUnknownSubcommand", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-14 14:31:26 +00:00
|
|
|
|
|
|
|
|
func TestRunRoutesAliasToConfigSubcommandHook(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
var got Invocation
|
|
|
|
|
|
|
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Aliases: map[string][]string{
|
|
|
|
|
"doctor": {CommandConfig, ConfigSubcommandTest},
|
|
|
|
|
},
|
|
|
|
|
Args: []string{"doctor", "--profile", "work"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
|
|
|
|
Hooks: Hooks{
|
|
|
|
|
ConfigTest: func(_ context.Context, inv Invocation) error {
|
|
|
|
|
got = inv
|
|
|
|
|
return nil
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Run error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if got.Command != "config test" {
|
|
|
|
|
t.Fatalf("invocation command = %q, want %q", got.Command, "config test")
|
|
|
|
|
}
|
|
|
|
|
wantArgs := []string{"--profile", "work"}
|
|
|
|
|
if !slices.Equal(got.Args, wantArgs) {
|
|
|
|
|
t.Fatalf("invocation args = %v, want %v", got.Args, wantArgs)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRunPrintsAliasHelpFromHelpCommand(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
|
2026-05-12 08:37:46 +00:00
|
|
|
noop := func(_ context.Context, _ Invocation) error { return nil }
|
2026-04-14 14:31:26 +00:00
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Aliases: map[string][]string{
|
|
|
|
|
"doctor": {CommandConfig, ConfigSubcommandTest},
|
|
|
|
|
},
|
|
|
|
|
Args: []string{"help", "doctor"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
2026-05-12 08:37:46 +00:00
|
|
|
Hooks: Hooks{ConfigTest: noop},
|
2026-04-14 14:31:26 +00:00
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Run error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
text := stdout.String()
|
|
|
|
|
if !strings.Contains(text, "my-mcp config test [args]") {
|
|
|
|
|
t.Fatalf("command help output = %q", text)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRunPrintsAliasHelpFromTrailingHelpFlag(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
|
2026-05-12 08:37:46 +00:00
|
|
|
noop := func(_ context.Context, _ Invocation) error { return nil }
|
2026-04-14 14:31:26 +00:00
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Aliases: map[string][]string{
|
|
|
|
|
"doctor": {CommandConfig, ConfigSubcommandTest},
|
|
|
|
|
},
|
|
|
|
|
Args: []string{"doctor", "--help"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
2026-05-12 08:37:46 +00:00
|
|
|
Hooks: Hooks{ConfigTest: noop},
|
2026-04-14 14:31:26 +00:00
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Run error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
text := stdout.String()
|
|
|
|
|
if !strings.Contains(text, "my-mcp config test [args]") {
|
|
|
|
|
t.Fatalf("command help output = %q", text)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-14 15:51:28 +00:00
|
|
|
|
|
|
|
|
func TestRunPrintsAliasesInGlobalHelp(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
|
|
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Aliases: map[string][]string{
|
|
|
|
|
"doctor": {CommandConfig, ConfigSubcommandTest},
|
|
|
|
|
},
|
|
|
|
|
AliasDescriptions: map[string]string{
|
|
|
|
|
"doctor": "Diagnostiquer la configuration locale.",
|
|
|
|
|
},
|
|
|
|
|
Args: []string{"help"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Run error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
text := stdout.String()
|
|
|
|
|
if !strings.Contains(text, "Alias:") {
|
|
|
|
|
t.Fatalf("global help output missing alias section: %q", text)
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(text, `doctor Diagnostiquer la configuration locale. (alias de "config test").`) {
|
|
|
|
|
t.Fatalf("global help output missing doctor alias details: %q", text)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRunRoutesDoctorAliasWhenEnabled(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
var got Invocation
|
|
|
|
|
|
|
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
EnableDoctorAlias: true,
|
|
|
|
|
Args: []string{"doctor", "--profile", "prod"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
|
|
|
|
Hooks: Hooks{
|
|
|
|
|
ConfigTest: func(_ context.Context, inv Invocation) error {
|
|
|
|
|
got = inv
|
|
|
|
|
return nil
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Run error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if got.Command != "config test" {
|
|
|
|
|
t.Fatalf("invocation command = %q, want %q", got.Command, "config test")
|
|
|
|
|
}
|
|
|
|
|
wantArgs := []string{"--profile", "prod"}
|
|
|
|
|
if !slices.Equal(got.Args, wantArgs) {
|
|
|
|
|
t.Fatalf("invocation args = %v, want %v", got.Args, wantArgs)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRunPrintsDoctorAliasInGlobalHelpWhenEnabled(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
|
|
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
EnableDoctorAlias: true,
|
|
|
|
|
Args: []string{"help"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Run error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
text := stdout.String()
|
|
|
|
|
if !strings.Contains(text, "Alias:") {
|
|
|
|
|
t.Fatalf("global help output missing alias section: %q", text)
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(text, `doctor Diagnostiquer la configuration locale. (alias de "config test").`) {
|
|
|
|
|
t.Fatalf("global help output missing default doctor alias details: %q", text)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-20 09:36:07 +00:00
|
|
|
|
|
|
|
|
func TestRunAcceptsGlobalDebugFlagAndRoutesCommand(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
var got Invocation
|
|
|
|
|
|
|
|
|
|
t.Setenv("MCP_FRAMEWORK_BITWARDEN_DEBUG", "")
|
|
|
|
|
|
|
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Args: []string{"--debug", "setup", "--profile", "prod"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
|
|
|
|
Hooks: Hooks{
|
|
|
|
|
Setup: func(_ context.Context, inv Invocation) error {
|
|
|
|
|
got = inv
|
|
|
|
|
return nil
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Run error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if got.Command != CommandSetup {
|
|
|
|
|
t.Fatalf("invocation command = %q, want %q", got.Command, CommandSetup)
|
|
|
|
|
}
|
|
|
|
|
wantArgs := []string{"--profile", "prod"}
|
|
|
|
|
if !slices.Equal(got.Args, wantArgs) {
|
|
|
|
|
t.Fatalf("invocation args = %v, want %v", got.Args, wantArgs)
|
|
|
|
|
}
|
|
|
|
|
if os.Getenv("MCP_FRAMEWORK_BITWARDEN_DEBUG") != "1" {
|
|
|
|
|
t.Fatalf("MCP_FRAMEWORK_BITWARDEN_DEBUG = %q, want %q", os.Getenv("MCP_FRAMEWORK_BITWARDEN_DEBUG"), "1")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRunAcceptsGlobalDebugFlagAfterCommand(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
var got Invocation
|
|
|
|
|
|
|
|
|
|
t.Setenv("MCP_FRAMEWORK_BITWARDEN_DEBUG", "")
|
|
|
|
|
|
|
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Args: []string{"setup", "--debug", "--profile", "prod"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
|
|
|
|
Hooks: Hooks{
|
|
|
|
|
Setup: func(_ context.Context, inv Invocation) error {
|
|
|
|
|
got = inv
|
|
|
|
|
return nil
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Run error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if got.Command != CommandSetup {
|
|
|
|
|
t.Fatalf("invocation command = %q, want %q", got.Command, CommandSetup)
|
|
|
|
|
}
|
|
|
|
|
wantArgs := []string{"--profile", "prod"}
|
|
|
|
|
if !slices.Equal(got.Args, wantArgs) {
|
|
|
|
|
t.Fatalf("invocation args = %v, want %v", got.Args, wantArgs)
|
|
|
|
|
}
|
|
|
|
|
if os.Getenv("MCP_FRAMEWORK_BITWARDEN_DEBUG") != "1" {
|
|
|
|
|
t.Fatalf("MCP_FRAMEWORK_BITWARDEN_DEBUG = %q, want %q", os.Getenv("MCP_FRAMEWORK_BITWARDEN_DEBUG"), "1")
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-12 08:30:11 +00:00
|
|
|
|
|
|
|
|
func TestDisabledCommandReturnsUnknown(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
|
|
|
|
|
|
err := Run(context.Background(), Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
Args: []string{"login"},
|
|
|
|
|
DisabledCommands: []string{"login"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
Stderr: &stderr,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if !errors.Is(err, ErrUnknownCommand) {
|
|
|
|
|
t.Fatalf("err = %v, want ErrUnknownCommand", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestDisabledCommandHiddenFromHelp(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
|
|
|
|
|
err := printGlobalHelp(Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
DisabledCommands: []string{"login", "setup"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("printGlobalHelp error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out := stdout.String()
|
|
|
|
|
if strings.Contains(out, "login") {
|
|
|
|
|
t.Fatalf("help should not contain disabled command %q, got:\n%s", "login", out)
|
|
|
|
|
}
|
|
|
|
|
if strings.Contains(out, "setup") {
|
|
|
|
|
t.Fatalf("help should not contain disabled command %q, got:\n%s", "setup", out)
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(out, "mcp") {
|
|
|
|
|
t.Fatalf("help should contain enabled command %q, got:\n%s", "mcp", out)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestDisabledCommandHelpReturnsUnknown(t *testing.T) {
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
|
|
|
|
|
err := printHelp(Options{
|
|
|
|
|
BinaryName: "my-mcp",
|
|
|
|
|
DisabledCommands: []string{"login"},
|
|
|
|
|
Stdout: &stdout,
|
|
|
|
|
}, "login")
|
|
|
|
|
|
|
|
|
|
if !errors.Is(err, ErrUnknownCommand) {
|
|
|
|
|
t.Fatalf("err = %v, want ErrUnknownCommand", err)
|
|
|
|
|
}
|
|
|
|
|
}
|