Static safety/lint runner for the BlockNinja codebase. ~25 invariant
checks across Go and frontend sources. Was at git.dev.alexdunmow.com:block/ninja
in backend/cmd/check-safety/ until the 2026-06-06 consolidation moved
the BlockNinja repos under a shared ~/src/blockninja/ parent.
This repo is the standalone extraction:
- Own go.mod (git.dev.alexdunmow.com/block/check-safety, go 1.26.4)
- Vendored internal/{helpers,theme} from CMS (Go's internal/ rule
blocks cross-module imports; vendoring is the workaround)
- CLI contract unchanged: `check-safety <target-dir> [--flags]`
- CMS Makefile shells into ../check-safety for safety-check /
install-safety-checker targets
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
192 lines
6.4 KiB
Go
192 lines
6.4 KiB
Go
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 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"])
|
|
}
|
|
}
|