Files
mailgoserver/internal/backup/server_test.go
T

188 lines
5.1 KiB
Go

package backup
import (
"bytes"
"database/sql"
"os"
"path/filepath"
"testing"
_ "modernc.org/sqlite"
)
func seedDir(t *testing.T) string {
t.Helper()
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "a.txt"), []byte("hello"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Join(dir, "sub"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "sub", "b.key"), []byte("secret-bytes"), 0o600); err != nil {
t.Fatal(err)
}
return dir
}
func TestWriteRestoreServerRoundTripPlain(t *testing.T) {
src := seedDir(t)
var buf bytes.Buffer
if err := WriteServer(&buf, src, ""); err != nil {
t.Fatal(err)
}
dst := filepath.Join(t.TempDir(), "restored")
if err := RestoreServer(&buf, dst, "", false); err != nil {
t.Fatal(err)
}
got, err := os.ReadFile(filepath.Join(dst, "sub", "b.key"))
if err != nil {
t.Fatal(err)
}
if string(got) != "secret-bytes" {
t.Fatalf("got %q", got)
}
}
func TestWriteRestoreServerRoundTripEncrypted(t *testing.T) {
src := seedDir(t)
var buf bytes.Buffer
if err := WriteServer(&buf, src, "correct horse"); err != nil {
t.Fatal(err)
}
dst := filepath.Join(t.TempDir(), "restored")
if err := RestoreServer(bytes.NewReader(buf.Bytes()), dst, "correct horse", false); err != nil {
t.Fatal(err)
}
got, err := os.ReadFile(filepath.Join(dst, "a.txt"))
if err != nil {
t.Fatal(err)
}
if string(got) != "hello" {
t.Fatalf("got %q", got)
}
// Wrong passphrase must fail, not silently produce garbage.
if err := RestoreServer(bytes.NewReader(buf.Bytes()), filepath.Join(t.TempDir(), "wrong"), "wrong passphrase", false); err == nil {
t.Fatal("expected an error restoring with the wrong passphrase")
}
}
func TestRestoreServerRefusesNonEmptyTargetWithoutForce(t *testing.T) {
src := seedDir(t)
var buf bytes.Buffer
if err := WriteServer(&buf, src, ""); err != nil {
t.Fatal(err)
}
dst := t.TempDir()
if err := os.WriteFile(filepath.Join(dst, "existing.txt"), []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
if err := RestoreServer(&buf, dst, "", false); err == nil {
t.Fatal("expected restore into a non-empty directory to be refused without force")
}
}
func TestWriteServerWithOverrideSubstitutesFileContent(t *testing.T) {
src := seedDir(t)
override := filepath.Join(t.TempDir(), "snapshot.txt")
if err := os.WriteFile(override, []byte("snapshot-content"), 0o644); err != nil {
t.Fatal(err)
}
var buf bytes.Buffer
if err := WriteServerWithOverride(&buf, src, "a.txt", override, ""); err != nil {
t.Fatal(err)
}
dst := filepath.Join(t.TempDir(), "restored")
if err := RestoreServer(&buf, dst, "", false); err != nil {
t.Fatal(err)
}
got, err := os.ReadFile(filepath.Join(dst, "a.txt"))
if err != nil {
t.Fatal(err)
}
if string(got) != "snapshot-content" {
t.Fatalf("got %q, want the override file's content substituted in place of the live a.txt", got)
}
// The untouched sibling file should still come through unmodified.
if got, err := os.ReadFile(filepath.Join(dst, "sub", "b.key")); err != nil || string(got) != "secret-bytes" {
t.Fatalf("sub/b.key = %q, %v; expected untouched", got, err)
}
}
func TestWriteServerConsistentSnapshotsLiveDatabase(t *testing.T) {
src := seedDir(t)
dbPath := filepath.Join(src, "app.db")
sqlDB, err := sql.Open("sqlite", dbPath)
if err != nil {
t.Fatal(err)
}
defer sqlDB.Close()
if _, err := sqlDB.Exec(`CREATE TABLE t (v TEXT)`); err != nil {
t.Fatal(err)
}
if _, err := sqlDB.Exec(`INSERT INTO t (v) VALUES ('before-backup')`); err != nil {
t.Fatal(err)
}
var buf bytes.Buffer
if err := WriteServerConsistent(sqlDB, &buf, src, dbPath, ""); err != nil {
t.Fatal(err)
}
// A write after the snapshot was taken must not appear in the archive — proves
// VACUUM INTO's point-in-time semantics are actually being used, not just a raw
// copy of the (possibly since-changed) live file.
if _, err := sqlDB.Exec(`INSERT INTO t (v) VALUES ('after-backup')`); err != nil {
t.Fatal(err)
}
dst := filepath.Join(t.TempDir(), "restored")
if err := RestoreServer(&buf, dst, "", false); err != nil {
t.Fatal(err)
}
restoredDB, err := sql.Open("sqlite", filepath.Join(dst, "app.db"))
if err != nil {
t.Fatal(err)
}
defer restoredDB.Close()
var rows []string
res, err := restoredDB.Query(`SELECT v FROM t ORDER BY v`)
if err != nil {
t.Fatal(err)
}
defer res.Close()
for res.Next() {
var v string
res.Scan(&v)
rows = append(rows, v)
}
if len(rows) != 1 || rows[0] != "before-backup" {
t.Fatalf("got %v, want exactly [\"before-backup\"] (the post-snapshot insert must not appear)", rows)
}
}
func TestRestoreServerForceOverwritesNonEmptyTarget(t *testing.T) {
src := seedDir(t)
var buf bytes.Buffer
if err := WriteServer(&buf, src, ""); err != nil {
t.Fatal(err)
}
dst := t.TempDir()
if err := os.WriteFile(filepath.Join(dst, "existing.txt"), []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
if err := RestoreServer(&buf, dst, "", true); err != nil {
t.Fatalf("expected force restore to succeed, got: %v", err)
}
if _, err := os.Stat(filepath.Join(dst, "a.txt")); err != nil {
t.Fatalf("expected restored file to exist: %v", err)
}
}