package backup import ( "archive/zip" "bytes" "context" "crypto/sha256" "encoding/base64" "io" "os" "path/filepath" "testing" ) func readZipEntry(t *testing.T, data []byte, name string) []byte { t.Helper() zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) if err != nil { t.Fatalf("zip.NewReader: %v", err) } for _, f := range zr.File { if f.Name == name { rc, err := f.Open() if err != nil { t.Fatalf("open %s: %v", name, err) } defer rc.Close() b, err := io.ReadAll(rc) if err != nil { t.Fatalf("read %s: %v", name, err) } return b } } t.Fatalf("zip entry %q not found", name) return nil } func TestBuildArchiveRoundTrip(t *testing.T) { dir := t.TempDir() mediaDir := filepath.Join(dir, "media") if err := os.MkdirAll(filepath.Join(mediaDir, "sub"), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(mediaDir, "sub", "a.txt"), []byte("hello"), 0o644); err != nil { t.Fatal(err) } dump := []byte("fake pg_dump custom-format payload") var buf bytes.Buffer manifest, warnings, err := BuildArchive(context.Background(), &buf, BuildInput{ Source: Source{SiteTitle: "Test Site", Domain: "test.example.com"}, Dump: dump, Password: "correct horse", Sections: []Section{ {Prefix: "media/", Dir: mediaDir}, {Prefix: "brand/", Dir: filepath.Join(dir, "does-not-exist")}, // missing dir → warning, not error }, }) if err != nil { t.Fatalf("BuildArchive: %v", err) } if len(warnings) != 0 { t.Fatalf("missing section dir must be silently skipped (cms parity), got warnings: %v", warnings) } if manifest.Version != FormatVersion || manifest.Format != FormatName { t.Fatalf("manifest version/format = %d/%q", manifest.Version, manifest.Format) } if manifest.MediaCount != 1 || manifest.BrandCount != 0 { t.Fatalf("counts: media=%d brand=%d", manifest.MediaCount, manifest.BrandCount) } wantSum := sha256.Sum256(dump) if manifest.Checksum != base64.StdEncoding.EncodeToString(wantSum[:]) { t.Fatalf("checksum mismatch") } // Read back: manifest parses, dump decrypts to the original bytes. ra := bytes.NewReader(buf.Bytes()) got, err := ReadManifest(ra, int64(buf.Len())) if err != nil { t.Fatalf("ReadManifest: %v", err) } encDump := readZipEntry(t, buf.Bytes(), "database.dump") plain, err := DecryptWithPassword(encDump, "correct horse", got.Encryption) if err != nil { t.Fatalf("DecryptWithPassword: %v", err) } if !bytes.Equal(plain, dump) { t.Fatal("decrypted dump != original") } if _, err := DecryptWithPassword(encDump, "wrong password", got.Encryption); err == nil { t.Fatal("wrong password must fail") } if string(readZipEntry(t, buf.Bytes(), "media/sub/a.txt")) != "hello" { t.Fatal("media file missing or corrupted in archive") } }