T-HUB/apps/archive-bot/src/manifest.test.ts

84 lines
2.9 KiB
TypeScript

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> = {}): 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);
});
});