package db import ( "database/sql" "os" "path/filepath" "testing" ) func TestOpen(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "test.db") database, err := Open(path) if err != nil { t.Fatalf("Open: %v", err) } defer func() { _ = database.Close() }() // Verify WAL mode is enabled. var journalMode string if err := database.QueryRow("PRAGMA journal_mode").Scan(&journalMode); err != nil { t.Fatalf("query journal_mode: %v", err) } if journalMode != "wal" { t.Fatalf("journal_mode = %q, want %q", journalMode, "wal") } // Verify foreign keys are enabled. var fk int if err := database.QueryRow("PRAGMA foreign_keys").Scan(&fk); err != nil { t.Fatalf("query foreign_keys: %v", err) } if fk != 1 { t.Fatalf("foreign_keys = %d, want 1", fk) } // Verify busy timeout. var timeout int if err := database.QueryRow("PRAGMA busy_timeout").Scan(&timeout); err != nil { t.Fatalf("query busy_timeout: %v", err) } if timeout != 5000 { t.Fatalf("busy_timeout = %d, want 5000", timeout) } } func TestOpenFilePermissions(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "test.db") database, err := Open(path) if err != nil { t.Fatalf("Open: %v", err) } _ = database.Close() info, err := os.Stat(path) if err != nil { t.Fatalf("Stat: %v", err) } perm := info.Mode().Perm() if perm != 0600 { t.Fatalf("permissions = %o, want 0600", perm) } } func TestOpenCreatesParentDir(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "sub", "dir", "test.db") database, err := Open(path) if err != nil { t.Fatalf("Open: %v", err) } _ = database.Close() if _, err := os.Stat(path); err != nil { t.Fatalf("database file does not exist: %v", err) } } func TestOpenExistingDB(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "test.db") // Create and populate. db1, err := Open(path) if err != nil { t.Fatalf("Open (first): %v", err) } if _, err := db1.Exec("CREATE TABLE t (id INTEGER PRIMARY KEY)"); err != nil { t.Fatalf("create table: %v", err) } if _, err := db1.Exec("INSERT INTO t (id) VALUES (42)"); err != nil { t.Fatalf("insert: %v", err) } _ = db1.Close() // Reopen and verify data persists. db2, err := Open(path) if err != nil { t.Fatalf("Open (second): %v", err) } defer func() { _ = db2.Close() }() var id int if err := db2.QueryRow("SELECT id FROM t").Scan(&id); err != nil { t.Fatalf("select: %v", err) } if id != 42 { t.Fatalf("id = %d, want 42", id) } } var testMigrations = []Migration{ { Version: 1, Name: "create users", SQL: `CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)`, }, { Version: 2, Name: "add email", SQL: `ALTER TABLE users ADD COLUMN email TEXT NOT NULL DEFAULT ''`, }, } func TestMigrate(t *testing.T) { database := openTestDB(t) if err := Migrate(database, testMigrations); err != nil { t.Fatalf("Migrate: %v", err) } // Verify both migrations applied. version, err := SchemaVersion(database) if err != nil { t.Fatalf("SchemaVersion: %v", err) } if version != 2 { t.Fatalf("schema version = %d, want 2", version) } // Verify schema is correct. if _, err := database.Exec("INSERT INTO users (name, email) VALUES ('a', 'a@b.c')"); err != nil { t.Fatalf("insert into migrated schema: %v", err) } } func TestMigrateIdempotent(t *testing.T) { database := openTestDB(t) // Run twice. if err := Migrate(database, testMigrations); err != nil { t.Fatalf("Migrate (first): %v", err) } if err := Migrate(database, testMigrations); err != nil { t.Fatalf("Migrate (second): %v", err) } version, err := SchemaVersion(database) if err != nil { t.Fatalf("SchemaVersion: %v", err) } if version != 2 { t.Fatalf("schema version = %d, want 2", version) } } func TestMigrateIncremental(t *testing.T) { database := openTestDB(t) // Apply only the first migration. if err := Migrate(database, testMigrations[:1]); err != nil { t.Fatalf("Migrate (first only): %v", err) } version, err := SchemaVersion(database) if err != nil { t.Fatalf("SchemaVersion: %v", err) } if version != 1 { t.Fatalf("schema version = %d, want 1", version) } // Now apply all — should pick up only migration 2. if err := Migrate(database, testMigrations); err != nil { t.Fatalf("Migrate (all): %v", err) } version, err = SchemaVersion(database) if err != nil { t.Fatalf("SchemaVersion: %v", err) } if version != 2 { t.Fatalf("schema version = %d, want 2", version) } } func TestMigrateRecordsName(t *testing.T) { database := openTestDB(t) if err := Migrate(database, testMigrations); err != nil { t.Fatalf("Migrate: %v", err) } var name string err := database.QueryRow( `SELECT name FROM schema_migrations WHERE version = 1`, ).Scan(&name) if err != nil { t.Fatalf("query migration name: %v", err) } if name != "create users" { t.Fatalf("migration name = %q, want %q", name, "create users") } } func TestSchemaVersionEmpty(t *testing.T) { database := openTestDB(t) // Create the table but apply no migrations. if err := Migrate(database, nil); err != nil { t.Fatalf("Migrate(nil): %v", err) } version, err := SchemaVersion(database) if err != nil { t.Fatalf("SchemaVersion: %v", err) } if version != 0 { t.Fatalf("schema version = %d, want 0", version) } } func TestSnapshot(t *testing.T) { database := openTestDB(t) // Create some data. if _, err := database.Exec("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT)"); err != nil { t.Fatalf("create table: %v", err) } if _, err := database.Exec("INSERT INTO t (val) VALUES ('hello')"); err != nil { t.Fatalf("insert: %v", err) } // Snapshot. dir := t.TempDir() snapPath := filepath.Join(dir, "snap.db") if err := Snapshot(database, snapPath); err != nil { t.Fatalf("Snapshot: %v", err) } // Verify snapshot file permissions. info, err := os.Stat(snapPath) if err != nil { t.Fatalf("Stat snapshot: %v", err) } if perm := info.Mode().Perm(); perm != 0600 { t.Fatalf("snapshot permissions = %o, want 0600", perm) } // Open snapshot and verify data. snapDB, err := sql.Open("sqlite", snapPath) if err != nil { t.Fatalf("open snapshot: %v", err) } defer func() { _ = snapDB.Close() }() var val string if err := snapDB.QueryRow("SELECT val FROM t").Scan(&val); err != nil { t.Fatalf("select from snapshot: %v", err) } if val != "hello" { t.Fatalf("val = %q, want %q", val, "hello") } } func TestSnapshotCreatesParentDir(t *testing.T) { database := openTestDB(t) dir := t.TempDir() snapPath := filepath.Join(dir, "sub", "snap.db") if err := Snapshot(database, snapPath); err != nil { t.Fatalf("Snapshot: %v", err) } if _, err := os.Stat(snapPath); err != nil { t.Fatalf("snapshot file does not exist: %v", err) } } func openTestDB(t *testing.T) *sql.DB { t.Helper() dir := t.TempDir() path := filepath.Join(dir, "test.db") database, err := Open(path) if err != nil { t.Fatalf("Open: %v", err) } t.Cleanup(func() { _ = database.Close() }) return database }