use crate::tool_parser;
use super::super::helpers::convert_stop_reason;
use super::super::types::{
AnthropicTool, AnthropicToolChoice, ContentBlock, MessagesRequest, SystemContent,
};
#[test]
fn stop_reason_maps_the_whole_wire_vocabulary() {
assert_eq!(convert_stop_reason("stop"), "end_turn");
assert_eq!(convert_stop_reason("tool_calls"), "tool_use");
assert_eq!(convert_stop_reason("length"), "max_tokens");
assert_eq!(convert_stop_reason("content_filter"), "refusal");
assert_eq!(
convert_stop_reason(crate::ir::FINISH_REASON_TIMEOUT),
"max_tokens"
);
assert_eq!(convert_stop_reason("something_new"), "end_turn");
}
#[test]
fn stop_reason_covers_every_finish_reason_wire_string() {
use crate::ir::FinishReason;
for (reason, expected) in [
(FinishReason::Stop, "end_turn"),
(FinishReason::Length, "max_tokens"),
(FinishReason::ToolCalls, "tool_use"),
(FinishReason::ContentFilter, "refusal"),
(
FinishReason::Other(crate::ir::FINISH_REASON_TIMEOUT.to_string()),
"max_tokens",
),
] {
assert_eq!(
convert_stop_reason(reason.as_wire()),
expected,
"finish reason {reason:?}"
);
}
}
fn choice(kind: &str, name: Option<&str>) -> AnthropicToolChoice {
AnthropicToolChoice {
choice_type: kind.to_string(),
name: name.map(str::to_string),
}
}
#[test]
fn tool_choice_modes_map_to_openai_vocabulary() {
let cases = [("any", "required"), ("auto", "auto"), ("none", "none")];
for (anthropic, openai) in cases {
match tool_parser::ToolChoice::from(&choice(anthropic, None)) {
tool_parser::ToolChoice::Mode(m) => assert_eq!(m, openai, "{anthropic}"),
other => panic!("{anthropic}: expected Mode, got {other:?}"),
}
}
}
#[test]
fn tool_choice_specific_tool_carries_the_name() {
match tool_parser::ToolChoice::from(&choice("tool", Some("get_weather"))) {
tool_parser::ToolChoice::Specific { function } => {
assert_eq!(function.name, "get_weather");
}
other => panic!("expected Specific, got {other:?}"),
}
}
#[test]
fn tool_choice_tool_without_a_name_degrades_to_auto() {
match tool_parser::ToolChoice::from(&choice("tool", None)) {
tool_parser::ToolChoice::Mode(m) => assert_eq!(m, "auto"),
other => panic!("expected Mode(auto), got {other:?}"),
}
match tool_parser::ToolChoice::from(&choice("wat", None)) {
tool_parser::ToolChoice::Mode(m) => assert_eq!(m, "auto"),
other => panic!("expected Mode(auto), got {other:?}"),
}
}
#[test]
fn tool_definition_carries_description_and_schema() {
let schema = serde_json::json!({
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
});
let tool = AnthropicTool {
name: "get_weather".to_string(),
description: Some("Get weather".to_string()),
input_schema: schema.clone(),
};
let def = tool_parser::ToolDefinition::from(&tool);
assert_eq!(def.tool_type, "function");
assert_eq!(def.function.name, "get_weather");
assert_eq!(def.function.description.as_deref(), Some("Get weather"));
assert_eq!(def.function.parameters.as_ref(), Some(&schema));
}
#[test]
fn tool_definition_without_a_description_keeps_none() {
let tool = AnthropicTool {
name: "x".to_string(),
description: None,
input_schema: serde_json::json!({"type": "object"}),
};
let def = tool_parser::ToolDefinition::from(&tool);
assert!(def.function.description.is_none());
}
#[test]
fn request_deserializes_string_system_and_defaults() {
let req: MessagesRequest = serde_json::from_value(serde_json::json!({
"model": "qwen3-80b",
"max_tokens": 1024,
"system": "You are helpful.",
"messages": [{"role": "user", "content": "Hello!"}],
}))
.expect("MessagesRequest");
assert_eq!(req.model, "qwen3-80b");
assert_eq!(req.max_tokens, 1024);
assert!(matches!(req.system, Some(SystemContent::Text(ref s)) if s == "You are helpful."));
assert_eq!(req.messages.len(), 1);
assert_eq!(req.messages[0].role, "user");
assert!(req.temperature.is_none());
assert!(req.top_k.is_none());
assert!(req.top_p.is_none());
assert!(req.tools.is_none());
assert!(req.tool_choice.is_none());
assert!(req.thinking.is_none());
assert!(req.stop_sequences.is_empty());
assert!(!req.stream);
}
#[test]
fn thinking_config_deserializes_type_and_budget() {
let req: MessagesRequest = serde_json::from_value(serde_json::json!({
"model": "qwen3-80b",
"max_tokens": 1024,
"thinking": {"type": "enabled", "budget_tokens": 4096},
"messages": [{"role": "user", "content": "Think hard."}],
}))
.expect("MessagesRequest");
let t = req.thinking.expect("thinking config");
assert_eq!(t.thinking_type, "enabled");
assert_eq!(t.budget_tokens, Some(4096));
}
#[test]
fn tool_result_is_error_defaults_to_none_when_absent() {
let with_err: ContentBlock = serde_json::from_str(
r#"{"type":"tool_result","tool_use_id":"x","content":"oops","is_error":true}"#,
)
.expect("tool_result with is_error");
match with_err {
ContentBlock::ToolResult { is_error, .. } => assert_eq!(is_error, Some(true)),
other => panic!("wrong variant: {other:?}"),
}
let no_field: ContentBlock =
serde_json::from_str(r#"{"type":"tool_result","tool_use_id":"x","content":"ok"}"#)
.expect("tool_result without is_error");
match no_field {
ContentBlock::ToolResult { is_error, .. } => assert_eq!(is_error, None),
other => panic!("wrong variant: {other:?}"),
}
}
#[test]
fn unrecognised_block_types_deserialize_to_unknown_instead_of_failing() {
let block: ContentBlock = serde_json::from_str(r#"{"type":"redacted_thinking","data":"AAAA"}"#)
.expect("unknown block type must parse");
assert!(matches!(block, ContentBlock::Unknown));
}