package objectbackup import ( "context" "os" "os/exec" "path/filepath" "testing" ) func requireRestic(t *testing.T) { t.Helper() if _, err := exec.LookPath("restic"); err != nil { t.Skip("restic nicht installiert, Integrationstest uebersprungen") } } func setupTest(t *testing.T) Config { t.Helper() requireRestic(t) cfg := Config{RepoDir: filepath.Join(t.TempDir(), "repo"), Password: "test-passwort-fuer-objectbackup"} if err := InitRepo(context.Background(), cfg); err != nil { t.Fatalf("initrepo: %v", err) } return cfg } func writeFile(t *testing.T, dir, name, content string) { t.Helper() if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0o600); err != nil { t.Fatalf("testdatei %q schreiben: %v", name, err) } } // TestBackup_UnchangedSecondRunTransmitsNothingNew ist Pruefung 1: // zweiter Sicherungslauf nach unveraendertem Bestand ueberraegt keine // Daten erneut. func TestBackup_UnchangedSecondRunTransmitsNothingNew(t *testing.T) { cfg := setupTest(t) ctx := context.Background() sourceDir := t.TempDir() writeFile(t, sourceDir, "dokument.pdf", "unveraenderter inhalt") first, err := Backup(ctx, cfg, sourceDir) if err != nil { t.Fatalf("erste sicherung: %v", err) } if first.FilesNew != 1 { t.Fatalf("erste sicherung: files_new = %d, want 1", first.FilesNew) } second, err := Backup(ctx, cfg, sourceDir) if err != nil { t.Fatalf("zweite sicherung: %v", err) } if second.FilesNew != 0 || second.FilesChanged != 0 { t.Fatalf("zweite sicherung (unveraendert): files_new=%d files_changed=%d, want beide 0", second.FilesNew, second.FilesChanged) } if second.FilesUnmodified != 1 { t.Fatalf("zweite sicherung: files_unmodified = %d, want 1", second.FilesUnmodified) } } // TestBackup_DeduplicatesIdenticalContent ist Pruefung 2: zwei identische // Testdateien belegen nachweislich nur einmal Speicherplatz. func TestBackup_DeduplicatesIdenticalContent(t *testing.T) { cfg := setupTest(t) ctx := context.Background() sourceDir := t.TempDir() content := "exakt identischer inhalt in beiden dateien fuer den dedup-nachweis" writeFile(t, sourceDir, "original.pdf", content) writeFile(t, sourceDir, "kopie.pdf", content) summary, err := Backup(ctx, cfg, sourceDir) if err != nil { t.Fatalf("sicherung: %v", err) } if summary.FilesNew != 2 { t.Fatalf("erwartet 2 neue dateien, habe %d", summary.FilesNew) } // Zwei Dateien mit IDENTISCHEM Inhalt duerfen nur EINEN data_blob // erzeugen - das ist der Dedup-Nachweis (Akzeptanzkriterium 2). if summary.DataBlobs != 1 { t.Fatalf("data_blobs = %d, want 1 (zwei identische dateien haetten nur einen blob erzeugen duerfen - keine dedup)", summary.DataBlobs) } } // TestCheck_DetectsCorruptedPack ist Pruefung 3: Vollstaendigkeitspruefung // erkennt ein beschaedigtes/fehlendes Objekt in der Sicherung. func TestCheck_DetectsCorruptedPack(t *testing.T) { cfg := setupTest(t) ctx := context.Background() sourceDir := t.TempDir() writeFile(t, sourceDir, "wichtig.pdf", "inhalt, der spaeter absichtlich beschaedigt wird") if _, err := Backup(ctx, cfg, sourceDir); err != nil { t.Fatalf("sicherung: %v", err) } if err := Check(ctx, cfg, true); err != nil { t.Fatalf("check (unbeschaedigt) haette erfolgreich sein muessen: %v", err) } // Absichtliche Beschaedigung: ein Byte in einer Pack-Datei im // Repository kippen (dieselbe Fundstelle wie beim manuellen // Nachweis waehrend der Recherche zu diesem Ticket). packDir := filepath.Join(cfg.RepoDir, "data") corrupted := false if err := filepath.Walk(packDir, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() || corrupted { return err } data, err := os.ReadFile(path) if err != nil { return err } if len(data) < 20 { return nil } data[10] ^= 0xFF if err := os.WriteFile(path, data, 0o600); err != nil { return err } corrupted = true return nil }); err != nil { t.Fatalf("pack-datei beschaedigen: %v", err) } if !corrupted { t.Fatal("keine pack-datei zum beschaedigen gefunden - testaufbau fehlerhaft") } if err := Check(ctx, cfg, true); err == nil { t.Fatal("check haette die beschaedigte pack-datei erkennen muessen") } } // TestForget_KeepsOnlyRequestedSnapshotCount prueft die Rotation. func TestForget_KeepsOnlyRequestedSnapshotCount(t *testing.T) { cfg := setupTest(t) ctx := context.Background() sourceDir := t.TempDir() for i := 0; i < 3; i++ { writeFile(t, sourceDir, "f.txt", "version "+string(rune('a'+i))) if _, err := Backup(ctx, cfg, sourceDir); err != nil { t.Fatalf("sicherung %d: %v", i, err) } } before, err := SnapshotCount(ctx, cfg) if err != nil { t.Fatalf("snapshotcount (vorher): %v", err) } if before != 3 { t.Fatalf("erwartet 3 snapshots vor rotation, habe %d", before) } if err := Forget(ctx, cfg, 1); err != nil { t.Fatalf("forget: %v", err) } after, err := SnapshotCount(ctx, cfg) if err != nil { t.Fatalf("snapshotcount (nachher): %v", err) } if after != 1 { t.Fatalf("erwartet 1 snapshot nach rotation (keep-last 1), habe %d", after) } }