import sharp from "sharp"; import { describe, expect, it } from "vitest"; import { sanitizeContentBlocksImages, sanitizeImageBlocks } from "./tool-images.js"; describe("base64 validation", () => { it("rejects invalid base64 characters and replaces with error text", async () => { const blocks = [ { type: "image" as const, data: "not-valid-base64!!!@#$%", mimeType: "image/png", }, ]; const out = await sanitizeContentBlocksImages(blocks, "test"); expect(out.length).toBe(1); expect(out[0].type).toBe("text"); if (out[0].type === "text") { expect(out[0].text).toContain("omitted image payload"); expect(out[0].text).toContain("invalid"); } }); it("strips data URL prefix and processes valid base64", async () => { // Create a small valid image const jpeg = await sharp({ create: { width: 10, height: 10, channels: 3, background: { r: 255, g: 0, b: 0 }, }, }) .jpeg() .toBuffer(); const base64 = jpeg.toString("base64"); const dataUrl = `data:image/jpeg;base64,${base64}`; const blocks = [ { type: "image" as const, data: dataUrl, mimeType: "image/jpeg", }, ]; const out = await sanitizeContentBlocksImages(blocks, "test"); expect(out.length).toBe(1); expect(out[0].type).toBe("image"); }); it("rejects base64 with invalid padding", async () => { const blocks = [ { type: "image" as const, data: "SGVsbG8===", // too many padding chars mimeType: "image/png", }, ]; const out = await sanitizeContentBlocksImages(blocks, "test"); expect(out.length).toBe(1); expect(out[0].type).toBe("text"); if (out[0].type === "text") { expect(out[0].text).toContain("omitted image payload"); } }); it("rejects base64 with invalid length", async () => { const blocks = [ { type: "image" as const, data: "AAAAA", // length 5 without padding is invalid (remainder 1) mimeType: "image/png", }, ]; const out = await sanitizeContentBlocksImages(blocks, "test"); expect(out.length).toBe(1); expect(out[0].type).toBe("text"); if (out[0].type === "text") { expect(out[0].text).toContain("omitted image payload"); } }); it("handles empty base64 data gracefully", async () => { const blocks = [ { type: "image" as const, data: " ", mimeType: "image/png", }, ]; const out = await sanitizeContentBlocksImages(blocks, "test"); expect(out.length).toBe(1); expect(out[0].type).toBe("text"); if (out[0].type === "text") { expect(out[0].text).toContain("omitted empty image payload"); } }); }); describe("tool image sanitizing", () => { it("shrinks oversized images to <=5MB", async () => { const width = 2800; const height = 2800; const raw = Buffer.alloc(width * height * 3, 0xff); const bigPng = await sharp(raw, { raw: { width, height, channels: 3 }, }) .png({ compressionLevel: 0 }) .toBuffer(); expect(bigPng.byteLength).toBeGreaterThan(5 * 1024 * 1024); const blocks = [ { type: "image" as const, data: bigPng.toString("base64"), mimeType: "image/png", }, ]; const out = await sanitizeContentBlocksImages(blocks, "test"); const image = out.find((b) => b.type === "image"); if (!image || image.type !== "image") { throw new Error("expected image block"); } const size = Buffer.from(image.data, "base64").byteLength; expect(size).toBeLessThanOrEqual(5 * 1024 * 1024); expect(image.mimeType).toBe("image/jpeg"); }, 20_000); it("sanitizes image arrays and reports drops", async () => { const width = 2600; const height = 400; const raw = Buffer.alloc(width * height * 3, 0x7f); const png = await sharp(raw, { raw: { width, height, channels: 3 }, }) .png({ compressionLevel: 9 }) .toBuffer(); const images = [ { type: "image" as const, data: png.toString("base64"), mimeType: "image/png" }, ]; const { images: out, dropped } = await sanitizeImageBlocks(images, "test"); expect(dropped).toBe(0); expect(out.length).toBe(1); const meta = await sharp(Buffer.from(out[0].data, "base64")).metadata(); expect(meta.width).toBeLessThanOrEqual(2000); expect(meta.height).toBeLessThanOrEqual(2000); }, 20_000); it("shrinks images that exceed max dimension even if size is small", async () => { const width = 2600; const height = 400; const raw = Buffer.alloc(width * height * 3, 0x7f); const png = await sharp(raw, { raw: { width, height, channels: 3 }, }) .png({ compressionLevel: 9 }) .toBuffer(); const blocks = [ { type: "image" as const, data: png.toString("base64"), mimeType: "image/png", }, ]; const out = await sanitizeContentBlocksImages(blocks, "test"); const image = out.find((b) => b.type === "image"); if (!image || image.type !== "image") { throw new Error("expected image block"); } const meta = await sharp(Buffer.from(image.data, "base64")).metadata(); expect(meta.width).toBeLessThanOrEqual(2000); expect(meta.height).toBeLessThanOrEqual(2000); expect(image.mimeType).toBe("image/jpeg"); }, 20_000); it("corrects mismatched jpeg mimeType", async () => { const jpeg = await sharp({ create: { width: 10, height: 10, channels: 3, background: { r: 255, g: 0, b: 0 }, }, }) .jpeg() .toBuffer(); const blocks = [ { type: "image" as const, data: jpeg.toString("base64"), mimeType: "image/png", }, ]; const out = await sanitizeContentBlocksImages(blocks, "test"); const image = out.find((b) => b.type === "image"); if (!image || image.type !== "image") { throw new Error("expected image block"); } expect(image.mimeType).toBe("image/jpeg"); }); });