use super::*;
use crate::adapters::base::ProviderAdapter;
#[test]
fn test_provider_name() {
let adapter = AnthropicAdapter::new();
assert_eq!(adapter.provider_name(), "anthropic");
}
#[test]
fn test_api_url_default() {
let adapter = AnthropicAdapter::new();
assert_eq!(adapter.api_url(), DEFAULT_API_URL);
}
#[test]
fn test_api_url_custom() {
let adapter = AnthropicAdapter::with_url("https://custom.api/v1/messages");
assert_eq!(adapter.api_url(), "https://custom.api/v1/messages");
}
#[test]
fn test_extra_headers() {
let adapter = AnthropicAdapter::new();
let headers = adapter.extra_headers();
assert!(
headers
.iter()
.any(|(k, v)| k == "anthropic-version" && v == ANTHROPIC_VERSION)
);
assert!(headers.iter().any(|(k, v)| k == "anthropic-beta"
&& v.contains("prompt-caching-2024-07-31")
&& v.contains("interleaved-thinking-2025-05-14")));
}
#[test]
fn test_extra_headers_no_caching() {
let adapter = AnthropicAdapter::new().with_caching(false);
let headers = adapter.extra_headers();
assert!(headers.iter().any(|(k, _)| k == "anthropic-version"));
assert!(
headers
.iter()
.any(|(k, v)| k == "anthropic-beta" && v.contains("interleaved-thinking-2025-05-14"))
);
}
#[test]
fn test_extract_system() {
let mut payload = json!({
"messages": [
{"role": "system", "content": "You are helpful."},
{"role": "user", "content": "Hello"}
]
});
AnthropicAdapter::extract_system(&mut payload);
assert_eq!(payload["system"], "You are helpful.");
let messages = payload["messages"].as_array().unwrap();
assert_eq!(messages.len(), 1);
assert_eq!(messages[0]["role"], "user");
}
#[test]
fn test_convert_image_blocks() {
let mut payload = json!({
"messages": [{
"role": "user",
"content": [{
"type": "image_url",
"image_url": {"url": "data:image/png;base64,abc123"}
}]
}]
});
AnthropicAdapter::convert_image_blocks(&mut payload);
let block = &payload["messages"][0]["content"][0];
assert_eq!(block["type"], "image");
assert_eq!(block["source"]["type"], "base64");
assert_eq!(block["source"]["media_type"], "image/png");
assert_eq!(block["source"]["data"], "abc123");
}
#[test]
fn test_add_cache_control_string_content() {
let mut payload = json!({
"messages": [
{"role": "user", "content": "Hello"}
]
});
AnthropicAdapter::add_cache_control(&mut payload);
let content = &payload["messages"][0]["content"];
assert!(content.is_array());
assert_eq!(content[0]["cache_control"]["type"], "ephemeral");
}
#[test]
fn test_convert_request_removes_unsupported() {
let adapter = AnthropicAdapter::new().with_caching(false);
let payload = json!({
"messages": [{"role": "user", "content": "Hi"}],
"n": 1,
"frequency_penalty": 0.5,
"presence_penalty": 0.5,
"logprobs": true
});
let result = adapter.convert_request(payload);
assert!(result.get("n").is_none());
assert!(result.get("frequency_penalty").is_none());
assert!(result.get("presence_penalty").is_none());
assert!(result.get("logprobs").is_none());
}
#[test]
fn test_response_to_chat_completions() {
let response = json!({
"id": "msg_123",
"type": "message",
"model": "claude-3-opus-20240229",
"content": [{"type": "text", "text": "Hello!"}],
"stop_reason": "end_turn",
"usage": {"input_tokens": 10, "output_tokens": 5}
});
let result = AnthropicAdapter::response_to_chat_completions(response);
assert_eq!(result["object"], "chat.completion");
assert_eq!(result["choices"][0]["message"]["content"], "Hello!");
assert_eq!(result["choices"][0]["finish_reason"], "stop");
assert_eq!(result["usage"]["prompt_tokens"], 10);
assert_eq!(result["usage"]["completion_tokens"], 5);
assert_eq!(result["usage"]["total_tokens"], 15);
}
#[test]
fn test_response_tool_use_finish_reason() {
let response = json!({
"id": "msg_456",
"model": "claude-3",
"content": [{"type": "text", "text": "Using tool"}],
"stop_reason": "tool_use",
"usage": {"input_tokens": 0, "output_tokens": 0}
});
let result = AnthropicAdapter::response_to_chat_completions(response);
assert_eq!(result["choices"][0]["finish_reason"], "tool_calls");
}
#[test]
fn test_response_extracts_thinking_blocks() {
let response = json!({
"id": "msg_789",
"model": "claude-sonnet-4-20250514",
"content": [
{"type": "thinking", "thinking": "Let me think about this..."},
{"type": "thinking", "thinking": "Step 2 of thinking"},
{"type": "text", "text": "The answer is 42."}
],
"stop_reason": "end_turn",
"usage": {"input_tokens": 10, "output_tokens": 50}
});
let result = AnthropicAdapter::response_to_chat_completions(response);
assert_eq!(
result["choices"][0]["message"]["content"],
"The answer is 42."
);
assert_eq!(
result["choices"][0]["message"]["reasoning_content"],
"Let me think about this...\n\nStep 2 of thinking"
);
}
#[test]
fn test_response_no_thinking_blocks() {
let response = json!({
"id": "msg_100",
"model": "claude-3-opus",
"content": [{"type": "text", "text": "Hello!"}],
"stop_reason": "end_turn",
"usage": {"input_tokens": 5, "output_tokens": 3}
});
let result = AnthropicAdapter::response_to_chat_completions(response);
assert!(
result["choices"][0]["message"]
.get("reasoning_content")
.is_none()
);
}
#[test]
fn test_supports_thinking() {
assert!(supports_thinking("claude-3-7-sonnet-20250219"));
assert!(supports_thinking("claude-3.7-sonnet"));
assert!(supports_thinking("claude-4-opus-20250514"));
assert!(supports_thinking("claude-opus-4-20250514"));
assert!(supports_thinking("claude-sonnet-4-20250514"));
assert!(!supports_thinking("claude-3-opus-20240229"));
assert!(!supports_thinking("claude-3-5-sonnet"));
assert!(!supports_thinking("gpt-4o"));
}
#[test]
fn test_convert_request_with_thinking() {
let adapter = AnthropicAdapter::new().with_caching(false);
let payload = json!({
"model": "claude-sonnet-4-20250514",
"messages": [{"role": "user", "content": "Think about this"}],
"_reasoning_effort": "medium"
});
let result = adapter.convert_request(payload);
assert_eq!(result["thinking"]["type"], "enabled");
assert_eq!(result["thinking"]["budget_tokens"], 16000);
assert_eq!(result["temperature"], 1);
assert!(result.get("_reasoning_effort").is_none());
}
#[test]
fn test_convert_request_thinking_unsupported_model() {
let adapter = AnthropicAdapter::new().with_caching(false);
let payload = json!({
"model": "claude-3-opus-20240229",
"messages": [{"role": "user", "content": "Hello"}],
"_reasoning_effort": "high"
});
let result = adapter.convert_request(payload);
assert!(result.get("thinking").is_none());
}
#[test]
fn test_convert_tool_messages_echoes_thinking() {
let mut payload = json!({
"messages": [
{
"role": "assistant",
"content": "Let me read that file.",
"reasoning_content": "I should read the file first.",
"tool_calls": [{
"id": "tc-1",
"function": {"name": "read_file", "arguments": "{\"path\": \"test.rs\"}"}
}]
},
{
"role": "tool",
"tool_call_id": "tc-1",
"content": "file contents"
}
]
});
AnthropicAdapter::convert_tool_messages(&mut payload);
let messages = payload["messages"].as_array().unwrap();
let assistant_content = messages[0]["content"].as_array().unwrap();
assert_eq!(assistant_content[0]["type"], "thinking");
assert_eq!(
assistant_content[0]["thinking"],
"I should read the file first."
);
assert_eq!(assistant_content[1]["type"], "text");
assert_eq!(assistant_content[2]["type"], "tool_use");
}
#[test]
fn test_convert_request_thinking_ensures_min_max_tokens() {
let adapter = AnthropicAdapter::new().with_caching(false);
let payload = json!({
"model": "claude-sonnet-4-20250514",
"messages": [{"role": "user", "content": "Think"}],
"_reasoning_effort": "high",
"max_tokens": 1024
});
let result = adapter.convert_request(payload);
assert!(result["max_tokens"].as_u64().unwrap() >= 33023);
}
#[test]
fn test_supports_adaptive_thinking() {
assert!(supports_adaptive_thinking("claude-opus-4-6-20260301"));
assert!(supports_adaptive_thinking("claude-opus-4.6-20260301"));
assert!(supports_adaptive_thinking("claude-sonnet-4-6-20260301"));
assert!(supports_adaptive_thinking("claude-sonnet-4.6-20260301"));
assert!(!supports_adaptive_thinking("claude-sonnet-4-20250514"));
assert!(!supports_adaptive_thinking("claude-opus-4-20250514"));
assert!(!supports_adaptive_thinking("claude-3-7-sonnet-20250219"));
assert!(!supports_adaptive_thinking("gpt-4o"));
}
#[test]
fn test_convert_request_adaptive_thinking_high() {
let adapter = AnthropicAdapter::new().with_caching(false);
let payload = json!({
"model": "claude-opus-4-6-20260301",
"messages": [{"role": "user", "content": "Think deeply"}],
"_reasoning_effort": "high"
});
let result = adapter.convert_request(payload);
assert_eq!(result["thinking"]["type"], "adaptive");
assert!(result["thinking"].get("budget_tokens").is_none());
assert_eq!(result["temperature"], 1);
}
#[test]
fn test_convert_request_adaptive_thinking_medium() {
let adapter = AnthropicAdapter::new().with_caching(false);
let payload = json!({
"model": "claude-sonnet-4.6-20260301",
"messages": [{"role": "user", "content": "Think"}],
"_reasoning_effort": "medium"
});
let result = adapter.convert_request(payload);
assert_eq!(result["thinking"]["type"], "adaptive");
assert_eq!(result["thinking"]["budget_tokens"], 16000);
}
#[test]
fn test_convert_request_adaptive_thinking_low() {
let adapter = AnthropicAdapter::new().with_caching(false);
let payload = json!({
"model": "claude-opus-4.6-20260301",
"messages": [{"role": "user", "content": "Quick"}],
"_reasoning_effort": "low"
});
let result = adapter.convert_request(payload);
assert_eq!(result["thinking"]["type"], "adaptive");
assert_eq!(result["thinking"]["budget_tokens"], 8000);
}
#[test]
fn test_thinking_blocks_signature_preserved_in_response() {
let response = json!({
"id": "msg_sig",
"model": "claude-opus-4-6-20260301",
"content": [
{"type": "thinking", "thinking": "Deep thought", "signature": "sig_abc123"},
{"type": "text", "text": "Answer."}
],
"stop_reason": "end_turn",
"usage": {"input_tokens": 10, "output_tokens": 20}
});
let result = AnthropicAdapter::response_to_chat_completions(response);
let msg = &result["choices"][0]["message"];
assert_eq!(msg["reasoning_content"], "Deep thought");
let blocks = msg["_thinking_blocks"].as_array().unwrap();
assert_eq!(blocks.len(), 1);
assert_eq!(blocks[0]["signature"], "sig_abc123");
assert_eq!(blocks[0]["thinking"], "Deep thought");
}
#[test]
fn test_thinking_blocks_signature_roundtrip() {
let mut payload = json!({
"messages": [
{
"role": "assistant",
"content": "Using a tool.",
"reasoning_content": "Let me think.",
"_thinking_blocks": [
{"type": "thinking", "thinking": "Let me think.", "signature": "sig_xyz"}
],
"tool_calls": [{
"id": "tc-1",
"function": {"name": "bash", "arguments": "{\"command\": \"ls\"}"}
}]
},
{
"role": "tool",
"tool_call_id": "tc-1",
"content": "file1.rs"
}
]
});
AnthropicAdapter::convert_tool_messages(&mut payload);
let messages = payload["messages"].as_array().unwrap();
let assistant_content = messages[0]["content"].as_array().unwrap();
assert_eq!(assistant_content[0]["type"], "thinking");
assert_eq!(assistant_content[0]["signature"], "sig_xyz");
assert_eq!(assistant_content[0]["thinking"], "Let me think.");
assert_eq!(assistant_content[1]["type"], "text");
assert_eq!(assistant_content[2]["type"], "tool_use");
}