a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
287 lines
7.0 KiB
Go
287 lines
7.0 KiB
Go
package languages
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
func TestGoSQL_DetectsTablesFromQuery(t *testing.T) {
|
|
src := `package foo
|
|
|
|
type DB struct{}
|
|
|
|
func (d *DB) Query(q string, args ...any) (any, error) { return nil, nil }
|
|
func (d *DB) Exec(q string, args ...any) (any, error) { return nil, nil }
|
|
|
|
func Run(db *DB) {
|
|
_, _ = db.Query("SELECT id, name FROM users WHERE active = true")
|
|
_, _ = db.Exec("INSERT INTO sessions (user_id) VALUES (1)")
|
|
}
|
|
`
|
|
fix := runGoExtract(t, src)
|
|
|
|
tables := fix.nodesByKind[graph.KindTable]
|
|
if len(tables) != 2 {
|
|
t.Fatalf("expected 2 KindTable, got %d: %+v", len(tables), tables)
|
|
}
|
|
gotIDs := map[string]bool{}
|
|
for _, n := range tables {
|
|
gotIDs[n.ID] = true
|
|
}
|
|
if !gotIDs["db::generic::users"] {
|
|
t.Errorf("missing users table, got %v", gotIDs)
|
|
}
|
|
if !gotIDs["db::generic::sessions"] {
|
|
t.Errorf("missing sessions table, got %v", gotIDs)
|
|
}
|
|
|
|
queries := fix.edgesByKind[graph.EdgeQueries]
|
|
if len(queries) != 2 {
|
|
t.Errorf("expected 2 EdgeQueries, got %d", len(queries))
|
|
}
|
|
}
|
|
|
|
func TestGoSQL_EmitsColumnReadsAndWrites(t *testing.T) {
|
|
src := `package foo
|
|
|
|
type DB struct{}
|
|
func (d *DB) Query(q string, args ...any) (any, error) { return nil, nil }
|
|
func (d *DB) Exec(q string, args ...any) (any, error) { return nil, nil }
|
|
|
|
func Run(db *DB) {
|
|
_, _ = db.Query("SELECT id, email FROM users WHERE id = $1")
|
|
_, _ = db.Exec("UPDATE users SET email = $1, updated_at = NOW() WHERE id = $2")
|
|
_, _ = db.Exec("INSERT INTO sessions (user_id, token) VALUES ($1, $2)")
|
|
}
|
|
`
|
|
fix := runGoExtract(t, src)
|
|
|
|
cols := fix.nodesByKind[graph.KindColumn]
|
|
if len(cols) == 0 {
|
|
t.Fatalf("expected KindColumn nodes; got none")
|
|
}
|
|
wantCols := map[string]bool{
|
|
"col::generic::users.id": false,
|
|
"col::generic::users.email": false,
|
|
"col::generic::users.updated_at": false,
|
|
"col::generic::sessions.user_id": false,
|
|
"col::generic::sessions.token": false,
|
|
}
|
|
for _, n := range cols {
|
|
if _, ok := wantCols[n.ID]; ok {
|
|
wantCols[n.ID] = true
|
|
}
|
|
}
|
|
for id, found := range wantCols {
|
|
if !found {
|
|
t.Errorf("expected KindColumn %s; got %v", id, nodeIDs(cols))
|
|
}
|
|
}
|
|
|
|
reads := fix.edgesByKind[graph.EdgeReadsCol]
|
|
hasIDRead := false
|
|
for _, e := range reads {
|
|
if e.To == "col::generic::users.id" {
|
|
hasIDRead = true
|
|
}
|
|
}
|
|
if !hasIDRead {
|
|
t.Errorf("expected EdgeReadsCol → users.id; got %v", edgeTargets(reads))
|
|
}
|
|
|
|
writes := fix.edgesByKind[graph.EdgeWritesCol]
|
|
hasEmailWrite := false
|
|
hasTokenWrite := false
|
|
for _, e := range writes {
|
|
if e.To == "col::generic::users.email" {
|
|
hasEmailWrite = true
|
|
}
|
|
if e.To == "col::generic::sessions.token" {
|
|
hasTokenWrite = true
|
|
}
|
|
}
|
|
if !hasEmailWrite {
|
|
t.Errorf("expected EdgeWritesCol → users.email; got %v", edgeTargets(writes))
|
|
}
|
|
if !hasTokenWrite {
|
|
t.Errorf("expected EdgeWritesCol → sessions.token; got %v", edgeTargets(writes))
|
|
}
|
|
}
|
|
|
|
func nodeIDs(nodes []*graph.Node) []string {
|
|
out := make([]string, 0, len(nodes))
|
|
for _, n := range nodes {
|
|
out = append(out, n.ID)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func TestGoSQL_DynamicQuerySkipped(t *testing.T) {
|
|
src := `package foo
|
|
|
|
type DB struct{}
|
|
|
|
func (d *DB) Query(q string) (any, error) { return nil, nil }
|
|
|
|
func Run(db *DB, q string) {
|
|
_, _ = db.Query(q)
|
|
}
|
|
`
|
|
fix := runGoExtract(t, src)
|
|
if got := len(fix.nodesByKind[graph.KindTable]); got != 0 {
|
|
t.Errorf("dynamic query should not produce tables, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestGoSQL_NonSQLMethodIgnored(t *testing.T) {
|
|
src := `package foo
|
|
|
|
type Cache struct{}
|
|
|
|
func (c *Cache) Set(key string, val any) {}
|
|
|
|
func Run(c *Cache) {
|
|
c.Set("FROM users", nil)
|
|
}
|
|
`
|
|
fix := runGoExtract(t, src)
|
|
if got := len(fix.nodesByKind[graph.KindTable]); got != 0 {
|
|
t.Errorf("non-SQL method must not produce tables, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestGoSQL_SchemaQualifiedTables(t *testing.T) {
|
|
src := `package foo
|
|
|
|
type DB struct{}
|
|
|
|
func (d *DB) Query(q string) (any, error) { return nil, nil }
|
|
|
|
func Run(db *DB) {
|
|
_, _ = db.Query("SELECT * FROM public.users JOIN auth.sessions ON id = session_id")
|
|
}
|
|
`
|
|
fix := runGoExtract(t, src)
|
|
tables := fix.nodesByKind[graph.KindTable]
|
|
if len(tables) != 2 {
|
|
t.Fatalf("expected 2 schema-qualified tables, got %d", len(tables))
|
|
}
|
|
gotSchemas := map[string]string{}
|
|
for _, n := range tables {
|
|
schema, _ := n.Meta["schema"].(string)
|
|
table, _ := n.Meta["table"].(string)
|
|
gotSchemas[table] = schema
|
|
}
|
|
if gotSchemas["users"] != "public" {
|
|
t.Errorf("users schema = %q", gotSchemas["users"])
|
|
}
|
|
if gotSchemas["sessions"] != "auth" {
|
|
t.Errorf("sessions schema = %q", gotSchemas["sessions"])
|
|
}
|
|
}
|
|
|
|
func TestGoSQL_DialectInferredFromPostgresImport(t *testing.T) {
|
|
src := `package foo
|
|
|
|
import (
|
|
_ "github.com/lib/pq"
|
|
"database/sql"
|
|
)
|
|
|
|
func Run(db *sql.DB) {
|
|
_, _ = db.Query("SELECT * FROM users")
|
|
}
|
|
`
|
|
fix := runGoExtract(t, src)
|
|
tables := fix.nodesByKind[graph.KindTable]
|
|
if len(tables) != 1 {
|
|
t.Fatalf("expected 1 table, got %d", len(tables))
|
|
}
|
|
if tables[0].ID != "db::postgres::users" {
|
|
t.Errorf("expected postgres dialect on ID, got %q", tables[0].ID)
|
|
}
|
|
if d, _ := tables[0].Meta["dialect"].(string); d != "postgres" {
|
|
t.Errorf("dialect meta = %q", d)
|
|
}
|
|
}
|
|
|
|
func TestGoSQL_DialectInferredFromMysqlImport(t *testing.T) {
|
|
src := `package foo
|
|
|
|
import (
|
|
_ "github.com/go-sql-driver/mysql"
|
|
"database/sql"
|
|
)
|
|
|
|
func Run(db *sql.DB) {
|
|
_, _ = db.Query("SELECT * FROM orders")
|
|
}
|
|
`
|
|
fix := runGoExtract(t, src)
|
|
tables := fix.nodesByKind[graph.KindTable]
|
|
if len(tables) != 1 || tables[0].ID != "db::mysql::orders" {
|
|
t.Errorf("expected mysql dialect, got %+v", tables)
|
|
}
|
|
}
|
|
|
|
func TestGoSQL_DialectInferredFromPgxV5(t *testing.T) {
|
|
// Major-version-suffix imports (pgx/v5) resolve via the
|
|
// prefix-stripped lookup.
|
|
src := `package foo
|
|
|
|
import (
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
type Pool struct{}
|
|
func (p *Pool) Query(q string) (any, error) { return nil, nil }
|
|
|
|
func Run(p *Pool) {
|
|
_, _ = p.Query("SELECT * FROM users")
|
|
}
|
|
`
|
|
fix := runGoExtract(t, src)
|
|
tables := fix.nodesByKind[graph.KindTable]
|
|
if len(tables) != 1 || tables[0].ID != "db::postgres::users" {
|
|
t.Errorf("expected postgres (pgx/v5 prefix-stripped to pgx), got %+v", tables)
|
|
}
|
|
}
|
|
|
|
func TestGoSQL_NoDriverFallsBackToGeneric(t *testing.T) {
|
|
src := `package foo
|
|
|
|
type DB struct{}
|
|
func (d *DB) Query(q string) (any, error) { return nil, nil }
|
|
|
|
func Run(db *DB) {
|
|
_, _ = db.Query("SELECT * FROM users")
|
|
}
|
|
`
|
|
fix := runGoExtract(t, src)
|
|
tables := fix.nodesByKind[graph.KindTable]
|
|
if len(tables) != 1 || tables[0].ID != "db::generic::users" {
|
|
t.Errorf("expected generic dialect fallback, got %+v", tables)
|
|
}
|
|
}
|
|
|
|
func TestGoSQL_DedupedAcrossCallSites(t *testing.T) {
|
|
src := `package foo
|
|
|
|
type DB struct{}
|
|
|
|
func (d *DB) Query(q string) (any, error) { return nil, nil }
|
|
|
|
func A(db *DB) { _, _ = db.Query("SELECT * FROM users") }
|
|
func B(db *DB) { _, _ = db.Query("SELECT id FROM users WHERE id = 1") }
|
|
`
|
|
fix := runGoExtract(t, src)
|
|
tables := fix.nodesByKind[graph.KindTable]
|
|
if len(tables) != 1 {
|
|
t.Errorf("expected 1 deduped users node, got %d", len(tables))
|
|
}
|
|
if got := len(fix.edgesByKind[graph.EdgeQueries]); got != 2 {
|
|
t.Errorf("expected 2 EdgeQueries (one per caller), got %d", got)
|
|
}
|
|
}
|