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>
209 lines
5.7 KiB
Go
209 lines
5.7 KiB
Go
package main
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestCheckRawSQL_ExecContextWithInsert(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, dir, "bad.go", `package service
|
|
|
|
func (s *Service) Create(ctx context.Context, db *sql.DB) error {
|
|
_, err := db.ExecContext(ctx, "INSERT INTO users (name) VALUES ($1)", name)
|
|
return err
|
|
}
|
|
`)
|
|
vs := checkRawSQL(dir)
|
|
if len(vs) == 0 {
|
|
t.Fatal("expected rawsql violation for ExecContext with INSERT INTO")
|
|
}
|
|
}
|
|
|
|
func TestCheckRawSQL_QueryContextWithSelect(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, dir, "bad.go", `package service
|
|
|
|
func (s *Service) List(ctx context.Context) {
|
|
rows, _ := db.QueryContext(ctx, "SELECT id FROM pages WHERE active = true")
|
|
_ = rows
|
|
}
|
|
`)
|
|
vs := checkRawSQL(dir)
|
|
if len(vs) == 0 {
|
|
t.Fatal("expected rawsql violation for QueryContext with SELECT FROM")
|
|
}
|
|
}
|
|
|
|
func TestCheckRawSQL_QueryRowContextWithSelect(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, dir, "bad.go", `package service
|
|
|
|
func (s *Service) Get(ctx context.Context) {
|
|
row := db.QueryRowContext(ctx, "SELECT name FROM users WHERE id = $1", id)
|
|
_ = row
|
|
}
|
|
`)
|
|
vs := checkRawSQL(dir)
|
|
if len(vs) == 0 {
|
|
t.Fatal("expected rawsql violation for QueryRowContext with SELECT FROM")
|
|
}
|
|
}
|
|
|
|
func TestCheckRawSQL_UpdateStatement(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, dir, "bad.go", `package service
|
|
|
|
func (s *Service) Update(ctx context.Context) {
|
|
db.Exec("UPDATE users SET name = $1 WHERE id = $2", name, id)
|
|
}
|
|
`)
|
|
vs := checkRawSQL(dir)
|
|
if len(vs) == 0 {
|
|
t.Fatal("expected rawsql violation for Exec with UPDATE ... SET")
|
|
}
|
|
}
|
|
|
|
func TestCheckRawSQL_DeleteStatement(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, dir, "bad.go", `package service
|
|
|
|
func (s *Service) Delete(ctx context.Context) {
|
|
db.QueryRow("DELETE FROM sessions WHERE expired = true")
|
|
}
|
|
`)
|
|
vs := checkRawSQL(dir)
|
|
if len(vs) == 0 {
|
|
t.Fatal("expected rawsql violation for QueryRow with DELETE FROM")
|
|
}
|
|
}
|
|
|
|
func TestCheckRawSQL_NoSQLKeyword_Clean(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, dir, "good.go", `package service
|
|
|
|
func (s *Service) DoSomething(ctx context.Context) error {
|
|
_, err := db.ExecContext(ctx, "NOTIFY channel, 'payload'")
|
|
return err
|
|
}
|
|
`)
|
|
vs := checkRawSQL(dir)
|
|
if len(vs) != 0 {
|
|
t.Fatalf("expected no violations for NOTIFY statement, got %d", len(vs))
|
|
}
|
|
}
|
|
|
|
func TestCheckRawSQL_SqlcGeneratedFile_AllowedByPath(t *testing.T) {
|
|
// Files under /internal/db/ are allowed
|
|
dir := t.TempDir()
|
|
dbDir := filepath.Join(dir, "internal", "db")
|
|
writeTestFile(t, filepath.Join(dbDir, "queries.go"), `package db
|
|
|
|
func GetUser(ctx context.Context) {
|
|
db.QueryContext(ctx, "SELECT id FROM users WHERE id = $1", id)
|
|
}
|
|
`, 0644)
|
|
vs := checkRawSQL(dir)
|
|
if len(vs) != 0 {
|
|
t.Fatalf("expected no violations for file under /internal/db/, got %d", len(vs))
|
|
}
|
|
}
|
|
|
|
func TestCheckRawSQL_MigrationFile_AllowedByPath(t *testing.T) {
|
|
dir := t.TempDir()
|
|
migDir := filepath.Join(dir, "sql", "migrations")
|
|
writeTestFile(t, filepath.Join(migDir, "001_init.go"), `package migrations
|
|
|
|
func up() {
|
|
db.Exec("CREATE TABLE users (id UUID PRIMARY KEY)")
|
|
}
|
|
`, 0644)
|
|
vs := checkRawSQL(dir)
|
|
if len(vs) != 0 {
|
|
t.Fatalf("expected no violations for file under /sql/, got %d", len(vs))
|
|
}
|
|
}
|
|
|
|
func TestCheckRawSQL_TestFile_AllowedByPath(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, dir, "service_test.go", `package service
|
|
|
|
func TestCreate(t *testing.T) {
|
|
db.ExecContext(ctx, "INSERT INTO users (name) VALUES ($1)", "test")
|
|
}
|
|
`)
|
|
vs := checkRawSQL(dir)
|
|
if len(vs) != 0 {
|
|
t.Fatalf("expected no violations for _test.go file, got %d", len(vs))
|
|
}
|
|
}
|
|
|
|
func TestCheckRawSQL_AllowedAdminPattern(t *testing.T) {
|
|
// pg_terminate_backend is in the allowlist
|
|
dir := t.TempDir()
|
|
writeFile(t, dir, "backup.go", `package backup
|
|
|
|
func killConnections(ctx context.Context) {
|
|
db.ExecContext(ctx, "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = $1", name)
|
|
}
|
|
`)
|
|
vs := checkRawSQL(dir)
|
|
if len(vs) != 0 {
|
|
t.Fatalf("expected no violations for allowed pg_terminate_backend pattern, got %d", len(vs))
|
|
}
|
|
}
|
|
|
|
func TestCheckRawSQL_SnippetTruncatedAt80(t *testing.T) {
|
|
dir := t.TempDir()
|
|
longSQL := "SELECT id, name, email, created_at, updated_at, status, role FROM users WHERE active = true AND deleted_at IS NULL"
|
|
writeFile(t, dir, "bad.go", "package service\n\nfunc f(ctx interface{}, db interface{}) {\n\tdb.QueryContext(ctx, \""+longSQL+"\")\n}\n")
|
|
vs := checkRawSQL(dir)
|
|
if len(vs) == 0 {
|
|
t.Fatal("expected violation for long SELECT statement")
|
|
}
|
|
if len(vs[0].snippet) > 80 {
|
|
t.Fatalf("snippet should be truncated to <=80 chars, got %d: %q", len(vs[0].snippet), vs[0].snippet)
|
|
}
|
|
}
|
|
|
|
func TestCheckRawSQL_RelativeFilePathRecorded(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, dir, "bad.go", `package service
|
|
|
|
func f(ctx interface{}, db interface{}) {
|
|
db.QueryContext(ctx, "SELECT id FROM users WHERE id = $1", 1)
|
|
}
|
|
`)
|
|
vs := checkRawSQL(dir)
|
|
if len(vs) == 0 {
|
|
t.Fatal("expected violation")
|
|
}
|
|
if vs[0].file != "bad.go" {
|
|
t.Fatalf("expected relative file path 'bad.go', got %q", vs[0].file)
|
|
}
|
|
}
|
|
|
|
func TestCheckRawSQL_NonGoFile_Skipped(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, dir, "query.sql", "SELECT id FROM users WHERE id = $1;\n")
|
|
vs := checkRawSQL(dir)
|
|
if len(vs) != 0 {
|
|
t.Fatalf("expected no violations for .sql file (only .go is scanned), got %d", len(vs))
|
|
}
|
|
}
|
|
|
|
func TestCheckRawSQL_PluginDir_Allowed(t *testing.T) {
|
|
dir := t.TempDir()
|
|
pluginDir := filepath.Join(dir, "internal", "plugins", "myplugin")
|
|
writeTestFile(t, filepath.Join(pluginDir, "service.go"), `package myplugin
|
|
|
|
func init() {
|
|
db.ExecContext(ctx, "INSERT INTO myplugin_rows (id) VALUES ($1)", id)
|
|
}
|
|
`, 0644)
|
|
vs := checkRawSQL(dir)
|
|
if len(vs) != 0 {
|
|
t.Fatalf("expected no violations for internal/plugins/ dir, got %d", len(vs))
|
|
}
|
|
}
|