//nolint:testpackage // exercises unexported read-only open internals package database import ( "context" "database/sql" "errors" "path/filepath" "testing" ) // genuineSnapshotDB writes a real snapshot database (the full schema // applied) to a fresh file and returns its path. func genuineSnapshotDB(t *testing.T) string { t.Helper() path := filepath.Join(t.TempDir(), "snapshot.db") db, err := New(context.Background(), path) if err != nil { t.Fatalf("creating snapshot database: %v", err) } err = db.Close() if err != nil { t.Fatalf("closing snapshot database: %v", err) } return path } // forgedDB creates an empty database file and runs the given statements // against it read-write, so a test can plant schema objects the real // schema never defines. func forgedDB(t *testing.T, stmts ...string) string { t.Helper() path := filepath.Join(t.TempDir(), "forged.db") db, err := sql.Open("sqlite", path) if err != nil { t.Fatalf("opening forged database: %v", err) } for _, stmt := range stmts { _, err = db.ExecContext(context.Background(), stmt) if err != nil { t.Fatalf("executing %q: %v", stmt, err) } } err = db.Close() if err != nil { t.Fatalf("closing forged database: %v", err) } return path } func TestOpenReadOnlyAcceptsGenuineSnapshot(t *testing.T) { t.Parallel() db, err := OpenReadOnly(context.Background(), genuineSnapshotDB(t)) if err != nil { t.Fatalf("OpenReadOnly refused a genuine snapshot database: %v", err) } t.Cleanup(func() { _ = db.Close() }) } func TestOpenReadOnlyRefusesWrites(t *testing.T) { t.Parallel() db, err := OpenReadOnly(context.Background(), genuineSnapshotDB(t)) if err != nil { t.Fatalf("OpenReadOnly: %v", err) } t.Cleanup(func() { _ = db.Close() }) // A schema write depends on no table columns, so the only reason it // can fail is that the database is open read-only. _, err = db.Conn().ExecContext(context.Background(), "CREATE TABLE probe_readonly (x)") if err == nil { t.Fatal("expected a write to a read-only snapshot database to fail") } } func TestOpenReadOnlyRejectsView(t *testing.T) { t.Parallel() path := forgedDB(t, "CREATE VIEW files AS SELECT 1 AS path") _, err := OpenReadOnly(context.Background(), path) if !errors.Is(err, errUntrustedSnapshotSchema) { t.Fatalf("expected a view named files to be refused, got %v", err) } } func TestOpenReadOnlyRejectsTrigger(t *testing.T) { t.Parallel() path := forgedDB(t, "CREATE TABLE files (path TEXT)", "CREATE TRIGGER t AFTER INSERT ON files BEGIN SELECT 1; END") _, err := OpenReadOnly(context.Background(), path) if !errors.Is(err, errUntrustedSnapshotSchema) { t.Fatalf("expected a trigger to be refused, got %v", err) } } func TestOpenReadOnlyRejectsMissingTable(t *testing.T) { t.Parallel() // Only one of the expected tables is present. path := forgedDB(t, "CREATE TABLE files (path TEXT)") _, err := OpenReadOnly(context.Background(), path) if !errors.Is(err, errUntrustedSnapshotSchema) { t.Fatalf("expected a missing expected table to be refused, got %v", err) } } func TestIsVirtualTableSQL(t *testing.T) { t.Parallel() cases := []struct { sql string want bool }{ {"CREATE VIRTUAL TABLE t USING fts5(x)", true}, {" create virtual table t using fts5(x)", true}, {"CREATE TABLE t (x)", false}, {"CREATE VIEW t AS SELECT 1", false}, {"", false}, } for _, c := range cases { if got := isVirtualTableSQL(c.sql); got != c.want { t.Errorf("isVirtualTableSQL(%q) = %v, want %v", c.sql, got, c.want) } } }