use llmshim::vision;
use serde_json::{json, Value};
#[test]
fn anthropic_from_openai_chat_completions_url() {
let block = json!({"type": "image_url", "image_url": {"url": "https://example.com/cat.jpg"}});
let result = vision::to_anthropic(&block).unwrap();
assert_eq!(result["type"], "image");
assert_eq!(result["source"]["type"], "url");
assert_eq!(result["source"]["url"], "https://example.com/cat.jpg");
}
#[test]
fn anthropic_from_openai_chat_completions_base64() {
let block = json!({"type": "image_url", "image_url": {"url": "data:image/png;base64,abc123"}});
let result = vision::to_anthropic(&block).unwrap();
assert_eq!(result["type"], "image");
assert_eq!(result["source"]["type"], "base64");
assert_eq!(result["source"]["media_type"], "image/png");
assert_eq!(result["source"]["data"], "abc123");
}
#[test]
fn anthropic_from_responses_api_url() {
let block = json!({"type": "input_image", "image_url": "https://example.com/dog.png"});
let result = vision::to_anthropic(&block).unwrap();
assert_eq!(result["type"], "image");
assert_eq!(result["source"]["type"], "url");
assert_eq!(result["source"]["url"], "https://example.com/dog.png");
}
#[test]
fn anthropic_from_responses_api_base64() {
let block = json!({"type": "input_image", "image_url": "data:image/jpeg;base64,xyz789"});
let result = vision::to_anthropic(&block).unwrap();
assert_eq!(result["source"]["type"], "base64");
assert_eq!(result["source"]["media_type"], "image/jpeg");
assert_eq!(result["source"]["data"], "xyz789");
}
#[test]
fn anthropic_passthrough_native() {
let block = json!({"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": "native"}});
let result = vision::to_anthropic(&block).unwrap();
assert_eq!(result["source"]["data"], "native");
}
#[test]
fn anthropic_unknown_type_returns_none() {
let block = json!({"type": "audio", "data": "..."});
assert!(vision::to_anthropic(&block).is_none());
}
#[test]
fn gemini_from_openai_chat_completions_base64() {
let block = json!({"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,img123"}});
let result = vision::to_gemini(&block).unwrap();
assert_eq!(result["inline_data"]["mime_type"], "image/jpeg");
assert_eq!(result["inline_data"]["data"], "img123");
}
#[test]
fn gemini_from_openai_chat_completions_url_fallback() {
let block = json!({"type": "image_url", "image_url": {"url": "https://example.com/cat.jpg"}});
let result = vision::to_gemini(&block).unwrap();
assert!(result["text"]
.as_str()
.unwrap()
.contains("https://example.com/cat.jpg"));
}
#[test]
fn gemini_from_responses_api_base64() {
let block = json!({"type": "input_image", "image_url": "data:image/png;base64,gemdata"});
let result = vision::to_gemini(&block).unwrap();
assert_eq!(result["inline_data"]["mime_type"], "image/png");
assert_eq!(result["inline_data"]["data"], "gemdata");
}
#[test]
fn gemini_from_anthropic_base64() {
let block = json!({"type": "image", "source": {"type": "base64", "media_type": "image/webp", "data": "webpdata"}});
let result = vision::to_gemini(&block).unwrap();
assert_eq!(result["inline_data"]["mime_type"], "image/webp");
assert_eq!(result["inline_data"]["data"], "webpdata");
}
#[test]
fn gemini_from_anthropic_url_fallback() {
let block =
json!({"type": "image", "source": {"type": "url", "url": "https://example.com/img.png"}});
let result = vision::to_gemini(&block).unwrap();
assert!(result["text"]
.as_str()
.unwrap()
.contains("https://example.com/img.png"));
}
#[test]
fn openai_passthrough_input_image() {
let block = json!({"type": "input_image", "image_url": "https://example.com/img.jpg"});
let result = vision::to_openai(&block).unwrap();
assert_eq!(result["type"], "input_image");
assert_eq!(result["image_url"], "https://example.com/img.jpg");
}
#[test]
fn openai_from_chat_completions_format() {
let block = json!({"type": "image_url", "image_url": {"url": "https://example.com/img.jpg"}});
let result = vision::to_openai(&block).unwrap();
assert_eq!(result["type"], "input_image");
assert_eq!(result["image_url"], "https://example.com/img.jpg");
}
#[test]
fn openai_from_anthropic_base64() {
let block = json!({"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": "abc"}});
let result = vision::to_openai(&block).unwrap();
assert_eq!(result["type"], "input_image");
assert_eq!(result["image_url"], "data:image/png;base64,abc");
}
#[test]
fn openai_from_anthropic_url() {
let block =
json!({"type": "image", "source": {"type": "url", "url": "https://example.com/img.jpg"}});
let result = vision::to_openai(&block).unwrap();
assert_eq!(result["type"], "input_image");
assert_eq!(result["image_url"], "https://example.com/img.jpg");
}
#[test]
fn translate_blocks_string_passthrough() {
let content = json!("Just a string");
let result = vision::translate_content_blocks(&content, vision::to_anthropic);
assert_eq!(result, "Just a string");
}
#[test]
fn translate_blocks_null_passthrough() {
let content = Value::Null;
let result = vision::translate_content_blocks(&content, vision::to_anthropic);
assert!(result.is_null());
}
#[test]
fn translate_blocks_text_preserved() {
let content = json!([{"type": "text", "text": "Hello!"}]);
let result = vision::translate_content_blocks(&content, vision::to_anthropic);
assert_eq!(result[0]["type"], "text");
assert_eq!(result[0]["text"], "Hello!");
}
#[test]
fn translate_blocks_mixed_text_and_image() {
let content = json!([
{"type": "text", "text": "What's in this image?"},
{"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,abc123"}}
]);
let result = vision::translate_content_blocks(&content, vision::to_anthropic);
let blocks = result.as_array().unwrap();
assert_eq!(blocks.len(), 2);
assert_eq!(blocks[0]["type"], "text");
assert_eq!(blocks[1]["type"], "image");
assert_eq!(blocks[1]["source"]["type"], "base64");
}
#[test]
fn translate_blocks_unknown_type_passthrough() {
let content = json!([{"type": "custom_widget", "data": "something"}]);
let result = vision::translate_content_blocks(&content, vision::to_anthropic);
assert_eq!(result[0]["type"], "custom_widget");
}
#[test]
fn anthropic_transforms_image_in_message() {
use llmshim::provider::Provider;
use llmshim::providers::anthropic::Anthropic;
let p = Anthropic::new("key".into());
let req = json!({
"model": "claude-sonnet-4-6",
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "Describe this image"},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,testdata"}}
]
}],
"max_tokens": 100,
});
let result = p.transform_request("claude-sonnet-4-6", &req).unwrap();
let content = result.body["messages"][0]["content"].as_array().unwrap();
assert_eq!(content[0]["type"], "text");
assert_eq!(content[1]["type"], "image");
assert_eq!(content[1]["source"]["type"], "base64");
assert_eq!(content[1]["source"]["data"], "testdata");
}
#[test]
fn gemini_transforms_image_in_message() {
use llmshim::provider::Provider;
use llmshim::providers::gemini::Gemini;
let p = Gemini::new("key".into());
let req = json!({
"model": "gemini-3-flash-preview",
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "What is this?"},
{"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,imgdata"}}
]
}],
});
let result = p.transform_request("gemini-3-flash-preview", &req).unwrap();
let parts = result.body["contents"][0]["parts"].as_array().unwrap();
assert_eq!(parts[0]["text"], "What is this?");
assert_eq!(parts[1]["inline_data"]["mime_type"], "image/jpeg");
assert_eq!(parts[1]["inline_data"]["data"], "imgdata");
}
#[test]
fn openai_transforms_image_in_message() {
use llmshim::provider::Provider;
use llmshim::providers::openai::OpenAi;
let p = OpenAi::new("key".into());
let req = json!({
"model": "gpt-5.4",
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "Describe"},
{"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": "abc"}}
]
}],
});
let result = p.transform_request("gpt-5.4", &req).unwrap();
let input = result.body["input"].as_array().unwrap();
let content = input[0]["content"].as_array().unwrap();
assert_eq!(content[0]["type"], "input_text"); assert_eq!(content[1]["type"], "input_image");
assert_eq!(content[1]["image_url"], "data:image/png;base64,abc");
}
#[test]
fn anthropic_preserves_interleaved_text_image_order() {
use llmshim::provider::Provider;
use llmshim::providers::anthropic::Anthropic;
let p = Anthropic::new("key".into());
let req = json!({
"model": "claude-sonnet-4-6",
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "First look at this"},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,img1data"}},
{"type": "text", "text": "Now compare with this"},
{"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,img2data"}},
{"type": "text", "text": "Which is better?"}
]
}],
"max_tokens": 100,
});
let result = p.transform_request("claude-sonnet-4-6", &req).unwrap();
let content = result.body["messages"][0]["content"].as_array().unwrap();
assert_eq!(content.len(), 5);
assert_eq!(content[0]["type"], "text");
assert_eq!(content[0]["text"], "First look at this");
assert_eq!(content[1]["type"], "image");
assert_eq!(content[1]["source"]["data"], "img1data");
assert_eq!(content[2]["type"], "text");
assert_eq!(content[2]["text"], "Now compare with this");
assert_eq!(content[3]["type"], "image");
assert_eq!(content[3]["source"]["data"], "img2data");
assert_eq!(content[4]["type"], "text");
assert_eq!(content[4]["text"], "Which is better?");
}
#[test]
fn gemini_preserves_interleaved_text_image_order() {
use llmshim::provider::Provider;
use llmshim::providers::gemini::Gemini;
let p = Gemini::new("key".into());
let req = json!({
"model": "gemini-3-flash-preview",
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "Image A:"},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,aaa"}},
{"type": "text", "text": "Image B:"},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,bbb"}}
]
}],
});
let result = p.transform_request("gemini-3-flash-preview", &req).unwrap();
let parts = result.body["contents"][0]["parts"].as_array().unwrap();
assert_eq!(parts.len(), 4);
assert_eq!(parts[0]["text"], "Image A:");
assert!(parts[1].get("inline_data").is_some());
assert_eq!(parts[1]["inline_data"]["data"], "aaa");
assert_eq!(parts[2]["text"], "Image B:");
assert!(parts[3].get("inline_data").is_some());
assert_eq!(parts[3]["inline_data"]["data"], "bbb");
}
#[test]
fn openai_preserves_interleaved_text_image_order() {
use llmshim::provider::Provider;
use llmshim::providers::openai::OpenAi;
let p = OpenAi::new("key".into());
let req = json!({
"model": "gpt-5.4",
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "Compare:"},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,x1"}},
{"type": "text", "text": "vs"},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,x2"}}
]
}],
});
let result = p.transform_request("gpt-5.4", &req).unwrap();
let input = result.body["input"].as_array().unwrap();
let content = input[0]["content"].as_array().unwrap();
assert_eq!(content.len(), 4);
assert_eq!(content[0]["type"], "input_text");
assert_eq!(content[0]["text"], "Compare:");
assert_eq!(content[1]["type"], "input_image");
assert_eq!(content[2]["type"], "input_text");
assert_eq!(content[2]["text"], "vs");
assert_eq!(content[3]["type"], "input_image");
}