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