package scaffold import ( "errors" "go/parser" "go/token" "os" "path/filepath" "slices" "strings" "testing" ) func TestGenerateCreatesRecommendedSkeleton(t *testing.T) { target := filepath.Join(t.TempDir(), "my-mcp") result, err := Generate(Options{ TargetDir: target, ModulePath: "example.com/acme/my-mcp", BinaryName: "my-mcp", Description: "Client MCP interne", DefaultProfile: "prod", Profiles: []string{"dev", "prod"}, }) if err != nil { t.Fatalf("Generate returned error: %v", err) } if result.Root != target { t.Fatalf("result root = %q, want %q", result.Root, target) } wantFiles := []string{ ".gitignore", "README.md", "cmd/my-mcp/main.go", "go.mod", "install.sh", "internal/app/app.go", "mcp.toml", } if !slices.Equal(result.Files, wantFiles) { t.Fatalf("result files = %v, want %v", result.Files, wantFiles) } for _, path := range wantFiles { if _, err := os.Stat(filepath.Join(target, filepath.FromSlash(path))); err != nil { t.Fatalf("generated file %q missing: %v", path, err) } } mainGo, err := os.ReadFile(filepath.Join(target, "cmd", "my-mcp", "main.go")) if err != nil { t.Fatalf("ReadFile main.go: %v", err) } if !strings.Contains(string(mainGo), "\"example.com/acme/my-mcp/internal/app\"") { t.Fatalf("main.go does not import internal app package") } if _, err := parser.ParseFile(token.NewFileSet(), "main.go", mainGo, parser.AllErrors); err != nil { t.Fatalf("generated main.go is invalid Go: %v", err) } appGo, err := os.ReadFile(filepath.Join(target, "internal", "app", "app.go")) if err != nil { t.Fatalf("ReadFile app.go: %v", err) } for _, snippet := range []string{ "config.NewStore[Profile]", "secretstore.Open(secretstore.Options", "secretstore.EnsureBitwardenSessionEnv", "func (r Runtime) ensureBitwardenSession() error {", "\\033[31m", "secretstore.LoginBitwarden", "update.Run", "manifest.LoadDefaultOrEmbedded", "bootstrap.Run", "os.Executable()", "errors.Is(err, os.ErrNotExist)", `var embeddedManifest = `, "ManifestSource", "ManifestCheck: r.manifestDoctorCheck()", "SecretBackendPolicy: r.activeBackendPolicy()", "Login: r.runLogin,", "func (r Runtime) runLogin(_ context.Context, inv bootstrap.Invocation) error {", "secretStoreFactory := memoizeSecretStoreFactory(r.openSecretStore)", "func memoizeSecretStoreFactory(factory func() (secretstore.Store, error)) func() (secretstore.Store, error) {", "cli.WriteSetupSecretVerified", } { if !strings.Contains(string(appGo), snippet) { t.Fatalf("app.go missing snippet %q", snippet) } } if _, err := parser.ParseFile(token.NewFileSet(), "app.go", appGo, parser.AllErrors); err != nil { t.Fatalf("generated app.go is invalid Go: %v", err) } manifestContent, err := os.ReadFile(filepath.Join(target, "mcp.toml")) if err != nil { t.Fatalf("ReadFile mcp.toml: %v", err) } for _, snippet := range []string{ "binary_name = \"my-mcp\"", "[update]", "checksum_required = true", "signature_asset_name = \"{asset}.sig\"", "signature_required = false", "[secret_store]", "[environment]", "[profiles]", "backend_policy = \"auto\"", } { if !strings.Contains(string(manifestContent), snippet) { t.Fatalf("mcp.toml missing snippet %q", snippet) } } readme, err := os.ReadFile(filepath.Join(target, "README.md")) if err != nil { t.Fatalf("ReadFile README.md: %v", err) } for _, snippet := range []string{ "Arborescence générée", "go run ./cmd/my-mcp setup", "curl -fsSL https://///raw/branch/main/install.sh | bash", "internal/app/app.go", } { if !strings.Contains(string(readme), snippet) { t.Fatalf("README missing snippet %q", snippet) } } installScriptPath := filepath.Join(target, "install.sh") installScript, err := os.ReadFile(installScriptPath) if err != nil { t.Fatalf("ReadFile install.sh: %v", err) } for _, snippet := range []string{ "#!/usr/bin/env bash", `MODULE_PATH="example.com/acme/my-mcp"`, `DEFAULT_RELEASE_REPOSITORY="org/my-mcp"`, `load_release_config_from_manifest`, `resolve_latest_release_url()`, `curl_download "$release_url" "$release_json" "json"`, `asset_name="$(resolve_asset_name "$goos" "$goarch")"`, `Reinstaller depuis la derniere release ? (y/N)`, "MCP Install Wizard", `menu_select() {`, `Utilise ↑/↓ puis Entrée.`, `Configurer le MCP maintenant ?`, `claude mcp add \`, `--transport stdio \`, `--scope "$claude_scope" \`, `--env "${PROFILE_ENV}=${PROFILE_VALUE}" \`, `codex mcp add \`, `Dossier projet cible pour .codex/config.toml`, `[mcp_servers.%s]`, `"${PROFILE_ENV}=${PROFILE_VALUE}"`, } { if !strings.Contains(string(installScript), snippet) { t.Fatalf("install.sh missing snippet %q", snippet) } } info, err := os.Stat(installScriptPath) if err != nil { t.Fatalf("Stat install.sh: %v", err) } if info.Mode().Perm() != 0o755 { t.Fatalf("install.sh mode = %o, want 755", info.Mode().Perm()) } } func TestGenerateUsesDefaultsFromTargetDirectory(t *testing.T) { target := filepath.Join(t.TempDir(), "super-agent-mcp") _, err := Generate(Options{TargetDir: target}) if err != nil { t.Fatalf("Generate returned error: %v", err) } goModContent, err := os.ReadFile(filepath.Join(target, "go.mod")) if err != nil { t.Fatalf("ReadFile go.mod: %v", err) } if !strings.Contains(string(goModContent), "module example.com/super-agent-mcp") { t.Fatalf("go.mod should contain default module path") } manifestContent, err := os.ReadFile(filepath.Join(target, "mcp.toml")) if err != nil { t.Fatalf("ReadFile mcp.toml: %v", err) } for _, snippet := range []string{ "binary_name = \"super-agent-mcp\"", "SUPER_AGENT_MCP_PROFILE", "SUPER_AGENT_MCP_API_TOKEN", } { if !strings.Contains(string(manifestContent), snippet) { t.Fatalf("mcp.toml missing snippet %q", snippet) } } } func TestGenerateFailsWhenFileAlreadyExistsWithoutOverwrite(t *testing.T) { target := t.TempDir() readmePath := filepath.Join(target, "README.md") if err := os.WriteFile(readmePath, []byte("pre-existing"), 0o644); err != nil { t.Fatalf("WriteFile README.md: %v", err) } _, err := Generate(Options{TargetDir: target}) if !errors.Is(err, ErrFileExists) { t.Fatalf("Generate error = %v, want ErrFileExists", err) } } func TestGenerateOverwritesExistingFilesWhenRequested(t *testing.T) { target := t.TempDir() readmePath := filepath.Join(target, "README.md") if err := os.WriteFile(readmePath, []byte("pre-existing"), 0o644); err != nil { t.Fatalf("WriteFile README.md: %v", err) } _, err := Generate(Options{TargetDir: target, Overwrite: true}) if err != nil { t.Fatalf("Generate returned error: %v", err) } readmeContent, err := os.ReadFile(readmePath) if err != nil { t.Fatalf("ReadFile README.md: %v", err) } if !strings.Contains(string(readmeContent), "Démarrage rapide") { t.Fatalf("README should be overwritten with scaffold content") } } func TestGenerateRequiresTargetDirectory(t *testing.T) { _, err := Generate(Options{}) if !errors.Is(err, ErrTargetDirRequired) { t.Fatalf("Generate error = %v, want ErrTargetDirRequired", err) } }