package db import ( "database/sql" "os" "path/filepath" "testing" ) // The point of VACUUM INTO over a file copy is that it captures rows still // sitting in the WAL. This writes with the source connection open (so the WAL // is hot and unlikely to have been checkpointed) and asserts the backup has // them. func TestBackupCapturesLiveWrites(t *testing.T) { dir := t.TempDir() src := filepath.Join(dir, "petal.db") dest := filepath.Join(dir, "backups", "petal-backup.db") d, err := Open(src) if err != nil { t.Fatalf("open source: %v", err) } defer d.Close() if _, err := d.Exec( `INSERT INTO documents (id, user_id, title, content_text) VALUES ('d1', ?, '春天', 'hello 春天')`, LocalUserID, ); err != nil { t.Fatalf("insert: %v", err) } if err := Backup(src, dest); err != nil { t.Fatalf("backup: %v", err) } // VACUUM INTO emits a single self-contained file — no -wal/-shm to ship // alongside it. for _, suffix := range []string{"-wal", "-shm"} { if _, err := os.Stat(dest + suffix); err == nil { t.Errorf("backup left a %s companion file behind", suffix) } } copyConn, err := sql.Open("sqlite", dsn(dest)) if err != nil { t.Fatalf("open backup: %v", err) } defer copyConn.Close() var title string if err := copyConn.QueryRow(`SELECT title FROM documents WHERE id = 'd1'`).Scan(&title); err != nil { t.Fatalf("row missing from backup: %v", err) } if title != "春天" { t.Errorf("title = %q, want 春天", title) } // The seeded user has to come across too, or a restore would orphan every // document's foreign key. var users int if err := copyConn.QueryRow(`SELECT COUNT(*) FROM users WHERE id = ?`, LocalUserID).Scan(&users); err != nil { t.Fatalf("count users: %v", err) } if users != 1 { t.Errorf("users in backup = %d, want 1", users) } } // A second run to the same path must fail loudly rather than clobber or // half-write the previous good backup. func TestBackupRefusesExistingDestination(t *testing.T) { dir := t.TempDir() src := filepath.Join(dir, "petal.db") dest := filepath.Join(dir, "petal-backup.db") d, err := Open(src) if err != nil { t.Fatalf("open source: %v", err) } defer d.Close() if err := Backup(src, dest); err != nil { t.Fatalf("first backup: %v", err) } before, err := os.ReadFile(dest) if err != nil { t.Fatalf("read backup: %v", err) } if err := Backup(src, dest); err == nil { t.Fatal("second backup to the same path succeeded; want an error") } after, err := os.ReadFile(dest) if err != nil { t.Fatalf("re-read backup: %v", err) } if len(before) != len(after) { t.Errorf("existing backup was modified: %d bytes → %d", len(before), len(after)) } } func TestBackupMissingSource(t *testing.T) { dir := t.TempDir() if err := Backup(filepath.Join(dir, "nope.db"), filepath.Join(dir, "out.db")); err == nil { t.Fatal("backup of a nonexistent database succeeded; want an error") } }