claudish/tests/image-handling.test.ts

122 lines
4.1 KiB
TypeScript

import { describe, it, expect, mock } from "bun:test";
import { createProxyServer } from "../src/proxy-server";
describe("Image Handling", () => {
const PORT = 4000;
// Mock data - 1x1 transparent PNG
const base64Image = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=";
const mediaType = "image/png";
it("should transform Anthropic image format to OpenAI image_url format", async () => {
// Save original fetch
const originalFetch = global.fetch;
// Create a mock for fetch to intercept OpenRouter calls
const mockFetch = mock((url, options) => {
// Intercept OpenRouter API calls
if (url === "https://openrouter.ai/api/v1/chat/completions") {
return Promise.resolve(new Response(JSON.stringify({
id: "test-id",
choices: [{
message: {
role: "assistant", // OpenRouter/OpenAI returns role: assistant in message, not delta for non-streaming
content: "I see a 1x1 pixel image."
},
finish_reason: "stop"
}],
usage: { prompt_tokens: 10, completion_tokens: 5 }
}), {
status: 200,
headers: { "Content-Type": "application/json" }
}));
}
// Pass through other calls (like the request to the proxy itself)
// Note: We need to use valid URL check or just catch-all
return originalFetch(url, options);
});
// Replace global fetch
global.fetch = mockFetch;
let proxy;
try {
// Start proxy
proxy = await createProxyServer(PORT, "fake-key", "test-model");
const serverUrl = `http://127.0.0.1:${PORT}`;
// Send request with image to proxy
// We use originalFetch here to actually hit the Hono server running on localhost
// (The Hono server will then call global.fetch, which is our mock)
const response = await originalFetch(`${serverUrl}/v1/messages`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "claude-3-sonnet-20240229",
messages: [
{
role: "user",
content: [
{
type: "image",
source: {
type: "base64",
media_type: mediaType,
data: base64Image
}
},
{
type: "text",
text: "What is this?"
}
]
}
],
max_tokens: 100
})
});
const result = await response.json();
// 1. Verify proxy response is successful
expect(response.status).toBe(200);
expect(result.content[0].text).toBe("I see a 1x1 pixel image.");
// 2. Verify OpenRouter request structure
// Find the call to OpenRouter in the mock's history
const openRouterCall = mockFetch.mock.calls.find(call =>
call[0] === "https://openrouter.ai/api/v1/chat/completions"
);
expect(openRouterCall).toBeDefined();
// Parse the body sent to OpenRouter
const requestBody = JSON.parse(openRouterCall[1].body);
// Check message content
const userMessage = requestBody.messages.find(m => m.role === "user");
expect(userMessage).toBeDefined();
// Find the image part (should be converted to image_url)
const imagePart = userMessage.content.find(c => c.type === "image_url");
expect(imagePart).toBeDefined();
// Verify structure matches OpenAI format: { type: "image_url", image_url: { url: "data..." } }
expect(imagePart.image_url).toBeDefined();
expect(imagePart.image_url.url).toBe(`data:${mediaType};base64,${base64Image}`);
// Verify text is preserved
const textPart = userMessage.content.find(c => c.type === "text");
expect(textPart).toBeDefined();
expect(textPart.text).toBe("What is this?");
} finally {
// Cleanup
if (proxy) await proxy.shutdown();
global.fetch = originalFetch;
}
});
});