import { describe, it, expect, beforeEach, afterEach } from "vitest"; import fs from "fs/promises"; import path from "path"; import os from "os"; import { getMimeTypeForImage, readImageAsBase64, convertImagesToContentBlocks, formatImagePathsForPrompt, } from "../src/image-handler"; describe("image-handler.ts", () => { let tempDir: string; beforeEach(async () => { tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "image-handler-test-")); }); afterEach(async () => { try { await fs.rm(tempDir, { recursive: true, force: true }); } catch (error) { // Ignore cleanup errors } }); describe("getMimeTypeForImage", () => { it("should return correct MIME type for .jpg", () => { expect(getMimeTypeForImage("image.jpg")).toBe("image/jpeg"); expect(getMimeTypeForImage("/path/to/image.jpg")).toBe("image/jpeg"); }); it("should return correct MIME type for .jpeg", () => { expect(getMimeTypeForImage("image.jpeg")).toBe("image/jpeg"); }); it("should return correct MIME type for .png", () => { expect(getMimeTypeForImage("image.png")).toBe("image/png"); }); it("should return correct MIME type for .gif", () => { expect(getMimeTypeForImage("image.gif")).toBe("image/gif"); }); it("should return correct MIME type for .webp", () => { expect(getMimeTypeForImage("image.webp")).toBe("image/webp"); }); it("should be case-insensitive", () => { expect(getMimeTypeForImage("image.JPG")).toBe("image/jpeg"); expect(getMimeTypeForImage("image.PNG")).toBe("image/png"); expect(getMimeTypeForImage("image.GIF")).toBe("image/gif"); }); it("should default to image/png for unknown extensions", () => { expect(getMimeTypeForImage("file.xyz")).toBe("image/png"); expect(getMimeTypeForImage("file.txt")).toBe("image/png"); expect(getMimeTypeForImage("file")).toBe("image/png"); }); it("should handle filenames with multiple dots", () => { expect(getMimeTypeForImage("my.file.name.jpg")).toBe("image/jpeg"); }); }); describe("readImageAsBase64", () => { it("should read image and return base64 data", async () => { const imagePath = path.join(tempDir, "test.png"); const imageContent = Buffer.from("fake png data"); await fs.writeFile(imagePath, imageContent); const result = await readImageAsBase64(imagePath); expect(result.base64).toBe(imageContent.toString("base64")); expect(result.mimeType).toBe("image/png"); expect(result.filename).toBe("test.png"); expect(result.originalPath).toBe(imagePath); }); it("should handle different image formats", async () => { const formats = [ { ext: "jpg", mime: "image/jpeg" }, { ext: "png", mime: "image/png" }, { ext: "gif", mime: "image/gif" }, { ext: "webp", mime: "image/webp" }, ]; for (const format of formats) { const imagePath = path.join(tempDir, `image.${format.ext}`); await fs.writeFile(imagePath, Buffer.from("data")); const result = await readImageAsBase64(imagePath); expect(result.mimeType).toBe(format.mime); expect(result.filename).toBe(`image.${format.ext}`); } }); it("should throw error if file doesn't exist", async () => { const imagePath = path.join(tempDir, "nonexistent.png"); await expect(readImageAsBase64(imagePath)).rejects.toThrow(); }); it("should handle binary image data correctly", async () => { const imagePath = path.join(tempDir, "binary.png"); const binaryData = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a]); await fs.writeFile(imagePath, binaryData); const result = await readImageAsBase64(imagePath); expect(result.base64).toBe(binaryData.toString("base64")); }); }); describe("convertImagesToContentBlocks", () => { it("should convert single image to content block", async () => { const imagePath = path.join(tempDir, "test.png"); await fs.writeFile(imagePath, Buffer.from("image data")); const result = await convertImagesToContentBlocks([imagePath]); expect(result).toHaveLength(1); expect(result[0]).toMatchObject({ type: "image", source: { type: "base64", media_type: "image/png", }, }); expect(result[0].source.data).toBeTruthy(); }); it("should convert multiple images", async () => { const image1 = path.join(tempDir, "image1.jpg"); const image2 = path.join(tempDir, "image2.png"); await fs.writeFile(image1, Buffer.from("jpg data")); await fs.writeFile(image2, Buffer.from("png data")); const result = await convertImagesToContentBlocks([image1, image2]); expect(result).toHaveLength(2); expect(result[0].source.media_type).toBe("image/jpeg"); expect(result[1].source.media_type).toBe("image/png"); }); it("should resolve relative paths with workDir", async () => { const image = "test.png"; const imagePath = path.join(tempDir, image); await fs.writeFile(imagePath, Buffer.from("data")); const result = await convertImagesToContentBlocks([image], tempDir); expect(result).toHaveLength(1); expect(result[0].type).toBe("image"); }); it("should handle absolute paths without workDir", async () => { const imagePath = path.join(tempDir, "absolute.png"); await fs.writeFile(imagePath, Buffer.from("data")); const result = await convertImagesToContentBlocks([imagePath]); expect(result).toHaveLength(1); }); it("should skip images that fail to load", async () => { const validImage = path.join(tempDir, "valid.png"); const invalidImage = path.join(tempDir, "nonexistent.png"); await fs.writeFile(validImage, Buffer.from("data")); const result = await convertImagesToContentBlocks([ validImage, invalidImage, ]); expect(result).toHaveLength(1); expect(result[0].source.media_type).toBe("image/png"); }); it("should return empty array for empty input", async () => { const result = await convertImagesToContentBlocks([]); expect(result).toEqual([]); }); it("should preserve order of images", async () => { const images = ["img1.jpg", "img2.png", "img3.gif"]; for (const img of images) { await fs.writeFile(path.join(tempDir, img), Buffer.from("data")); } const result = await convertImagesToContentBlocks(images, tempDir); expect(result).toHaveLength(3); expect(result[0].source.media_type).toBe("image/jpeg"); expect(result[1].source.media_type).toBe("image/png"); expect(result[2].source.media_type).toBe("image/gif"); }); }); describe("formatImagePathsForPrompt", () => { it("should return empty string for empty array", () => { const result = formatImagePathsForPrompt([]); expect(result).toBe(""); }); it("should format single image path", () => { const result = formatImagePathsForPrompt(["/path/to/image.png"]); expect(result).toBe("\n\nAttached images:\n- /path/to/image.png\n"); }); it("should format multiple image paths", () => { const result = formatImagePathsForPrompt([ "/path/image1.png", "/path/image2.jpg", "/path/image3.gif", ]); expect(result).toBe( "\n\nAttached images:\n" + "- /path/image1.png\n" + "- /path/image2.jpg\n" + "- /path/image3.gif\n" ); }); it("should handle relative paths", () => { const result = formatImagePathsForPrompt([ "relative/path/image.png", "another/image.jpg", ]); expect(result).toContain("- relative/path/image.png"); expect(result).toContain("- another/image.jpg"); }); it("should start with newlines", () => { const result = formatImagePathsForPrompt(["/image.png"]); expect(result.startsWith("\n\n")).toBe(true); }); it("should include header text", () => { const result = formatImagePathsForPrompt(["/image.png"]); expect(result).toContain("Attached images:"); }); }); });