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