test(rust): encode anthropic response serialization shape as rstest cases

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yujong Lee 2026-09-24 08:17:12 -07:00
parent e135a199ad
commit ae00e0f37c
3 changed files with 61 additions and 2 deletions

View file

@ -3414,6 +3414,7 @@ dependencies = [
name = "litellm-types"
version = "0.1.0"
dependencies = [
"rstest",
"serde",
"serde_json",
]

View file

@ -8,3 +8,6 @@ repository.workspace = true
[dependencies]
serde.workspace = true
serde_json.workspace = true
[dev-dependencies]
rstest.workspace = true

View file

@ -9,8 +9,6 @@ pub struct AnthropicMessagesResponse {
pub role: String,
pub model: String,
pub content: Vec<Value>,
// Anthropic always includes stop_reason / stop_sequence, null until the turn
// ends; serialize them even when None so callers see the same shape as Python.
pub stop_reason: Option<String>,
pub stop_sequence: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
@ -20,3 +18,60 @@ pub struct AnthropicMessagesResponse {
#[serde(flatten)]
pub extra: Map<String, Value>,
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
use serde_json::json;
fn response(
stop_reason: Option<&str>,
stop_sequence: Option<&str>,
usage: Option<Value>,
container: Option<Value>,
) -> AnthropicMessagesResponse {
AnthropicMessagesResponse {
id: "msg_1".to_string(),
message_type: "message".to_string(),
role: "assistant".to_string(),
model: "claude".to_string(),
content: vec![],
stop_reason: stop_reason.map(str::to_string),
stop_sequence: stop_sequence.map(str::to_string),
usage,
container,
extra: Map::new(),
}
}
#[rstest]
#[case::turn_in_progress(None, None, json!(null), json!(null))]
#[case::ended_on_end_turn(Some("end_turn"), None, json!("end_turn"), json!(null))]
#[case::ended_on_stop_sequence(Some("stop_sequence"), Some("###"), json!("stop_sequence"), json!("###"))]
fn stop_fields_are_always_serialized(
#[case] stop_reason: Option<&str>,
#[case] stop_sequence: Option<&str>,
#[case] expected_reason: Value,
#[case] expected_sequence: Value,
) {
let body: Value = serde_json::to_value(response(stop_reason, stop_sequence, None, None))
.expect("serializable");
assert_eq!(body.get("stop_reason"), Some(&expected_reason));
assert_eq!(body.get("stop_sequence"), Some(&expected_sequence));
}
#[rstest]
#[case::absent(None, None)]
#[case::present(Some(json!({"input_tokens": 1})), Some(json!({"id": "c_1"})))]
fn usage_and_container_are_omitted_only_when_none(
#[case] usage: Option<Value>,
#[case] container: Option<Value>,
) {
let body: Value =
serde_json::to_value(response(None, None, usage.clone(), container.clone()))
.expect("serializable");
assert_eq!(body.get("usage").cloned(), usage);
assert_eq!(body.get("container").cloned(), container);
}
}