import { describe, it, expect } from "vitest"; import { classifyMediaKind, resolveDeliveryKind, isUnsupportedImageMime, isSupportedVideoMime, isSupportedAudioMime, sniffUnsupportedImage, sniffUnsupportedVideo, WA_LIMITS, WA_MAX_BYTES, } from "./whatsapp-media.js"; function isobmffHeader(brand: string): Uint8Array { const buf = new Uint8Array(12); buf[0] = 0; buf[1] = 0; buf[2] = 0; buf[3] = 0x18; buf[4] = 0x66; // 'f' buf[5] = 0x74; // 't' buf[6] = 0x79; // 'y' buf[7] = 0x70; // 'p' for (let i = 0; i < 4; i++) { buf[8 + i] = brand.charCodeAt(i) || 0x20; } return buf; } describe("@cmbot/shared whatsapp-media — cross-package contract", () => { it("size limits are stable (image 5MB / video 16MB / audio 16MB / document 100MB)", () => { expect(WA_LIMITS.image).toBe(5 * 1024 * 1024); expect(WA_LIMITS.video).toBe(16 * 1024 * 1024); expect(WA_LIMITS.audio).toBe(16 * 1024 * 1024); expect(WA_LIMITS.document).toBe(100 * 1024 * 1024); expect(WA_MAX_BYTES).toBe(WA_LIMITS.document); }); it("classifyMediaKind buckets by top-level mime category", () => { expect(classifyMediaKind("image/png")).toBe("image"); expect(classifyMediaKind("video/mp4")).toBe("video"); expect(classifyMediaKind("audio/mpeg")).toBe("audio"); expect(classifyMediaKind("application/pdf")).toBe("document"); }); it("resolveDeliveryKind keeps supported native types in place", () => { expect(resolveDeliveryKind("image/jpeg")).toBe("image"); expect(resolveDeliveryKind("video/mp4")).toBe("video"); expect(resolveDeliveryKind("audio/mpeg")).toBe("audio"); }); it("resolveDeliveryKind demotes HEIC/AVIF to document (mime-based)", () => { expect(resolveDeliveryKind("image/heic")).toBe("document"); expect(resolveDeliveryKind("image/avif")).toBe("document"); }); it("resolveDeliveryKind demotes HEIC by bytes when mime lies (image/jpeg + heic ftyp)", () => { // The exact case iOS Safari produces. expect(resolveDeliveryKind("image/jpeg", isobmffHeader("heic"))).toBe("document"); }); it("resolveDeliveryKind demotes QuickTime/WebM/MKV to document", () => { expect(resolveDeliveryKind("video/quicktime")).toBe("document"); expect(resolveDeliveryKind("video/webm")).toBe("document"); expect(resolveDeliveryKind("video/x-matroska")).toBe("document"); }); it("resolveDeliveryKind demotes .mov by bytes (qt brand under video/mp4 mime)", () => { expect(resolveDeliveryKind("video/mp4", isobmffHeader("qt "))).toBe("document"); }); it("resolveDeliveryKind keeps genuine MP4/3GP bytes in the video path", () => { expect(resolveDeliveryKind("video/mp4", isobmffHeader("mp42"))).toBe("video"); expect(resolveDeliveryKind("video/3gpp", isobmffHeader("3gp4"))).toBe("video"); }); it("resolveDeliveryKind demotes unsupported audio formats", () => { expect(resolveDeliveryKind("audio/x-flac")).toBe("document"); expect(resolveDeliveryKind("audio/x-aiff")).toBe("document"); }); it("isUnsupportedImageMime / isSupportedVideoMime / isSupportedAudioMime fundamentals", () => { expect(isUnsupportedImageMime("image/heic")).toBe(true); expect(isUnsupportedImageMime("image/jpeg")).toBe(false); expect(isSupportedVideoMime("video/mp4")).toBe(true); expect(isSupportedVideoMime("video/quicktime")).toBe(false); expect(isSupportedAudioMime("audio/mpeg")).toBe(true); expect(isSupportedAudioMime("audio/x-flac")).toBe(false); }); it("byte sniffs only fire on ISOBMFF data with the matching brand", () => { expect(sniffUnsupportedImage(isobmffHeader("heic"))).toBe(true); expect(sniffUnsupportedImage(isobmffHeader("mp42"))).toBe(false); expect(sniffUnsupportedVideo(isobmffHeader("qt "))).toBe(true); expect(sniffUnsupportedVideo(isobmffHeader("mp42"))).toBe(false); // No ftyp marker → not ISOBMFF → both return false. const jpeg = new Uint8Array([0xff, 0xd8, 0xff, 0xe0, 0, 0x10, 0x4a, 0x46, 0x49, 0x46, 0, 1]); expect(sniffUnsupportedImage(jpeg)).toBe(false); expect(sniffUnsupportedVideo(jpeg)).toBe(false); }); });