package bootstrap import ( "bytes" "context" "errors" "os" "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) } } 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) } } 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 noop := func(_ context.Context, _ Invocation) error { return nil } err := Run(context.Background(), Options{ BinaryName: "my-mcp", Args: []string{"config"}, Stdout: &stdout, Stderr: &stderr, Hooks: Hooks{ConfigShow: noop}, }) if !errors.Is(err, ErrSubcommandRequired) { t.Fatalf("Run error = %v, want ErrSubcommandRequired", err) } if !strings.Contains(err.Error(), "show, test, delete") { t.Fatalf("Run error = %q, want mention of show, test, delete", err) } } 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") } } func TestRunVersionAutoHiddenWithoutHookOrVersionString(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, }) if !errors.Is(err, ErrUnknownCommand) { t.Fatalf("Run error = %v, want ErrUnknownCommand", err) } } func TestRunPrintsGlobalHelp(t *testing.T) { var stdout bytes.Buffer var stderr bytes.Buffer noop := func(_ context.Context, _ Invocation) error { return nil } err := Run(context.Background(), Options{ BinaryName: "my-mcp", Description: "Binaire MCP de test.", Version: "v1.2.3", Args: []string{"help"}, Stdout: &stdout, Stderr: &stderr, Hooks: Hooks{Setup: noop, MCP: noop, ConfigShow: noop, Update: noop}, }) 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) } } } func TestRunPrintsGlobalHelpWhenNoArgs(t *testing.T) { var stdout bytes.Buffer var stderr bytes.Buffer noop := func(_ context.Context, _ Invocation) error { return nil } err := Run(context.Background(), Options{ BinaryName: "my-mcp", Description: "Binaire MCP de test.", Version: "v1.2.3", Args: []string{}, Stdout: &stdout, Stderr: &stderr, Hooks: Hooks{Setup: noop, MCP: noop, ConfigShow: noop, Update: noop}, }) 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) } } } func TestRunPrintsCommandHelp(t *testing.T) { var stdout bytes.Buffer var stderr bytes.Buffer noop := func(_ context.Context, _ Invocation) error { return nil } err := Run(context.Background(), Options{ BinaryName: "my-mcp", Args: []string{"help", "update"}, Stdout: &stdout, Stderr: &stderr, Hooks: Hooks{Update: noop}, }) 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) } } func TestRunPrintsLoginCommandHelp(t *testing.T) { var stdout bytes.Buffer var stderr bytes.Buffer noop := func(_ context.Context, _ Invocation) error { return nil } err := Run(context.Background(), Options{ BinaryName: "my-mcp", Args: []string{"help", "login"}, Stdout: &stdout, Stderr: &stderr, Hooks: Hooks{Login: noop}, }) 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) } } func TestRunPrintsConfigHelp(t *testing.T) { var stdout bytes.Buffer var stderr bytes.Buffer noop := func(_ context.Context, _ Invocation) error { return nil } err := Run(context.Background(), Options{ BinaryName: "my-mcp", Args: []string{"help", "config"}, Stdout: &stdout, Stderr: &stderr, Hooks: Hooks{ConfigShow: noop}, }) if err != nil { t.Fatalf("Run error = %v", err) } text := stdout.String() for _, snippet := range []string{ "my-mcp config ", "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 noop := func(_ context.Context, _ Invocation) error { return nil } err := Run(context.Background(), Options{ BinaryName: "my-mcp", Args: []string{"help", "config", "show"}, Stdout: &stdout, Stderr: &stderr, Hooks: Hooks{ConfigShow: noop}, }) 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 noop := func(_ context.Context, _ Invocation) error { return nil } err := Run(context.Background(), Options{ BinaryName: "my-mcp", Args: []string{"config", "show"}, Stdout: &stdout, Stderr: &stderr, Hooks: Hooks{ConfigTest: noop}, }) 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 noop := func(_ context.Context, _ Invocation) error { return nil } err := Run(context.Background(), Options{ BinaryName: "my-mcp", Args: []string{"config", "sync"}, Stdout: &stdout, Stderr: &stderr, Hooks: Hooks{ConfigShow: noop}, }) if !errors.Is(err, ErrUnknownSubcommand) { t.Fatalf("Run error = %v, want ErrUnknownSubcommand", err) } } 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 noop := func(_ context.Context, _ Invocation) error { return nil } err := Run(context.Background(), Options{ BinaryName: "my-mcp", Aliases: map[string][]string{ "doctor": {CommandConfig, ConfigSubcommandTest}, }, Args: []string{"help", "doctor"}, Stdout: &stdout, Stderr: &stderr, Hooks: Hooks{ConfigTest: noop}, }) 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 noop := func(_ context.Context, _ Invocation) error { return nil } err := Run(context.Background(), Options{ BinaryName: "my-mcp", Aliases: map[string][]string{ "doctor": {CommandConfig, ConfigSubcommandTest}, }, Args: []string{"doctor", "--help"}, Stdout: &stdout, Stderr: &stderr, Hooks: Hooks{ConfigTest: noop}, }) 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 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) } } 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") } } 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) } }