import { describe, expect, it } from "vitest"; import type { RoomMapping } from "./config.js"; import { buildManifest, MAX_ATTACHMENT_BYTES } from "./exporter.js"; import type { IndexedFile } from "./archive-index.js"; import { payloadPath } from "./archive-index.js"; const mapping: RoomMapping = { roomId: "!room:example.org", workspaceId: "11111111-1111-4111-8111-111111111111", groupId: "22222222-2222-4222-8222-222222222222", }; function row(overrides: Partial = {}): IndexedFile { return { id: "0d0e0f10-1111-4222-8222-333333333333", eventId: "$ev1", mxcUri: "mxc://example.org/abc", originalName: "cat photo.png", safeName: "cat_photo.png", mime: "image/png", mimeClass: "image", byteSize: 1234, sha256: "a".repeat(64), sender: "@alice:example.org", createdAt: "2026-01-02T03:04:05.000Z", ocisResourceId: "42", available: true, ...overrides, }; } describe("bundle manifest shape", () => { it("carries every index row with the documented field names", () => { const rows = [row(), row({ eventId: "$ev2", available: false, byteSize: 10, safeName: "gone.bin" })]; const manifest = buildManifest(mapping, rows, new Date("2026-02-03T04:05:06.000Z"), ["age1testrecipient"]); expect(manifest).toMatchObject({ format: "chat-media-archive-bundle", version: 1, roomId: mapping.roomId, workspaceId: mapping.workspaceId, groupId: mapping.groupId, exportedAt: "2026-02-03T04:05:06.000Z", objectKey: null, recipients: ["age1testrecipient"], }); expect(manifest.files).toHaveLength(2); expect(manifest.files[0]).toEqual({ eventId: "$ev1", mxcUri: "mxc://example.org/abc", originalName: "cat photo.png", safeName: "cat_photo.png", mime: "image/png", mimeClass: "image", byteSize: 1234, sha256: "a".repeat(64), sender: "@alice:example.org", createdAt: "2026-01-02T03:04:05.000Z", ocisResourceId: "42", available: true, }); }); it("counts only available files in fileCount/totalBytes, but records every row", () => { const manifest = buildManifest(mapping, [ row({ byteSize: 100 }), row({ eventId: "$ev2", byteSize: 40, available: false }), ], new Date(0), []); expect(manifest.fileCount).toBe(1); expect(manifest.totalBytes).toBe(100); expect(manifest.files).toHaveLength(2); expect(manifest.files.map((f) => f.available)).toEqual([true, false]); }); it("derives deterministic, collision-free tar payload paths", () => { const one = row({ eventId: "$a", safeName: "x.png" }); expect(payloadPath(one)).toBe("files/$a/x.png"); // Different events never collide even with identical safe names. expect(payloadPath(row({ eventId: "$b", safeName: "x.png" }))).not.toBe(payloadPath(one)); }); it("documents the per-attachment budget constant", () => { expect(MAX_ATTACHMENT_BYTES).toBe(20 * 1024 * 1024); }); });