package main import ( "os" "path/filepath" "strings" "testing" ) func TestDefaultScanTargetDirUsesCWDPlugin(t *testing.T) { cwd := t.TempDir() pluginMod := filepath.Join(cwd, "plugin.mod") if err := os.WriteFile(pluginMod, []byte("[plugin]\nname = \"example\"\nversion = \"1.0.0\"\n"), 0644); err != nil { t.Fatalf("write plugin.mod: %v", err) } got := defaultScanTargetDir(".", nil, cwd) if got != cwd { t.Fatalf("defaultScanTargetDir() = %q, want %q", got, cwd) } } func TestDefaultScanTargetDirFallsBackToCoreBackend(t *testing.T) { cwd := t.TempDir() got := defaultScanTargetDir(".", nil, cwd) want := filepath.Join(blockNinjaRepoRoot(), "backend") if got != want { t.Fatalf("defaultScanTargetDir() = %q, want %q", got, want) } } func TestShouldIncludeCoreTargetsUsesBackendPathAsToolHostForPluginScans(t *testing.T) { targetDir := filepath.Join(blockNinjaRepoRoot(), "backend") if !shouldIncludeCoreTargets(targetDir, targetDir, nil, nil) { t.Fatalf("expected core targets to be included when no plugin flags are present") } if shouldIncludeCoreTargets(targetDir, targetDir, []string{"/tmp/plugin"}, nil) { t.Fatalf("expected core targets to be skipped when plugin roots are passed with the checker backend path") } if shouldIncludeCoreTargets(targetDir, targetDir, nil, []string{"/tmp/plugin/web/src"}) { t.Fatalf("expected core targets to be skipped when plugin pages are passed with the checker backend path") } } func TestCollectTargetsSkipsCoreWhenIncludeCoreTargetsFalse(t *testing.T) { repoRoot := t.TempDir() backendDir := filepath.Join(repoRoot, "backend") pluginFrontendDir := filepath.Join(repoRoot, "plugins", "example", "web", "src") for _, dir := range []string{backendDir, pluginFrontendDir} { if err := os.MkdirAll(dir, 0755); err != nil { t.Fatalf("mkdir %s: %v", dir, err) } } if targets := collectBackendScanTargets(repoRoot, backendDir, false); len(targets) != 0 { t.Fatalf("collectBackendScanTargets(..., false) returned %d targets, want 0", len(targets)) } frontendTargets := collectFrontendScanTargets(repoRoot, backendDir, []pluginScanTarget{{ root: filepath.Join(repoRoot, "plugins", "example"), frontendDir: pluginFrontendDir, display: "plugins/example", frontendLabel: "plugins/example/web/src", }}, false) if len(frontendTargets) != 1 { t.Fatalf("collectFrontendScanTargets(..., false) returned %d targets, want 1", len(frontendTargets)) } if frontendTargets[0].dir != pluginFrontendDir { t.Fatalf("frontend target dir = %q, want %q", frontendTargets[0].dir, pluginFrontendDir) } if !frontendTargets[0].pluginRules { t.Fatalf("frontend target pluginRules = false, want true") } if !frontendTargets[0].managedConfigs { t.Fatalf("frontend target managedConfigs = false, want true") } } func TestResolveScanRootsDetectsConsolidatedBackendDir(t *testing.T) { repoRoot := t.TempDir() backendDir := filepath.Join(repoRoot, "backend") protoDir := filepath.Join(repoRoot, "proto") for _, dir := range []string{backendDir, protoDir} { if err := os.MkdirAll(dir, 0755); err != nil { t.Fatalf("mkdir %s: %v", dir, err) } } if err := os.WriteFile(filepath.Join(backendDir, "go.mod"), []byte("module example.com/cms/backend\n"), 0644); err != nil { t.Fatalf("write go.mod: %v", err) } if err := os.WriteFile(filepath.Join(repoRoot, "buf.yaml"), []byte("version: v2\n"), 0644); err != nil { t.Fatalf("write buf.yaml: %v", err) } gotBackend, gotRepo, err := resolveScanRoots(backendDir) if err != nil { t.Fatalf("resolveScanRoots() error = %v", err) } if gotBackend != backendDir { t.Fatalf("backendDir = %q, want %q", gotBackend, backendDir) } if gotRepo != repoRoot { t.Fatalf("repoRoot = %q, want %q", gotRepo, repoRoot) } } func TestDiscoverOrchestratorTestTargetsRequiresCoreScan(t *testing.T) { repoRoot := blockNinjaRepoRoot() backendDir := filepath.Join(repoRoot, "backend") if targets := discoverOrchestratorTestTargets(repoRoot, backendDir, false); len(targets) != 0 { t.Fatalf("discoverOrchestratorTestTargets(..., false) returned %d targets, want 0", len(targets)) } pluginRoot := filepath.Join(t.TempDir(), "plugin") if targets := discoverOrchestratorTestTargets(pluginRoot, pluginRoot, true); len(targets) != 0 { t.Fatalf("discoverOrchestratorTestTargets(plugin root) returned %d targets, want 0", len(targets)) } } func TestDiscoverOrchestratorTestTargetsFromRoot(t *testing.T) { root := t.TempDir() backendDir := filepath.Join(root, "backend") if err := os.MkdirAll(backendDir, 0755); err != nil { t.Fatalf("mkdir backend: %v", err) } if targets := discoverOrchestratorTestTargetsFromRoot(root); len(targets) != 0 { t.Fatalf("targets without go.mod = %d, want 0", len(targets)) } if err := os.WriteFile(filepath.Join(backendDir, "go.mod"), []byte("module example.com/orchestrator\n"), 0644); err != nil { t.Fatalf("write go.mod: %v", err) } targets := discoverOrchestratorTestTargetsFromRoot(root) if len(targets) != 1 { t.Fatalf("targets = %d, want 1", len(targets)) } if targets[0].label != "orchestrator/backend" { t.Fatalf("label = %q, want orchestrator/backend", targets[0].label) } if targets[0].moduleRoot != backendDir { t.Fatalf("moduleRoot = %q, want %q", targets[0].moduleRoot, backendDir) } if got := strings.Join(targets[0].args, " "); got != "test ./..." { t.Fatalf("args = %q, want test ./...", got) } } func TestResolvePluginTargetsUsesRootFolderNameForDisplay(t *testing.T) { repoRoot := t.TempDir() pluginRoot := filepath.Join(t.TempDir(), "messenger") pluginFrontend := filepath.Join(pluginRoot, "web") for _, dir := range []string{pluginRoot, pluginFrontend} { if err := os.MkdirAll(dir, 0755); err != nil { t.Fatalf("mkdir %s: %v", dir, err) } } if err := os.WriteFile(filepath.Join(pluginFrontend, "index.tsx"), []byte("export const x = 1\n"), 0644); err != nil { t.Fatalf("write frontend file: %v", err) } targets, unresolved := resolvePluginTargets(repoRoot, []string{pluginRoot}) if len(unresolved) != 0 { t.Fatalf("unresolved = %v, want none", unresolved) } if len(targets) != 1 { t.Fatalf("targets = %d, want 1", len(targets)) } if targets[0].display != "messenger" { t.Fatalf("display = %q, want messenger", targets[0].display) } if targets[0].frontendLabel != "messenger" { t.Fatalf("frontendLabel = %q, want messenger", targets[0].frontendLabel) } } func TestExtractRBACMethodRolesParsesRoleValues(t *testing.T) { dir := t.TempDir() file := filepath.Join(dir, "interceptor.go") if err := os.WriteFile(file, []byte(`package rbac type Role string const ( RoleViewer Role = "viewer" RoleAdmin Role = "admin" ) var MethodRoles = map[string]Role{ "/example.v1.Service/Public": "", "/example.v1.Service/View": RoleViewer, "/example.v1.Service/Admin": RoleAdmin, } `), 0644); err != nil { t.Fatalf("write interceptor.go: %v", err) } methods, err := extractRBACMethodRoles(file) if err != nil { t.Fatalf("extractRBACMethodRoles() error = %v", err) } if methods["/example.v1.Service/Public"] != "public" { t.Fatalf("public role = %q, want public", methods["/example.v1.Service/Public"]) } if methods["/example.v1.Service/View"] != "viewer" { t.Fatalf("viewer role = %q, want viewer", methods["/example.v1.Service/View"]) } if methods["/example.v1.Service/Admin"] != "admin" { t.Fatalf("admin role = %q, want admin", methods["/example.v1.Service/Admin"]) } } func TestExtractRBACMethodRolesParsesSeedTable(t *testing.T) { // The CMS renamed the static RBAC table to methodRolesSeed, kept behind an // atomic-pointer copy-on-write live table for thread safety. The extractor must // recognise methodRolesSeed as well as MethodRoles, or every CMS RPC reads as // "missing RBAC entry". dir := t.TempDir() file := filepath.Join(dir, "interceptor.go") if err := os.WriteFile(file, []byte(`package rbac type Role string const ( RoleAdmin Role = "admin" ) var methodRolesSeed = map[string]Role{ "/example.v1.Service/Public": "", "/example.v1.Service/Admin": RoleAdmin, } `), 0644); err != nil { t.Fatalf("write interceptor.go: %v", err) } methods, err := extractRBACMethodRoles(file) if err != nil { t.Fatalf("extractRBACMethodRoles() error = %v", err) } if methods["/example.v1.Service/Public"] != "public" { t.Fatalf("public role = %q, want public", methods["/example.v1.Service/Public"]) } if methods["/example.v1.Service/Admin"] != "admin" { t.Fatalf("admin role = %q, want admin", methods["/example.v1.Service/Admin"]) } }