import { mkdtemp } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; import sharp from "sharp"; import { FileAttachmentStore } from "../src/index.js"; async function image(format: "jpeg" | "png" | "webp") { const pipeline = sharp({ create: { width: 4, height: 3, channels: 3, background: "#25f4ee" } }); return format === "jpeg" ? pipeline.jpeg().toBuffer() : format === "png" ? pipeline.png().toBuffer() : pipeline.webp().toBuffer(); } function upload(filename: string, mimetype: string, data: Buffer) { return { filename, mimetype, stream: (async function* () { yield data; })() }; } describe("FileAttachmentStore", () => { it.each([["jpeg", "photo.jpg", "image/jpeg"], ["png", "photo.png", "image/png"], ["webp", "photo.webp", "image/webp"]] as const)("normalizes valid %s", async (format, name, mime) => { const root = await mkdtemp(path.join(tmpdir(), "studio-images-")); const store = new FileAttachmentStore(root); const item = await store.add("s", upload(name, mime, await image(format))); expect(item).toMatchObject({ sessionId: "s", mediaType: mime, width: 4, height: 3, status: "ready" }); }); it("rejects spoofed MIME and SVG", async () => { const root = await mkdtemp(path.join(tmpdir(), "studio-images-")); const store = new FileAttachmentStore(root); await expect(store.add("s", upload("fake.jpg", "image/jpeg", Buffer.from("not jpeg")))).rejects.toMatchObject({ code: "ATTACHMENT_MAGIC_MISMATCH" }); await expect(store.add("s", upload("x.svg", "image/svg+xml", Buffer.from("")))).rejects.toMatchObject({ code: "ATTACHMENT_TYPE_UNSUPPORTED" }); }); it("rejects an oversized stream before decoding", async () => { const root = await mkdtemp(path.join(tmpdir(), "studio-images-")); const store = new FileAttachmentStore(root, 10); await expect(store.add("s", upload("x.jpg", "image/jpeg", Buffer.alloc(11)))).rejects.toMatchObject({ code: "ATTACHMENT_TOO_LARGE" }); }); it("isolates attachments across sessions", async () => { const root = await mkdtemp(path.join(tmpdir(), "studio-images-")); const store = new FileAttachmentStore(root); const item = await store.add("one", upload("x.png", "image/png", await image("png"))); expect(store.get("two", item.attachmentId)).toBeUndefined(); }); it("removes all session attachment state", async () => { const root = await mkdtemp(path.join(tmpdir(), "studio-images-")); const store = new FileAttachmentStore(root); const item = await store.add("one", upload("x.png", "image/png", await image("png"))); await store.removeAll("one"); expect(store.get("one", item.attachmentId)).toBeUndefined(); }); });