package main import ( "os" "os/exec" "path/filepath" "strconv" "strings" "testing" ) func TestCheckSafetySkipsRPCCheckForPluginWithoutRPCs(t *testing.T) { pluginRoot := t.TempDir() sdkVersion, err := currentCMSCoreSDKVersion() if err != nil { t.Fatalf("currentCMSCoreSDKVersion: %v", err) } writeTestFile(t, filepath.Join(pluginRoot, "plugin.mod"), "[plugin]\nname = \"example\"\nversion = \"1.0.0\"\n", 0644) writeTestFile(t, filepath.Join(pluginRoot, "go.mod"), "module example.com/plugin\n\ngo 1.26.2\n\nrequire git.dev.alexdunmow.com/block/core "+sdkVersion+"\n", 0644) writeTestFile(t, filepath.Join(pluginRoot, "main.go"), "package example\n", 0644) cwd, err := os.Getwd() if err != nil { t.Fatalf("getwd: %v", err) } cmd := exec.Command("go", "run", ".", pluginRoot, "--verbose") cmd.Dir = cwd output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("go run check-safety: %v\n%s", err, output) } got := string(output) if !strings.Contains(got, "SKIP 2 ") { t.Fatalf("output missing Check 2 skip line:\n%s", got) } if !strings.Contains(got, "no proto-backed RPC procedures found in scanned target(s)") { t.Fatalf("output missing no-RPC plugin skip:\n%s", got) } } func TestCheckSafetyTypechecksExternalPluginFrontendWithWorkspaceDeps(t *testing.T) { pluginRoot := t.TempDir() pluginWebDir := filepath.Join(pluginRoot, "web") sdkVersion, err := currentCMSCoreSDKVersion() if err != nil { t.Fatalf("currentCMSCoreSDKVersion: %v", err) } realRepoRoot := blockNinjaRepoRoot() relESLintConfig, err := filepath.Rel(pluginWebDir, filepath.Join(realRepoRoot, "web", "eslint.config.js")) if err != nil { t.Fatalf("rel eslint config: %v", err) } writeTestFile(t, filepath.Join(pluginRoot, "plugin.mod"), "[plugin]\nname = \"example\"\nversion = \"1.0.0\"\n", 0644) writeTestFile(t, filepath.Join(pluginRoot, "go.mod"), "module example.com/plugin\n\ngo 1.26.2\n\nrequire git.dev.alexdunmow.com/block/core "+sdkVersion+"\n", 0644) writeTestFile(t, filepath.Join(pluginRoot, "main.go"), "package example\n", 0644) writeTestFile(t, filepath.Join(pluginWebDir, "package.json"), "{\n \"name\": \"external-plugin-test\",\n \"private\": true,\n \"type\": \"module\",\n \"dependencies\": {\n \"@block-ninja/ui\": \"workspace:*\"\n }\n}\n", 0644) writeTestFile(t, filepath.Join(pluginWebDir, "tsconfig.json"), "{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n \"jsx\": \"react-jsx\",\n \"strict\": true,\n \"skipLibCheck\": true,\n \"noEmit\": true\n },\n \"include\": [\"*.tsx\", \"*.ts\"]\n}\n", 0644) writeTestFile(t, filepath.Join(pluginWebDir, "eslint.config.js"), "import config from "+strconv.Quote(filepath.ToSlash(relESLintConfig))+"\n\nexport default config\n", 0644) writeTestFile(t, filepath.Join(pluginWebDir, "editor.tsx"), "import { Separator } from '@block-ninja/ui'\n\nexport function Editor() {\n\treturn \n}\n", 0644) cwd, err := os.Getwd() if err != nil { t.Fatalf("getwd: %v", err) } cmd := exec.Command("go", "run", ".", pluginRoot, "--verbose") cmd.Dir = cwd output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("go run check-safety: %v\n%s", err, output) } got := string(output) if !strings.Contains(got, "Prettier, ESLint, and TypeScript checks clean for 1 frontend target") { t.Fatalf("output missing successful frontend typecheck result:\n%s", got) } if strings.Contains(got, "Cannot find module '@block-ninja/ui'") { t.Fatalf("output still shows unresolved workspace dependency:\n%s", got) } } func TestCheckSafetyFailsStandalonePluginImportsFromBlockNinjaCMS(t *testing.T) { pluginRoot := t.TempDir() writeTestFile(t, filepath.Join(pluginRoot, "plugin.mod"), "[plugin]\nname = \"example\"\nversion = \"1.0.0\"\n", 0644) writeTestFile(t, filepath.Join(pluginRoot, "main.go"), `package example import "git.dev.alexdunmow.com/block/cms/internal/helpers" func Example() string { return helpers.Slugify("danger") } `, 0644) cwd, err := os.Getwd() if err != nil { t.Fatalf("getwd: %v", err) } cmd := exec.Command("go", "run", ".", pluginRoot) cmd.Dir = cwd output, err := cmd.CombinedOutput() if err == nil { t.Fatalf("expected go run check-safety to fail\n%s", output) } got := string(output) if !strings.Contains(got, "FAIL 2c ") { t.Fatalf("output missing Check 2c fail line:\n%s", got) } if !strings.Contains(got, "imports BlockNinja CMS package") { t.Fatalf("output missing forbidden BlockNinja import message:\n%s", got) } } func TestCheckSafetyFailsStandalonePluginReplaceDirective(t *testing.T) { pluginRoot := t.TempDir() sdkVersion, err := currentCMSCoreSDKVersion() if err != nil { t.Fatalf("currentCMSCoreSDKVersion: %v", err) } writeTestFile(t, filepath.Join(pluginRoot, "plugin.mod"), "[plugin]\nname = \"example\"\nversion = \"1.0.0\"\n", 0644) writeTestFile(t, filepath.Join(pluginRoot, "go.mod"), "module example.com/plugin\n\ngo 1.26.2\n\nrequire git.dev.alexdunmow.com/block/core "+sdkVersion+"\n\nreplace git.dev.alexdunmow.com/block/core => ../block-core\n", 0644) writeTestFile(t, filepath.Join(pluginRoot, "main.go"), "package example\n", 0644) cwd, err := os.Getwd() if err != nil { t.Fatalf("getwd: %v", err) } cmd := exec.Command("go", "run", ".", pluginRoot) cmd.Dir = cwd output, err := cmd.CombinedOutput() if err == nil { t.Fatalf("expected go run check-safety to fail\n%s", output) } got := string(output) if !strings.Contains(got, "replace directive present") { t.Fatalf("output missing replace violation detail:\n%s", got) } } func TestCheckSafetyFailsStandalonePluginSQLCUUIDStringOverrides(t *testing.T) { pluginRoot := t.TempDir() sdkVersion, err := currentCMSCoreSDKVersion() if err != nil { t.Fatalf("currentCMSCoreSDKVersion: %v", err) } writeTestFile(t, filepath.Join(pluginRoot, "plugin.mod"), "[plugin]\nname = \"example\"\nversion = \"1.0.0\"\n", 0644) writeTestFile(t, filepath.Join(pluginRoot, "go.mod"), "module example.com/plugin\n\ngo 1.26.2\n\nrequire git.dev.alexdunmow.com/block/core "+sdkVersion+"\n", 0644) writeTestFile(t, filepath.Join(pluginRoot, "main.go"), "package example\n", 0644) writeTestFile(t, filepath.Join(pluginRoot, "sqlc.yaml"), `version: "2" sql: - engine: "postgresql" gen: go: overrides: - db_type: "uuid" go_type: "string" - db_type: "uuid" nullable: true go_type: type: "string" pointer: true `, 0644) cwd, err := os.Getwd() if err != nil { t.Fatalf("getwd: %v", err) } cmd := exec.Command("go", "run", ".", pluginRoot) cmd.Dir = cwd output, err := cmd.CombinedOutput() if err == nil { t.Fatalf("expected go run check-safety to fail\n%s", output) } got := string(output) if !strings.Contains(got, "FAIL 2d ") { t.Fatalf("output missing Check 2d fail line:\n%s", got) } if !strings.Contains(got, "github.com/google/uuid.UUID") { t.Fatalf("output missing UUID remediation:\n%s", got) } if !strings.Contains(got, "github.com/google/uuid.NullUUID") { t.Fatalf("output missing NullUUID remediation:\n%s", got) } } func TestCheckSafetyWarnsOnStandalonePluginGoAnyUsage(t *testing.T) { pluginRoot := t.TempDir() sdkVersion, err := currentCMSCoreSDKVersion() if err != nil { t.Fatalf("currentCMSCoreSDKVersion: %v", err) } writeTestFile(t, filepath.Join(pluginRoot, "plugin.mod"), "[plugin]\nname = \"example\"\nversion = \"1.0.0\"\n", 0644) writeTestFile(t, filepath.Join(pluginRoot, "go.mod"), "module example.com/plugin\n\ngo 1.26.2\n\nrequire git.dev.alexdunmow.com/block/core "+sdkVersion+"\n", 0644) writeTestFile(t, filepath.Join(pluginRoot, "main.go"), `package example type Payload struct { Value any } `, 0644) cwd, err := os.Getwd() if err != nil { t.Fatalf("getwd: %v", err) } // The default any check is scoped to the unstaged diff; the temp plugin dir // is not a git repo, so --all-any is required to surface the warning. cmd := exec.Command("go", "run", ".", pluginRoot, "--all-any") cmd.Dir = cwd output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("expected go run check-safety to pass with warning: %v\n%s", err, output) } got := string(output) if !strings.Contains(got, "WARN 2e ") { t.Fatalf("output missing Check 2e warn line:\n%s", got) } if !strings.Contains(got, "[go]") { t.Fatalf("output missing Go any warning:\n%s", got) } } func TestCheckSafetyFailsStandalonePluginSQLCCompile(t *testing.T) { pluginRoot := t.TempDir() sdkVersion, err := currentCMSCoreSDKVersion() if err != nil { t.Fatalf("currentCMSCoreSDKVersion: %v", err) } writeTestFile(t, filepath.Join(pluginRoot, "plugin.mod"), "[plugin]\nname = \"example\"\nversion = \"1.0.0\"\n", 0644) writeTestFile(t, filepath.Join(pluginRoot, "go.mod"), "module example.com/plugin\n\ngo 1.26.2\n\nrequire git.dev.alexdunmow.com/block/core "+sdkVersion+"\n", 0644) writeTestFile(t, filepath.Join(pluginRoot, "main.go"), "package example\n", 0644) writeTestFile(t, filepath.Join(pluginRoot, "sqlc.yaml"), `version: "2" sql: - engine: "postgresql" queries: "sql/queries" schema: "sql/schema.sql" gen: go: package: "db" out: "db" `, 0644) cwd, err := os.Getwd() if err != nil { t.Fatalf("getwd: %v", err) } cmd := exec.Command("go", "run", ".", pluginRoot) cmd.Dir = cwd output, err := cmd.CombinedOutput() if err == nil { t.Fatalf("expected go run check-safety to fail\n%s", output) } got := string(output) if !strings.Contains(got, "FAIL 2f ") { t.Fatalf("output missing Check 2f fail line:\n%s", got) } if !strings.Contains(got, "[sqlc compile]") { t.Fatalf("output missing sqlc compile stage:\n%s", got) } } func TestRunFrontendAutoFixAndLintFailsTypeScriptTargetsAtTSC(t *testing.T) { repoRoot, sourceDir := setupFrontendLintTestPackage(t, frontendLintTestOptions{ withTSConfig: true, tscScript: "#!/bin/sh\n" + "echo \"src/index.ts(1,1): error TS2322: Type 'string' is not assignable to type 'number'.\" >&2\n" + "exit 1\n", }) completed, skipped, failures, err := runFrontendAutoFixAndLint(repoRoot, []frontendScanTarget{ {dir: sourceDir, label: "frontend/src"}, }) if err != nil { t.Fatalf("runFrontendAutoFixAndLint() error = %v", err) } if len(completed) != 0 { t.Fatalf("completed = %v, want none", completed) } if len(skipped) != 0 { t.Fatalf("skipped = %v, want none", skipped) } if len(failures) != 1 { t.Fatalf("failures = %d, want 1", len(failures)) } if failures[0].stage != "tsc --noEmit" { t.Fatalf("failure stage = %q, want %q", failures[0].stage, "tsc --noEmit") } if !strings.Contains(failures[0].output, "TS2322") { t.Fatalf("failure output = %q, want TS2322", failures[0].output) } } func TestRunFrontendAutoFixAndLintFailsWhenTypeScriptTargetHasNoTSConfig(t *testing.T) { repoRoot, sourceDir := setupFrontendLintTestPackage(t, frontendLintTestOptions{}) completed, skipped, failures, err := runFrontendAutoFixAndLint(repoRoot, []frontendScanTarget{ {dir: sourceDir, label: "frontend/src"}, }) if err != nil { t.Fatalf("runFrontendAutoFixAndLint() error = %v", err) } if len(completed) != 0 { t.Fatalf("completed = %v, want none", completed) } if len(skipped) != 0 { t.Fatalf("skipped = %v, want none", skipped) } if len(failures) != 1 { t.Fatalf("failures = %d, want 1", len(failures)) } if failures[0].stage != "tsc --noEmit" { t.Fatalf("failure stage = %q, want %q", failures[0].stage, "tsc --noEmit") } if !strings.Contains(failures[0].output, "no tsconfig.json was located") { t.Fatalf("failure output = %q, want missing tsconfig message", failures[0].output) } } func TestRunFrontendAutoFixAndLintRunsTSCBeforePrettierAndESLint(t *testing.T) { repoRoot, sourceDir := setupFrontendLintTestPackage(t, frontendLintTestOptions{ withTSConfig: true, tscScript: "#!/bin/sh\n" + "echo \"src/index.ts(1,1): error TS9999: forced type failure.\" >&2\n" + "exit 1\n", }) packageRoot := filepath.Dir(sourceDir) prettierMarker := filepath.Join(packageRoot, "prettier-ran") eslintMarker := filepath.Join(packageRoot, "eslint-ran") writeExecutable(t, filepath.Join(packageRoot, "node_modules", ".bin", "prettier"), "#!/bin/sh\n"+ "echo ran > "+shellQuote(prettierMarker)+"\n"+ "exit 0\n") writeExecutable(t, filepath.Join(packageRoot, "node_modules", ".bin", "eslint"), "#!/bin/sh\n"+ "echo ran > "+shellQuote(eslintMarker)+"\n"+ "exit 0\n") _, _, failures, err := runFrontendAutoFixAndLint(repoRoot, []frontendScanTarget{ {dir: sourceDir, label: "frontend/src"}, }) if err != nil { t.Fatalf("runFrontendAutoFixAndLint() error = %v", err) } if len(failures) != 1 { t.Fatalf("failures = %d, want 1", len(failures)) } if failures[0].stage != "tsc --noEmit" { t.Fatalf("failure stage = %q, want %q", failures[0].stage, "tsc --noEmit") } if _, err := os.Stat(prettierMarker); !os.IsNotExist(err) { t.Fatalf("prettier marker exists, want TypeScript failure to short-circuit before prettier") } if _, err := os.Stat(eslintMarker); !os.IsNotExist(err) { t.Fatalf("eslint marker exists, want TypeScript failure to short-circuit before eslint") } } func TestRunFrontendAutoFixAndLintUsesSyntheticWorkspaceForPluginTargets(t *testing.T) { repoRoot, sourceDir := setupFrontendLintTestPackage(t, frontendLintTestOptions{ withTSConfig: true, }) hostNodeModules := filepath.Join(repoRoot, "web", "node_modules") if err := os.MkdirAll(hostNodeModules, 0755); err != nil { t.Fatalf("mkdir host node_modules: %v", err) } hostBinDir := filepath.Join(hostNodeModules, ".bin") if err := os.MkdirAll(hostBinDir, 0755); err != nil { t.Fatalf("mkdir host bin dir: %v", err) } writeExecutable(t, filepath.Join(hostBinDir, "tsc"), "#!/bin/sh\n"+ "if [ ! -e node_modules/react ]; then echo \"missing plugin dep\" >&2; exit 1; fi\n"+ "if [ ! -e node_modules/@block-ninja/api ]; then echo \"missing blockninja api\" >&2; exit 1; fi\n"+ "if [ ! -e package.json ]; then echo \"missing package\" >&2; exit 1; fi\n"+ "exit 0\n") writeExecutable(t, filepath.Join(hostBinDir, "prettier"), "#!/bin/sh\nexit 0\n") writeExecutable(t, filepath.Join(hostBinDir, "eslint"), "#!/bin/sh\nexit 0\n") if err := os.MkdirAll(filepath.Join(hostNodeModules, "@block-ninja", "api"), 0755); err != nil { t.Fatalf("mkdir host @block-ninja/api: %v", err) } packageRoot := filepath.Dir(sourceDir) if err := os.MkdirAll(filepath.Join(packageRoot, "node_modules"), 0755); err != nil { t.Fatalf("mkdir local node_modules: %v", err) } writeExecutable(t, filepath.Join(packageRoot, "node_modules", "react"), "#!/bin/sh\nexit 0\n") completed, skipped, failures, err := runFrontendAutoFixAndLint(repoRoot, []frontendScanTarget{ {dir: sourceDir, label: "plugin/web/src", pluginRules: true}, }) if err != nil { t.Fatalf("runFrontendAutoFixAndLint() error = %v", err) } if len(skipped) != 0 { t.Fatalf("skipped = %v, want none", skipped) } if len(failures) != 0 { t.Fatalf("failures = %v, want none", failures) } if len(completed) != 1 { t.Fatalf("completed = %v, want one completed target", completed) } } func TestRunFrontendAutoFixAndLintCreatesDefaultConfigsForManagedStandaloneTargets(t *testing.T) { repoRoot, sourceDir := setupFrontendLintTestPackage(t, frontendLintTestOptions{ withTSConfig: true, }) packageRoot := filepath.Dir(sourceDir) if err := os.Remove(filepath.Join(packageRoot, "eslint.config.js")); err != nil { t.Fatalf("remove eslint config: %v", err) } completed, skipped, failures, err := runFrontendAutoFixAndLint(repoRoot, []frontendScanTarget{ {dir: sourceDir, label: "managed/frontend/src", managedConfigs: true}, }) if err != nil { t.Fatalf("runFrontendAutoFixAndLint() error = %v", err) } if len(skipped) != 0 { t.Fatalf("skipped = %v, want none", skipped) } if len(failures) != 0 { t.Fatalf("failures = %v, want none", failures) } if len(completed) != 1 { t.Fatalf("completed = %v, want one completed target", completed) } if _, err := os.Stat(filepath.Join(packageRoot, "eslint.config.js")); err != nil { t.Fatalf("expected generated eslint config: %v", err) } if _, err := os.Stat(filepath.Join(packageRoot, "prettier.config.cjs")); err != nil { t.Fatalf("expected generated prettier config: %v", err) } } func TestRunFrontendAutoFixAndLintOmitsExtForFlatConfigTargets(t *testing.T) { repoRoot, sourceDir := setupFrontendLintTestPackage(t, frontendLintTestOptions{ withTSConfig: true, tscScript: "#!/bin/sh\nexit 0\n", }) packageRoot := filepath.Dir(sourceDir) writeExecutable(t, filepath.Join(packageRoot, "node_modules", ".bin", "eslint"), "#!/bin/sh\n"+ "for arg in \"$@\"; do\n"+ " if [ \"$arg\" = \"--ext\" ]; then\n"+ " echo \"unexpected --ext\" >&2\n"+ " exit 1\n"+ " fi\n"+ "done\n"+ "exit 0\n") completed, skipped, failures, err := runFrontendAutoFixAndLint(repoRoot, []frontendScanTarget{ {dir: sourceDir, label: "frontend/src"}, }) if err != nil { t.Fatalf("runFrontendAutoFixAndLint() error = %v", err) } if len(skipped) != 0 { t.Fatalf("skipped = %v, want none", skipped) } if len(failures) != 0 { t.Fatalf("failures = %v, want none", failures) } if len(completed) != 1 { t.Fatalf("completed = %v, want one completed target", completed) } } func TestRunFrontendAutoFixAndLintPrefersWorkspaceESLintForManagedTargets(t *testing.T) { repoRoot, sourceDir := setupFrontendLintTestPackage(t, frontendLintTestOptions{ withTSConfig: true, tscScript: "#!/bin/sh\nexit 0\n", }) packageRoot := filepath.Dir(sourceDir) if err := os.Remove(filepath.Join(packageRoot, "eslint.config.js")); err != nil { t.Fatalf("remove eslint config: %v", err) } writeExecutable(t, filepath.Join(packageRoot, "node_modules", ".bin", "eslint"), "#!/bin/sh\n"+ "echo local-eslint-should-not-run >&2\n"+ "exit 1\n") workspaceBinDir := filepath.Join(repoRoot, "web", "node_modules", ".bin") if err := os.MkdirAll(workspaceBinDir, 0755); err != nil { t.Fatalf("mkdir workspace bin dir: %v", err) } writeExecutable(t, filepath.Join(workspaceBinDir, "eslint"), "#!/bin/sh\nexit 0\n") completed, skipped, failures, err := runFrontendAutoFixAndLint(repoRoot, []frontendScanTarget{ {dir: sourceDir, label: "managed/frontend/src", managedConfigs: true}, }) if err != nil { t.Fatalf("runFrontendAutoFixAndLint() error = %v", err) } if len(skipped) != 0 { t.Fatalf("skipped = %v, want none", skipped) } if len(failures) != 0 { t.Fatalf("failures = %v, want none", failures) } if len(completed) != 1 { t.Fatalf("completed = %v, want one completed target", completed) } } func TestRunStrictGoLintFailsCompileCheck(t *testing.T) { _, moduleRoot := setupGoLintTestModule(t) vetMarker := filepath.Join(t.TempDir(), "vet-ran") goBin := writeExecutableFile(t, filepath.Join(t.TempDir(), "go"), "#!/bin/sh\n"+ "case \"$1\" in\n"+ " fix)\n"+ " exit 0\n"+ " ;;\n"+ " test)\n"+ " echo \"compile failure: undefined: brokenSymbol\" >&2\n"+ " exit 1\n"+ " ;;\n"+ " vet)\n"+ " echo ran > "+shellQuote(vetMarker)+"\n"+ " exit 0\n"+ " ;;\n"+ "esac\n"+ "exit 0\n") lintBin := writeExecutableFile(t, filepath.Join(t.TempDir(), "golangci-lint"), "#!/bin/sh\nexit 0\n") completed, skipped, failures, err := runStrictGoLintWithBinaries([]goLintTarget{ {label: "backend", moduleRoot: moduleRoot}, }, nil, goBin, lintBin) if err != nil { t.Fatalf("runStrictGoLintWithBinaries() error = %v", err) } if len(completed) != 0 { t.Fatalf("completed = %v, want none", completed) } if len(skipped) != 0 { t.Fatalf("skipped = %v, want none", skipped) } if len(failures) != 1 { t.Fatalf("failures = %d, want 1", len(failures)) } if failures[0].stage != "go test -run=^$ ./..." { t.Fatalf("failure stage = %q, want compile stage", failures[0].stage) } if !strings.Contains(failures[0].output, "brokenSymbol") { t.Fatalf("failure output = %q, want compile error", failures[0].output) } if _, err := os.Stat(vetMarker); !os.IsNotExist(err) { t.Fatalf("vet marker exists, want compile failure to stop before go vet") } } func TestRunStrictGoLintRunsCompileBeforeVetAndFinalLint(t *testing.T) { _, moduleRoot := setupGoLintTestModule(t) logPath := filepath.Join(t.TempDir(), "go-lint-order.log") goBin := writeExecutableFile(t, filepath.Join(t.TempDir(), "go"), "#!/bin/sh\n"+ "echo go-$1 >> "+shellQuote(logPath)+"\n"+ "exit 0\n") lintBin := writeExecutableFile(t, filepath.Join(t.TempDir(), "golangci-lint"), "#!/bin/sh\n"+ "if [ \"$2\" = \"--fix\" ]; then\n"+ " echo golangci-fix >> "+shellQuote(logPath)+"\n"+ " exit 0\n"+ "fi\n"+ "echo golangci >> "+shellQuote(logPath)+"\n"+ "exit 0\n") completed, skipped, failures, err := runStrictGoLintWithBinaries([]goLintTarget{ {label: "backend", moduleRoot: moduleRoot}, }, nil, goBin, lintBin) if err != nil { t.Fatalf("runStrictGoLintWithBinaries() error = %v", err) } if len(skipped) != 0 { t.Fatalf("skipped = %v, want none", skipped) } if len(failures) != 0 { t.Fatalf("failures = %v, want none", failures) } if len(completed) != 1 { t.Fatalf("completed = %v, want one completed target", completed) } content, err := os.ReadFile(logPath) if err != nil { t.Fatalf("read log: %v", err) } got := strings.Fields(string(content)) want := []string{"go-fix", "golangci-fix", "go-test", "go-vet", "golangci"} if strings.Join(got, ",") != strings.Join(want, ",") { t.Fatalf("command order = %v, want %v", got, want) } } func TestRunGoTestTargetsRunsFullPackageTests(t *testing.T) { moduleRoot := t.TempDir() logPath := filepath.Join(t.TempDir(), "go-test.log") goBin := writeExecutableFile(t, filepath.Join(t.TempDir(), "go"), "#!/bin/sh\n"+ "pwd > "+shellQuote(logPath)+"\n"+ "printf ' %s' \"$@\" >> "+shellQuote(logPath)+"\n"+ "exit 0\n") completed, failures, err := runGoTestTargetsWithBinary([]goTestTarget{ {label: "orchestrator/backend", moduleRoot: moduleRoot, args: []string{"test", "./..."}}, }, goBin) if err != nil { t.Fatalf("runGoTestTargetsWithBinary() error = %v", err) } if len(failures) != 0 { t.Fatalf("failures = %v, want none", failures) } if len(completed) != 1 || completed[0] != "orchestrator/backend [go test ./...]" { t.Fatalf("completed = %v, want orchestrator/backend go test", completed) } content, err := os.ReadFile(logPath) if err != nil { t.Fatalf("read log: %v", err) } if !strings.Contains(string(content), moduleRoot+"\n test ./...") { t.Fatalf("go command log = %q, want module root and full test args", content) } } type frontendLintTestOptions struct { withTSConfig bool tscScript string } func setupFrontendLintTestPackage(t *testing.T, opts frontendLintTestOptions) (repoRoot string, sourceDir string) { t.Helper() repoRoot = t.TempDir() packageRoot := filepath.Join(repoRoot, "frontend") sourceDir = filepath.Join(packageRoot, "src") binDir := filepath.Join(packageRoot, "node_modules", ".bin") for _, dir := range []string{sourceDir, binDir} { if err := os.MkdirAll(dir, 0755); err != nil { t.Fatalf("mkdir %s: %v", dir, err) } } writeTestFile(t, filepath.Join(packageRoot, "package.json"), "{\n \"name\": \"frontend-test\"\n}\n", 0644) writeTestFile(t, filepath.Join(packageRoot, "eslint.config.js"), "export default []\n", 0644) writeTestFile(t, filepath.Join(sourceDir, "index.ts"), "export const value: number = 1\n", 0644) writeExecutable(t, filepath.Join(binDir, "prettier"), "#!/bin/sh\nexit 0\n") writeExecutable(t, filepath.Join(binDir, "eslint"), "#!/bin/sh\nexit 0\n") if opts.withTSConfig { writeTestFile(t, filepath.Join(packageRoot, "tsconfig.json"), "{\n \"compilerOptions\": {\n \"target\": \"ES2020\"\n },\n \"include\": [\"src/**/*\"]\n}\n", 0644) } if opts.tscScript != "" { writeExecutable(t, filepath.Join(binDir, "tsc"), opts.tscScript) } return repoRoot, sourceDir } func setupGoLintTestModule(t *testing.T) (repoRoot string, moduleRoot string) { t.Helper() repoRoot = t.TempDir() moduleRoot = filepath.Join(repoRoot, "backend") if err := os.MkdirAll(moduleRoot, 0755); err != nil { t.Fatalf("mkdir module root: %v", err) } writeTestFile(t, filepath.Join(moduleRoot, "go.mod"), "module example.com/checksafetytest\n\ngo 1.24.0\n", 0644) writeTestFile(t, filepath.Join(moduleRoot, "main.go"), "package main\n\nfunc main() {}\n", 0644) return repoRoot, moduleRoot } func writeExecutable(t *testing.T, path string, content string) { t.Helper() writeTestFile(t, path, content, 0755) } func writeExecutableFile(t *testing.T, path string, content string) string { t.Helper() writeExecutable(t, path, content) return path } func writeTestFile(t *testing.T, path string, content string, mode os.FileMode) { t.Helper() if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { t.Fatalf("mkdir %s: %v", filepath.Dir(path), err) } if err := os.WriteFile(path, []byte(content), mode); err != nil { t.Fatalf("write %s: %v", path, err) } } func shellQuote(path string) string { return "'" + strings.ReplaceAll(path, "'", `'"'"'`) + "'" }