package dbquery import ( "bytes" "context" "database/sql" "errors" "path/filepath" "strings" "testing" _ "modernc.org/sqlite" ) func fixtureDB(t *testing.T) string { t.Helper() path := filepath.Join(t.TempDir(), "shop.db") db, err := sql.Open("sqlite", path) if err != nil { t.Fatal(err) } defer db.Close() for _, q := range []string{ `CREATE TABLE orders (id INTEGER PRIMARY KEY, note TEXT, data BLOB)`, `INSERT INTO orders (note, data) VALUES ('a tab', x'00ff'), (NULL, NULL), ('two lines', 'x')`, } { if _, err := db.Exec(q); err != nil { t.Fatal(err) } } return path } func TestRunContextFormatsLikeTheCLI(t *testing.T) { t.Parallel() path := fixtureDB(t) for _, opt := range []Options{{}, {Immutable: true}} { var out bytes.Buffer if err := RunContext(context.Background(), &out, path, "SELECT id, note FROM orders ORDER BY id", opt); err != nil { t.Fatalf("%+v: %v", opt, err) } want := "id\tnote\n1\ta\\ttab\n2\tNULL\n3\ttwo\\nlines\n" if out.String() != want { t.Fatalf("%+v: got %q, want %q", opt, out.String(), want) } } } func TestRunContextRefusesWritesAndAttach(t *testing.T) { t.Parallel() path := fixtureDB(t) for _, opt := range []Options{{}, {Immutable: true}} { for _, q := range []string{ "DELETE FROM orders", "ATTACH DATABASE 'x.db' AS x", "VACUUM INTO 'copy.db'", } { if err := RunContext(context.Background(), &bytes.Buffer{}, path, q, opt); err == nil { t.Fatalf("%+v: %q ran", opt, q) } } } } func TestCheckSQLTable(t *testing.T) { t.Parallel() for _, c := range []struct { q string want error }{ {"SELECT 1", nil}, {"SELECT 1; SELECT 2", ErrOneStatement}, {"SELECT ';' AS x", nil}, {"PRAGMA table_info(orders)", nil}, {"PRAGMA query_only = 0", ErrPragmaChange}, {"EXPLAIN QUERY PLAN PRAGMA writable_schema=1", ErrPragmaChange}, {"SELECT pragma FROM t", nil}, } { if got := CheckSQL(c.q); !errors.Is(got, c.want) && !(got == nil && c.want == nil) { t.Errorf("CheckSQL(%q) = %v, want %v", c.q, got, c.want) } } } func TestImmutableOpenAddsTheFlagOnly(t *testing.T) { t.Parallel() if got := dsn("/x/copy.db", Options{}); strings.Contains(got, "immutable") { t.Fatalf("the CLI's open changed: %s", got) } if got := dsn("/x/copy.db", Options{Immutable: true}); !strings.Contains(got, "mode=ro&immutable=1&") { t.Fatalf("the worker's open lacks immutable=1: %s", got) } }