check-safety/presets_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

284 lines
7.5 KiB
Go

package main
import (
"os"
"path/filepath"
"testing"
)
// minimalValidPreset returns a presets.json that satisfies the strict decoder
// (all known fields, correct types, required id+name).
const minimalValidPresetJSON = `[
{
"id": "test-preset",
"name": "Test Preset",
"description": "A minimal test preset",
"theme": {
"lightColors": {
"background": "0 0% 100%",
"foreground": "240 10% 3.9%",
"card": "0 0% 100%",
"cardForeground": "240 10% 3.9%",
"popover": "0 0% 100%",
"popoverForeground": "240 10% 3.9%",
"primary": "240 5.9% 10%",
"primaryForeground": "0 0% 98%",
"secondary": "240 4.8% 95.9%",
"secondaryForeground": "240 5.9% 10%",
"muted": "240 4.8% 95.9%",
"mutedForeground": "240 3.8% 46.1%",
"accent": "240 4.8% 95.9%",
"accentForeground": "240 5.9% 10%",
"destructive": "0 84.2% 60.2%",
"destructiveForeground": "0 0% 98%",
"border": "240 5.9% 90%",
"input": "240 5.9% 90%",
"ring": "240 5.9% 10%"
},
"darkColors": {
"background": "240 10% 3.9%",
"foreground": "0 0% 98%",
"card": "240 10% 3.9%",
"cardForeground": "0 0% 98%",
"popover": "240 10% 3.9%",
"popoverForeground": "0 0% 98%",
"primary": "0 0% 98%",
"primaryForeground": "240 5.9% 10%",
"secondary": "240 3.7% 15.9%",
"secondaryForeground": "0 0% 98%",
"muted": "240 3.7% 15.9%",
"mutedForeground": "240 5% 64.9%",
"accent": "240 3.7% 15.9%",
"accentForeground": "0 0% 98%",
"destructive": "0 62.8% 30.6%",
"destructiveForeground": "0 0% 98%",
"border": "240 3.7% 15.9%",
"input": "240 3.7% 15.9%",
"ring": "240 4.9% 83.9%"
},
"typography": {
"fontHeading": "Inter",
"fontBody": "Inter",
"fontMono": "JetBrains Mono",
"fontSizeBase": "1rem",
"lineHeightBase": "1.5",
"fontWeightBase": "400"
},
"spacing": {
"radius": "0.5rem",
"buttonRadius": "0.5rem",
"cardRadius": "0.5rem",
"inputRadius": "0.5rem",
"pillButtons": false,
"borderWidth": "1px",
"containerWidth": "1280px",
"spacingScale": "1"
},
"effects": {
"shadowIntensity": 20,
"shadowColor": "220 3% 15%",
"shadowBlur": "8px",
"elevationPreset": "soft",
"transitionSpeed": "200ms",
"reduceMotion": false,
"hoverIntensity": "medium"
},
"mode": "light"
}
}
]`
func TestCheckPresets_ValidFile(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "presets.json", minimalValidPresetJSON)
violations := checkPresets(dir)
if len(violations) != 0 {
t.Fatalf("checkPresets() returned %d violations for valid presets.json, want 0: %#v", len(violations), violations)
}
}
func TestCheckPresets_EmptyArray(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "presets.json", `[]`)
violations := checkPresets(dir)
if len(violations) != 0 {
t.Fatalf("checkPresets() returned %d violations for empty array, want 0", len(violations))
}
}
func TestCheckPresets_MissingID(t *testing.T) {
dir := t.TempDir()
// Preset with no "id" field (empty string after decode)
writeFile(t, dir, "presets.json", `[
{
"id": "",
"name": "My Preset",
"theme": {}
}
]`)
violations := checkPresets(dir)
if len(violations) == 0 {
t.Fatal("checkPresets() returned 0 violations for preset with empty id, want >= 1")
}
found := false
for _, v := range violations {
if containsStr(v.message, "missing \"id\"") {
found = true
}
}
if !found {
t.Fatalf("expected 'missing id' violation, got: %#v", violations)
}
}
func TestCheckPresets_MissingName(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "presets.json", `[
{
"id": "my-id",
"name": "",
"theme": {}
}
]`)
violations := checkPresets(dir)
if len(violations) == 0 {
t.Fatal("checkPresets() returned 0 violations for preset with empty name, want >= 1")
}
found := false
for _, v := range violations {
if containsStr(v.message, "missing \"name\"") {
found = true
}
}
if !found {
t.Fatalf("expected 'missing name' violation, got: %#v", violations)
}
}
func TestCheckPresets_BothIDAndNameMissing(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "presets.json", `[
{
"id": "",
"name": "",
"theme": {}
}
]`)
violations := checkPresets(dir)
if len(violations) < 2 {
t.Fatalf("checkPresets() returned %d violations, want at least 2 (id+name): %#v", len(violations), violations)
}
}
func TestCheckPresets_InvalidJSON(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "presets.json", `{ this is not valid json`)
violations := checkPresets(dir)
if len(violations) == 0 {
t.Fatal("checkPresets() returned 0 violations for invalid JSON, want >= 1")
}
found := false
for _, v := range violations {
if containsStr(v.message, "invalid JSON") {
found = true
}
}
if !found {
t.Fatalf("expected 'invalid JSON' violation, got: %#v", violations)
}
}
func TestCheckPresets_UnknownField(t *testing.T) {
dir := t.TempDir()
// Valid except for an extra unknown field — triggers "stale preset data" path
writeFile(t, dir, "presets.json", `[
{
"id": "test",
"name": "Test",
"unknownExtraField": "oops",
"theme": {}
}
]`)
violations := checkPresets(dir)
if len(violations) == 0 {
t.Fatal("checkPresets() returned 0 violations for unknown field, want >= 1")
}
found := false
for _, v := range violations {
if containsStr(v.message, "unknown fields") || containsStr(v.message, "stale preset data") {
found = true
}
}
if !found {
t.Fatalf("expected 'unknown fields' violation, got: %#v", violations)
}
}
func TestCheckPresets_NoPresetsFileReturnsClean(t *testing.T) {
dir := t.TempDir()
// No presets.json at all — should return 0 violations
violations := checkPresets(dir)
if len(violations) != 0 {
t.Fatalf("checkPresets() returned %d violations for dir without presets.json, want 0", len(violations))
}
}
func TestFindPresetsFiles_SkipsVendorAndWeb(t *testing.T) {
root := t.TempDir()
// Legitimate presets.json at root level
writeFile(t, root, "presets.json", `[]`)
// Should be skipped (vendor/)
vendorDir := filepath.Join(root, "vendor", "somelib")
if err := os.MkdirAll(vendorDir, 0755); err != nil {
t.Fatalf("mkdir vendor: %v", err)
}
writeFile(t, vendorDir, "presets.json", `[]`)
// Should be skipped (web/)
webDir := filepath.Join(root, "web", "src")
if err := os.MkdirAll(webDir, 0755); err != nil {
t.Fatalf("mkdir web: %v", err)
}
writeFile(t, webDir, "presets.json", `[]`)
files := findPresetsFiles(root)
if len(files) != 1 {
t.Fatalf("findPresetsFiles() returned %d files, want 1 (only root-level): %v", len(files), files)
}
}
func TestFindPresetsFiles_FindsInSubdir(t *testing.T) {
root := t.TempDir()
pluginDir := filepath.Join(root, "internal", "plugins", "myplugin")
if err := os.MkdirAll(pluginDir, 0755); err != nil {
t.Fatalf("mkdir plugin: %v", err)
}
writeFile(t, pluginDir, "presets.json", `[]`)
files := findPresetsFiles(root)
if len(files) != 1 {
t.Fatalf("findPresetsFiles() returned %d files, want 1: %v", len(files), files)
}
}
// containsStr is a helper to test substring containment.
func containsStr(s, substr string) bool {
return len(s) > 0 && len(substr) > 0 && func() bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}()
}