import { describe, expect, test } from "bun:test"; import { buildMultimodalMessages } from "@/lib/ai-event-messages"; // --------------------------------------------------------------------------- // buildMultimodalMessages – behavioral tests // // Public behavior under test: given a system prompt, an optional text prompt, // and an array of base64 image strings, returns a well-formed messages array // for the OpenRouter chat API. // // We test WHAT the function produces (message structure), not HOW it does it. // --------------------------------------------------------------------------- const SYSTEM_PROMPT = "You are an assistant…"; const FAKE_PNG = "data:image/png;base64,abc123"; const FAKE_JPEG = "data:image/jpeg;base64,def456"; describe("buildMultimodalMessages – message structure", () => { test("first message is always the system prompt", () => { const messages = buildMultimodalMessages(SYSTEM_PROMPT, "hello", [FAKE_PNG]); expect(messages[0].role).toBe("system"); expect((messages[0].content as string)).toBe(SYSTEM_PROMPT); }); test("second message is the user message", () => { const messages = buildMultimodalMessages(SYSTEM_PROMPT, "hello", [FAKE_PNG]); expect(messages[1].role).toBe("user"); }); test("user message content array starts with the text part", () => { const messages = buildMultimodalMessages(SYSTEM_PROMPT, "any prompt", [FAKE_PNG]); const userContent = messages[1].content as Array<{ type: string }>; expect(userContent[0].type).toBe("text"); }); test("user message content array includes one image_url part per image", () => { const messages = buildMultimodalMessages(SYSTEM_PROMPT, "prompt", [FAKE_PNG, FAKE_JPEG]); const userContent = messages[1].content as Array<{ type: string }>; const imageparts = userContent.filter((p) => p.type === "image_url"); expect(imageparts).toHaveLength(2); }); test("each image_url part carries the correct base64 URL", () => { const messages = buildMultimodalMessages(SYSTEM_PROMPT, undefined, [FAKE_PNG, FAKE_JPEG]); const userContent = messages[1].content as Array<{ type: string; imageUrl?: { url: string }; }>; const imageParts = userContent.filter((p) => p.type === "image_url"); expect(imageParts[0].imageUrl?.url).toBe(FAKE_PNG); expect(imageParts[1].imageUrl?.url).toBe(FAKE_JPEG); }); test("text part uses a fallback when prompt is undefined", () => { const messages = buildMultimodalMessages(SYSTEM_PROMPT, undefined, [FAKE_PNG]); const userContent = messages[1].content as Array<{ type: string; text?: string; }>; const textPart = userContent.find((p) => p.type === "text"); expect(typeof textPart?.text).toBe("string"); expect(textPart?.text?.length ?? 0).toBeGreaterThan(0); }); test("text part carries the provided prompt text when given", () => { const prompt = "Extract all events from these flyers"; const messages = buildMultimodalMessages(SYSTEM_PROMPT, prompt, [FAKE_PNG]); const userContent = messages[1].content as Array<{ type: string; text?: string; }>; const textPart = userContent.find((p) => p.type === "text"); expect(textPart?.text).toBe(prompt); }); test("produces exactly 2 messages (system + user)", () => { const messages = buildMultimodalMessages(SYSTEM_PROMPT, "x", [FAKE_PNG]); expect(messages).toHaveLength(2); }); test("single image produces content array of length 2 (1 text + 1 image)", () => { const messages = buildMultimodalMessages(SYSTEM_PROMPT, "x", [FAKE_PNG]); const userContent = messages[1].content as unknown[]; expect(userContent).toHaveLength(2); }); test("three images produce content array of length 4 (1 text + 3 images)", () => { const messages = buildMultimodalMessages(SYSTEM_PROMPT, "x", [ FAKE_PNG, FAKE_JPEG, FAKE_PNG, ]); const userContent = messages[1].content as unknown[]; expect(userContent).toHaveLength(4); }); });