diff --git a/src/media/mime.test.ts b/src/media/mime.test.ts new file mode 100644 index 000000000..3eeb72a68 --- /dev/null +++ b/src/media/mime.test.ts @@ -0,0 +1,57 @@ +import JSZip from "jszip"; +import { describe, expect, it } from "vitest"; + +import { detectMime } from "./mime.js"; + +async function makeOoxmlZip(opts: { + mainMime: string; + partPath: string; +}): Promise { + const zip = new JSZip(); + zip.file( + "[Content_Types].xml", + ``, + ); + zip.file(opts.partPath.slice(1), ""); + return await zip.generateAsync({ type: "nodebuffer" }); +} + +describe("mime detection", () => { + it("detects docx from buffer", async () => { + const buf = await makeOoxmlZip({ + mainMime: + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + partPath: "/word/document.xml", + }); + const mime = await detectMime({ buffer: buf, filePath: "/tmp/file.bin" }); + expect(mime).toBe( + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + ); + }); + + it("detects pptx from buffer", async () => { + const buf = await makeOoxmlZip({ + mainMime: + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + partPath: "/ppt/presentation.xml", + }); + const mime = await detectMime({ buffer: buf, filePath: "/tmp/file.bin" }); + expect(mime).toBe( + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + ); + }); + + it("prefers extension mapping over generic zip", async () => { + const zip = new JSZip(); + zip.file("hello.txt", "hi"); + const buf = await zip.generateAsync({ type: "nodebuffer" }); + + const mime = await detectMime({ + buffer: buf, + filePath: "/tmp/file.xlsx", + }); + expect(mime).toBe( + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ); + }); +}); diff --git a/src/web/media.test.ts b/src/web/media.test.ts index 827c34041..45d200934 100644 --- a/src/web/media.test.ts +++ b/src/web/media.test.ts @@ -3,7 +3,7 @@ import os from "node:os"; import path from "node:path"; import sharp from "sharp"; -import { afterEach, describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { loadWebMedia } from "./media.js"; @@ -54,4 +54,25 @@ describe("web media loading", () => { expect(result.kind).toBe("image"); expect(result.contentType).toBe("image/jpeg"); }); + + it("adds extension to URL fileName when missing", async () => { + const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValueOnce({ + ok: true, + body: true, + arrayBuffer: async () => Buffer.from("%PDF-1.4").buffer, + headers: { get: () => "application/pdf" }, + status: 200, + } as Response); + + const result = await loadWebMedia( + "https://example.com/download", + 1024 * 1024, + ); + + expect(result.kind).toBe("document"); + expect(result.contentType).toBe("application/pdf"); + expect(result.fileName).toBe("download.pdf"); + + fetchMock.mockRestore(); + }); });