check-safety/segmentation_test.go
Alex Dunmow cd88c808b0 initial: standalone check-safety module hoisted from CMS
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>
2026-06-06 13:04:02 +08:00

487 lines
14 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"os"
"path/filepath"
"testing"
)
// ─────────────────────────────────────────────
// loadSafetyRules
// ─────────────────────────────────────────────
func TestLoadSafetyRules_Missing(t *testing.T) {
dir := t.TempDir()
rules, err := loadSafetyRules(dir)
if err != nil {
t.Fatalf("loadSafetyRules() unexpected error for missing file: %v", err)
}
if rules != nil {
t.Fatalf("loadSafetyRules() want nil for missing file, got %+v", rules)
}
}
func TestLoadSafetyRules_ValidYAML(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "safety-rules.yml", `
segmentation:
no_go_imports_from:
- otherplugin
migration_table_prefix: "myplugin_"
federation_name: myplugin
`)
rules, err := loadSafetyRules(dir)
if err != nil {
t.Fatalf("loadSafetyRules() unexpected error: %v", err)
}
if rules == nil || rules.Segmentation == nil {
t.Fatal("loadSafetyRules() returned nil rules or nil segmentation")
}
if len(rules.Segmentation.NoGoImportsFrom) != 1 || rules.Segmentation.NoGoImportsFrom[0] != "otherplugin" {
t.Fatalf("unexpected NoGoImportsFrom: %v", rules.Segmentation.NoGoImportsFrom)
}
if rules.Segmentation.MigrationTablePrefix != "myplugin_" {
t.Fatalf("unexpected MigrationTablePrefix: %q", rules.Segmentation.MigrationTablePrefix)
}
if rules.Segmentation.FederationName != "myplugin" {
t.Fatalf("unexpected FederationName: %q", rules.Segmentation.FederationName)
}
}
func TestLoadSafetyRules_InvalidYAML(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "safety-rules.yml", `
segmentation:
no_go_imports_from: {bad yaml [
`)
_, err := loadSafetyRules(dir)
if err == nil {
t.Fatal("loadSafetyRules() expected error for invalid YAML, got nil")
}
}
func TestLoadSafetyRules_EmptyFile(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "safety-rules.yml", ``)
rules, err := loadSafetyRules(dir)
if err != nil {
t.Fatalf("loadSafetyRules() unexpected error for empty file: %v", err)
}
// Empty YAML → non-nil SafetyRules with nil Segmentation
if rules != nil && rules.Segmentation != nil {
t.Fatalf("loadSafetyRules() want nil segmentation for empty file, got %+v", rules.Segmentation)
}
}
// ─────────────────────────────────────────────
// checkSegmentation require_files rule
// ─────────────────────────────────────────────
func TestCheckSegmentation_RequireFiles_Present(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "README.md", "# plugin")
rules := &SegmentationRules{
RequireFiles: []string{"README.md"},
}
vs := checkSegmentation(dir, "myplugin", rules)
if len(vs) != 0 {
t.Fatalf("expected 0 violations when required file exists, got %d: %#v", len(vs), vs)
}
}
func TestCheckSegmentation_RequireFiles_Missing(t *testing.T) {
dir := t.TempDir()
// README.md does NOT exist
rules := &SegmentationRules{
RequireFiles: []string{"README.md"},
}
vs := checkSegmentation(dir, "myplugin", rules)
if len(vs) != 1 {
t.Fatalf("expected 1 violation for missing required file, got %d: %#v", len(vs), vs)
}
if vs[0].rule != "require_files" {
t.Fatalf("expected rule 'require_files', got %q", vs[0].rule)
}
if vs[0].pluginName != "myplugin" {
t.Fatalf("expected pluginName 'myplugin', got %q", vs[0].pluginName)
}
}
func TestCheckSegmentation_RequireFiles_Multiple(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "exists.md", "exists")
// "missing.md" does not exist
rules := &SegmentationRules{
RequireFiles: []string{"exists.md", "missing.md"},
}
vs := checkSegmentation(dir, "plugin", rules)
if len(vs) != 1 {
t.Fatalf("expected 1 violation for 1 missing file, got %d: %#v", len(vs), vs)
}
if vs[0].file != "missing.md" {
t.Fatalf("expected violation for missing.md, got %q", vs[0].file)
}
}
// ─────────────────────────────────────────────
// checkSegmentation no_go_imports_from rule
// ─────────────────────────────────────────────
func TestCheckSegmentation_NoGoImports_Clean(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "plugin.go", `package myplugin
import "fmt"
func Hello() string {
return fmt.Sprintf("hello")
}
`)
rules := &SegmentationRules{
NoGoImportsFrom: []string{"otherplugin"},
}
vs := checkSegmentation(dir, "myplugin", rules)
if len(vs) != 0 {
t.Fatalf("expected 0 violations for clean Go file, got %d: %#v", len(vs), vs)
}
}
func TestCheckSegmentation_NoGoImports_Violation(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "plugin.go", `package myplugin
import "git.example.com/block/ninja/internal/plugins/otherplugin"
func Hello() {
_ = otherplugin.SomeFunc()
}
`)
rules := &SegmentationRules{
NoGoImportsFrom: []string{"otherplugin"},
}
vs := checkSegmentation(dir, "myplugin", rules)
if len(vs) == 0 {
t.Fatal("expected violations for forbidden import, got 0")
}
found := false
for _, v := range vs {
if v.rule == "no_go_imports_from" {
found = true
}
}
if !found {
t.Fatalf("expected rule 'no_go_imports_from', got: %#v", vs)
}
}
func TestCheckSegmentation_NoGoImports_TestFilesSkipped(t *testing.T) {
dir := t.TempDir()
// Test files should be skipped by the scanner
writeFile(t, dir, "plugin_test.go", `package myplugin
import "git.example.com/block/ninja/internal/plugins/otherplugin"
func TestSomething(t *testing.T) {
_ = otherplugin.SomeFunc()
}
`)
rules := &SegmentationRules{
NoGoImportsFrom: []string{"otherplugin"},
}
vs := checkSegmentation(dir, "myplugin", rules)
if len(vs) != 0 {
t.Fatalf("expected 0 violations for test file, got %d: %#v", len(vs), vs)
}
}
// ─────────────────────────────────────────────
// checkSegmentation no_frontend_imports_from rule
// ─────────────────────────────────────────────
func TestCheckSegmentation_NoFrontendImports_NoWebDir(t *testing.T) {
dir := t.TempDir()
// No web/ directory — should return nothing
rules := &SegmentationRules{
NoFrontendImportsFrom: []string{"otherplugin"},
}
vs := checkSegmentation(dir, "myplugin", rules)
if len(vs) != 0 {
t.Fatalf("expected 0 violations when no web/ dir, got %d: %#v", len(vs), vs)
}
}
func TestCheckSegmentation_NoFrontendImports_Clean(t *testing.T) {
dir := t.TempDir()
webDir := filepath.Join(dir, "web", "src")
if err := os.MkdirAll(webDir, 0755); err != nil {
t.Fatalf("mkdir web: %v", err)
}
writeFile(t, webDir, "component.tsx", `import React from 'react'
export function MyComponent() {
return <div>Hello</div>
}
`)
rules := &SegmentationRules{
NoFrontendImportsFrom: []string{"otherplugin"},
}
vs := checkSegmentation(dir, "myplugin", rules)
if len(vs) != 0 {
t.Fatalf("expected 0 violations for clean frontend, got %d: %#v", len(vs), vs)
}
}
func TestCheckSegmentation_NoFrontendImports_Violation(t *testing.T) {
dir := t.TempDir()
webDir := filepath.Join(dir, "web", "src")
if err := os.MkdirAll(webDir, 0755); err != nil {
t.Fatalf("mkdir web: %v", err)
}
writeFile(t, webDir, "component.tsx", `import { SomeThing } from 'otherplugin/components'
export function MyComponent() {
return <SomeThing />
}
`)
rules := &SegmentationRules{
NoFrontendImportsFrom: []string{"otherplugin"},
}
vs := checkSegmentation(dir, "myplugin", rules)
if len(vs) == 0 {
t.Fatal("expected violations for forbidden frontend import, got 0")
}
found := false
for _, v := range vs {
if v.rule == "no_frontend_imports_from" {
found = true
}
}
if !found {
t.Fatalf("expected rule 'no_frontend_imports_from', got: %#v", vs)
}
}
// ─────────────────────────────────────────────
// checkSegmentation migration_table_prefix rule
// ─────────────────────────────────────────────
func TestCheckSegmentation_MigrationFK_NoMigrationsDir(t *testing.T) {
dir := t.TempDir()
// No migrations/ directory — should skip gracefully
rules := &SegmentationRules{
MigrationTablePrefix: "myplugin_",
}
vs := checkSegmentation(dir, "myplugin", rules)
if len(vs) != 0 {
t.Fatalf("expected 0 violations when no migrations/ dir, got %d: %#v", len(vs), vs)
}
}
func TestCheckSegmentation_MigrationFK_AllowsOwnPrefix(t *testing.T) {
dir := t.TempDir()
migrDir := filepath.Join(dir, "migrations")
if err := os.MkdirAll(migrDir, 0755); err != nil {
t.Fatalf("mkdir migrations: %v", err)
}
writeFile(t, migrDir, "001_create.sql", `
CREATE TABLE myplugin_items (
id BIGINT PRIMARY KEY,
FOREIGN KEY (owner_id) REFERENCES myplugin_owners(id)
);
`)
rules := &SegmentationRules{
MigrationTablePrefix: "myplugin_",
}
vs := checkSegmentation(dir, "myplugin", rules)
if len(vs) != 0 {
t.Fatalf("expected 0 violations for FK within own prefix, got %d: %#v", len(vs), vs)
}
}
func TestCheckSegmentation_MigrationFK_AllowsPublicUsers(t *testing.T) {
dir := t.TempDir()
migrDir := filepath.Join(dir, "migrations")
if err := os.MkdirAll(migrDir, 0755); err != nil {
t.Fatalf("mkdir migrations: %v", err)
}
writeFile(t, migrDir, "001_create.sql", `
CREATE TABLE myplugin_posts (
id BIGINT PRIMARY KEY,
user_id BIGINT REFERENCES public_users(id)
);
`)
rules := &SegmentationRules{
MigrationTablePrefix: "myplugin_",
}
vs := checkSegmentation(dir, "myplugin", rules)
if len(vs) != 0 {
t.Fatalf("expected 0 violations for FK to public_users, got %d: %#v", len(vs), vs)
}
}
func TestCheckSegmentation_MigrationFK_CrossPluginViolation(t *testing.T) {
dir := t.TempDir()
migrDir := filepath.Join(dir, "migrations")
if err := os.MkdirAll(migrDir, 0755); err != nil {
t.Fatalf("mkdir migrations: %v", err)
}
writeFile(t, migrDir, "001_create.sql", `
CREATE TABLE myplugin_items (
id BIGINT PRIMARY KEY,
other_id BIGINT REFERENCES otherplugin_widgets(id)
);
`)
rules := &SegmentationRules{
MigrationTablePrefix: "myplugin_",
}
vs := checkSegmentation(dir, "myplugin", rules)
if len(vs) == 0 {
t.Fatal("expected violations for cross-plugin FK reference, got 0")
}
found := false
for _, v := range vs {
if v.rule == "migration_table_prefix" {
found = true
}
}
if !found {
t.Fatalf("expected rule 'migration_table_prefix', got: %#v", vs)
}
}
func TestCheckSegmentation_MigrationFK_SkipsDownFiles(t *testing.T) {
dir := t.TempDir()
migrDir := filepath.Join(dir, "migrations")
if err := os.MkdirAll(migrDir, 0755); err != nil {
t.Fatalf("mkdir migrations: %v", err)
}
// _down file should be skipped
writeFile(t, migrDir, "001_create_down.sql", `
DROP TABLE IF EXISTS myplugin_items;
ALTER TABLE otherplugin_widgets DROP CONSTRAINT fk_myplugin;
`)
rules := &SegmentationRules{
MigrationTablePrefix: "myplugin_",
}
vs := checkSegmentation(dir, "myplugin", rules)
if len(vs) != 0 {
t.Fatalf("expected 0 violations for _down migration, got %d: %#v", len(vs), vs)
}
}
// ─────────────────────────────────────────────
// checkSegmentation federation_name rule
// ─────────────────────────────────────────────
func TestCheckSegmentation_FederationName_NoViteConfig(t *testing.T) {
dir := t.TempDir()
webDir := filepath.Join(dir, "web")
if err := os.MkdirAll(webDir, 0755); err != nil {
t.Fatalf("mkdir web: %v", err)
}
// No vite.config.ts — should skip (require_files handles this)
rules := &SegmentationRules{
FederationName: "myplugin",
}
vs := checkSegmentation(dir, "myplugin", rules)
if len(vs) != 0 {
t.Fatalf("expected 0 violations when vite.config.ts is absent, got %d: %#v", len(vs), vs)
}
}
func TestCheckSegmentation_FederationName_Correct(t *testing.T) {
dir := t.TempDir()
webDir := filepath.Join(dir, "web")
if err := os.MkdirAll(webDir, 0755); err != nil {
t.Fatalf("mkdir web: %v", err)
}
writeFile(t, webDir, "vite.config.ts", `
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
federation({
name: "myplugin",
exposes: { './Editor': './src/Editor' },
}),
],
})
`)
rules := &SegmentationRules{
FederationName: "myplugin",
}
vs := checkSegmentation(dir, "myplugin", rules)
if len(vs) != 0 {
t.Fatalf("expected 0 violations for correct federation name, got %d: %#v", len(vs), vs)
}
}
func TestCheckSegmentation_FederationName_Wrong(t *testing.T) {
dir := t.TempDir()
webDir := filepath.Join(dir, "web")
if err := os.MkdirAll(webDir, 0755); err != nil {
t.Fatalf("mkdir web: %v", err)
}
writeFile(t, webDir, "vite.config.ts", `
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
federation({
name: "wrongname",
}),
],
})
`)
rules := &SegmentationRules{
FederationName: "myplugin",
}
vs := checkSegmentation(dir, "myplugin", rules)
if len(vs) == 0 {
t.Fatal("expected violations for wrong federation name, got 0")
}
if vs[0].rule != "federation_name" {
t.Fatalf("expected rule 'federation_name', got %q", vs[0].rule)
}
}
func TestCheckSegmentation_FederationName_NoNameDecl(t *testing.T) {
dir := t.TempDir()
webDir := filepath.Join(dir, "web")
if err := os.MkdirAll(webDir, 0755); err != nil {
t.Fatalf("mkdir web: %v", err)
}
// vite.config.ts without a federation name: "..." declaration
writeFile(t, webDir, "vite.config.ts", `
export default {}
`)
rules := &SegmentationRules{
FederationName: "myplugin",
}
vs := checkSegmentation(dir, "myplugin", rules)
if len(vs) == 0 {
t.Fatal("expected violation when federation name declaration is absent, got 0")
}
if vs[0].rule != "federation_name" {
t.Fatalf("expected rule 'federation_name', got %q", vs[0].rule)
}
}