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
Hello
} `) 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 } `) 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) } }