79 lines
2.7 KiB
Go
79 lines
2.7 KiB
Go
package store
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
// TestBackupSQLiteCreatesValidRestorePoint pins the F4 happy path: a green integrity_check followed by a
|
|
// VACUUM INTO copy that is itself a valid, integrity-clean, openable SQLite project database.
|
|
func TestBackupSQLiteCreatesValidRestorePoint(t *testing.T) {
|
|
dir := t.TempDir()
|
|
dbPath := filepath.Join(dir, "book.db")
|
|
s, err := Open(dbPath) // a real, migrated project DB
|
|
if err != nil {
|
|
t.Fatalf("open source: %v", err)
|
|
}
|
|
s.Close()
|
|
|
|
backupDir := filepath.Join(dir, "backups")
|
|
const stamp = "20260802T000000Z"
|
|
path, err := BackupSQLite(dbPath, backupDir, stamp)
|
|
if err != nil {
|
|
t.Fatalf("backup: %v", err)
|
|
}
|
|
if want := filepath.Join(backupDir, stamp+".db"); path != want {
|
|
t.Fatalf("backup path = %q, want %q", path, want)
|
|
}
|
|
if _, err := os.Stat(path); err != nil {
|
|
t.Fatalf("backup file missing: %v", err)
|
|
}
|
|
// The backup must itself pass integrity and be openable as a store (a real, migrated copy).
|
|
if err := IntegrityCheck(path); err != nil {
|
|
t.Errorf("the backup failed its own integrity_check: %v", err)
|
|
}
|
|
s2, err := Open(path)
|
|
if err != nil {
|
|
t.Fatalf("backup is not openable as a project store: %v", err)
|
|
}
|
|
s2.Close()
|
|
}
|
|
|
|
// TestBackupRefusesCorruptSource pins the SPOF half: a source that is not a valid SQLite file fails loud and
|
|
// writes NO backup — a corrupt bank must never be laundered into a trusted restore point.
|
|
func TestBackupRefusesCorruptSource(t *testing.T) {
|
|
dir := t.TempDir()
|
|
dbPath := filepath.Join(dir, "corrupt.db")
|
|
if err := os.WriteFile(dbPath, []byte("this is not a sqlite database, just garbage bytes"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
backupDir := filepath.Join(dir, "backups")
|
|
if _, err := BackupSQLite(dbPath, backupDir, "x"); err == nil {
|
|
t.Fatal("a corrupt source must fail loud, not silently produce a backup")
|
|
}
|
|
if _, err := os.Stat(filepath.Join(backupDir, "x.db")); err == nil {
|
|
t.Error("no backup file may be written when the source fails integrity")
|
|
}
|
|
}
|
|
|
|
// TestBackupRefusesMissingSource and refuses to overwrite an existing restore point.
|
|
func TestBackupRefusesMissingSourceAndOverwrite(t *testing.T) {
|
|
dir := t.TempDir()
|
|
if _, err := BackupSQLite(filepath.Join(dir, "nope.db"), filepath.Join(dir, "b"), "x"); err == nil {
|
|
t.Fatal("a missing source must be a loud error")
|
|
}
|
|
dbPath := filepath.Join(dir, "book.db")
|
|
s, err := Open(dbPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s.Close()
|
|
backupDir := filepath.Join(dir, "backups")
|
|
if _, err := BackupSQLite(dbPath, backupDir, "same"); err != nil {
|
|
t.Fatalf("first backup: %v", err)
|
|
}
|
|
if _, err := BackupSQLite(dbPath, backupDir, "same"); err == nil {
|
|
t.Fatal("a second backup at the same stamp must refuse to overwrite the restore point")
|
|
}
|
|
}
|