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

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