internal/restoretest wiederholt BAK-03s eigene Pruefdiskt als Produktcode: echter Restore der neuesten Sicherung, echter Kurzstart einer isolierten Postgres-Instanz, echte SELECT-1-Abfrage; Objekt-Seite echter restic-Restore + Inhaltspruefung. JSONL-Historie (append-only), sichtbare Warnung ueber denselben OPS-05-Pull-Weg wie BAK-08, eigenes /metrics-Modul (archive-restoretest). Zwei reale Defekte gefunden und behoben: Unix-Socket-Pfadlaenge unter tief verschachtelten Testpfaden, restic-stderr-Vermischung beim JSON-Parsen unter dem Dienstnutzer. Real auf 131 verdrahtet und ausgeloest (beide Testarten erfolgreich, End-zu-Ende ueber OPS-03 bestaetigt).
123 lines
3.7 KiB
Go
123 lines
3.7 KiB
Go
package restoretest
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.perlbach24.de/scripte/nexarch/archive/internal/backup"
|
|
"gitea.perlbach24.de/scripte/nexarch/archive/internal/objectbackup"
|
|
)
|
|
|
|
func requireDBTestConfig(t *testing.T) backup.Config {
|
|
t.Helper()
|
|
user := os.Getenv("TEST_BACKUP_PG_USER")
|
|
if user == "" {
|
|
t.Skip("TEST_BACKUP_PG_USER nicht gesetzt, Integrationstest uebersprungen")
|
|
}
|
|
if _, err := exec.LookPath("pg_combinebackup"); err != nil {
|
|
t.Skip("pg_combinebackup nicht installiert, Integrationstest uebersprungen")
|
|
}
|
|
if _, err := exec.LookPath("pg_ctl"); err != nil {
|
|
t.Skip("pg_ctl nicht installiert, Integrationstest uebersprungen")
|
|
}
|
|
return backup.Config{
|
|
Host: envOrT("TEST_BACKUP_PG_HOST", "localhost"),
|
|
Port: envOrT("TEST_BACKUP_PG_PORT", "5432"),
|
|
User: user,
|
|
Password: os.Getenv("TEST_BACKUP_PG_PASSWORD"),
|
|
BackupDir: t.TempDir(),
|
|
}
|
|
}
|
|
|
|
func envOrT(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
// TestRunDatabaseTest_SucceedsAgainstRealBackup ist Pruefung 1: geplanter
|
|
// Testlauf gegen aktuelle Sicherung erfolgreich durchgefuehrt.
|
|
func TestRunDatabaseTest_SucceedsAgainstRealBackup(t *testing.T) {
|
|
cfg := requireDBTestConfig(t)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
defer cancel()
|
|
|
|
genID := backup.NewGenerationID(time.Now())
|
|
if _, err := backup.FullBackup(ctx, cfg, genID); err != nil {
|
|
t.Fatalf("fullbackup: %v", err)
|
|
}
|
|
|
|
res := RunDatabaseTest(ctx, cfg, PgConfig{TestPort: "55434"}, t.TempDir())
|
|
if !res.Success {
|
|
t.Fatalf("erwartet erfolgreichen testlauf, habe: %+v", res)
|
|
}
|
|
if res.Source != genID {
|
|
t.Fatalf("source = %q, want %q", res.Source, genID)
|
|
}
|
|
}
|
|
|
|
// TestRunDatabaseTest_DetectsCorruptedBackup ist Pruefung 2: absichtlich
|
|
// beschaedigte Sicherung laesst den Testlauf sichtbar fehlschlagen.
|
|
func TestRunDatabaseTest_DetectsCorruptedBackup(t *testing.T) {
|
|
cfg := requireDBTestConfig(t)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
defer cancel()
|
|
|
|
genID := backup.NewGenerationID(time.Now())
|
|
manifest, err := backup.FullBackup(ctx, cfg, genID)
|
|
if err != nil {
|
|
t.Fatalf("fullbackup: %v", err)
|
|
}
|
|
|
|
// Absichtliche Beschaedigung: base.tar.gz durch Muell ersetzen.
|
|
tarPath := filepath.Join(filepath.Dir(manifest), backup.BaseTarGzFile)
|
|
if err := os.WriteFile(tarPath, []byte("das ist kein gueltiges tar.gz"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
res := RunDatabaseTest(ctx, cfg, PgConfig{TestPort: "55435"}, t.TempDir())
|
|
if res.Success {
|
|
t.Fatal("erwartet fehlschlag bei beschaedigter sicherung, testlauf meldete erfolg")
|
|
}
|
|
if res.Detail == "" {
|
|
t.Fatal("erwartet aussagekraeftiges detail zum fehlschlag")
|
|
}
|
|
}
|
|
|
|
func requireObjTestConfig(t *testing.T) objectbackup.Config {
|
|
t.Helper()
|
|
if _, err := exec.LookPath("restic"); err != nil {
|
|
t.Skip("restic nicht installiert, Integrationstest uebersprungen")
|
|
}
|
|
cfg := objectbackup.Config{RepoDir: filepath.Join(t.TempDir(), "repo"), Password: "restoretest-passwort"}
|
|
if err := objectbackup.InitRepo(context.Background(), cfg); err != nil {
|
|
t.Fatalf("initrepo: %v", err)
|
|
}
|
|
return cfg
|
|
}
|
|
|
|
// TestRunObjectTest_SucceedsAgainstRealSnapshot ist Pruefung 1 fuer den
|
|
// Objekt-Storage-Teil.
|
|
func TestRunObjectTest_SucceedsAgainstRealSnapshot(t *testing.T) {
|
|
cfg := requireObjTestConfig(t)
|
|
ctx := context.Background()
|
|
|
|
sourceDir := t.TempDir()
|
|
if err := os.WriteFile(filepath.Join(sourceDir, "datei.txt"), []byte("inhalt"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := objectbackup.Backup(ctx, cfg, sourceDir); err != nil {
|
|
t.Fatalf("backup: %v", err)
|
|
}
|
|
|
|
res := RunObjectTest(ctx, cfg, t.TempDir())
|
|
if !res.Success {
|
|
t.Fatalf("erwartet erfolgreichen testlauf, habe: %+v", res)
|
|
}
|
|
}
|