Files
artex/db/db_test.go
T
dela 0335d572de
ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
detections / detections (push) Canceled after 0s
First Commit
2026-10-09 08:38:16 +08:00

115 lines
3.2 KiB
Go

package db
import (
"context"
"database/sql"
"errors"
"testing"
"time"
"github.com/jackc/pgx/v5/pgconn"
)
type schemaExecFunc func(context.Context, string, ...any) (sql.Result, error)
func (f schemaExecFunc) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) {
return f(ctx, query, args...)
}
func TestApplySchemaRetriesOnlyDeadlocks(t *testing.T) {
deadlockAttempts := 0
delays := []time.Duration{}
err := applySchemaWithRetry(t.Context(), schemaExecFunc(func(context.Context, string, ...any) (sql.Result, error) {
deadlockAttempts++
if deadlockAttempts < 3 {
return nil, &pgconn.PgError{Code: "40P01", Message: "deadlock detected"}
}
return nil, nil
}), func(delay time.Duration) {
delays = append(delays, delay)
})
if err != nil {
t.Fatal(err)
}
if deadlockAttempts != 3 {
t.Fatalf("schema attempts=%d, want 3", deadlockAttempts)
}
if len(delays) != 2 || delays[0] != schemaDeadlockRetryDelays[0] || delays[1] != schemaDeadlockRetryDelays[1] {
t.Fatalf("schema retry delays=%v", delays)
}
nonRetryable := errors.New("permission denied")
nonRetryableAttempts := 0
err = applySchemaWithRetry(t.Context(), schemaExecFunc(func(context.Context, string, ...any) (sql.Result, error) {
nonRetryableAttempts++
return nil, nonRetryable
}), func(time.Duration) {
t.Fatal("non-deadlock error must not be retried")
})
if !errors.Is(err, nonRetryable) || nonRetryableAttempts != 1 {
t.Fatalf("non-retryable result: attempts=%d err=%v", nonRetryableAttempts, err)
}
}
// testDSN returns the configured DSN, skipping the test when neither the env var
// nor a config file supplies one (DSN no longer has a built-in default).
func testDSN(t *testing.T) string {
t.Helper()
dsn, _, err := DSN()
if err != nil {
t.Skipf("no database config (%v) — skipping", err)
}
return dsn
}
// TestOpenSeed opens the live dev PG, applies schema, seeds, and verifies the
// builtin agents + their variable catalog exist. Skips if PG is unreachable.
func TestOpenSeed(t *testing.T) {
d, err := Open(testDSN(t))
if err != nil {
t.Skipf("postgres unavailable (%v) — skipping", err)
}
defer d.Close()
wantAgents := len(builtinAgents)
var agents int
if err := d.QueryRow(`SELECT count(*) FROM agents WHERE builtin`).Scan(&agents); err != nil {
t.Fatal(err)
}
if agents != wantAgents {
t.Fatalf("want %d builtin agents, got %d", wantAgents, agents)
}
// planner should have at least its seeded catalog vars
wantPlannerVars := 0
for _, a := range builtinAgents {
if a.key == "planner" {
wantPlannerVars = len(a.vars)
break
}
}
var plannerVars int
if err := d.QueryRow(`
SELECT count(*) FROM agent_prompt_vars v
JOIN agents a ON a.id = v.agent_id
WHERE a.key = 'planner'`).Scan(&plannerVars); err != nil {
t.Fatal(err)
}
if plannerVars < wantPlannerVars {
t.Fatalf("want at least %d planner vars, got %d", wantPlannerVars, plannerVars)
}
// re-open must be idempotent (no duplicate agents)
d2, err := Open(testDSN(t))
if err != nil {
t.Fatal(err)
}
defer d2.Close()
if err := d2.QueryRow(`SELECT count(*) FROM agents WHERE builtin`).Scan(&agents); err != nil {
t.Fatal(err)
}
if agents != wantAgents {
t.Fatalf("after re-open want %d agents, got %d", wantAgents, agents)
}
}