797 lines
24 KiB
Rust
797 lines
24 KiB
Rust
/// Comprehensive edge case tests for SessionUpdate variants
|
|
use dirigent_protocol::types::{
|
|
ContentBlock, Meta, SessionUpdate, ToolCall, ToolCallContent, ToolCallStatus,
|
|
};
|
|
use serde_json::json;
|
|
|
|
// ===== UserMessageChunk Tests =====
|
|
|
|
/// Test UserMessageChunk minimal (no _meta)
|
|
#[test]
|
|
fn test_user_message_chunk_minimal() {
|
|
let update = SessionUpdate::UserMessageChunk {
|
|
message_id: "msg_001".to_string(),
|
|
content: ContentBlock::Text {
|
|
text: "Hello".to_string(),
|
|
},
|
|
_meta: None,
|
|
};
|
|
|
|
let json = serde_json::to_string(&update).unwrap();
|
|
assert!(json.contains(r#""type":"user_message_chunk"#));
|
|
assert!(json.contains(r#""message_id":"msg_001"#));
|
|
assert!(!json.contains(r#""_meta""#));
|
|
|
|
let deserialized: SessionUpdate = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(update, deserialized);
|
|
}
|
|
|
|
/// Test UserMessageChunk with empty message_id
|
|
#[test]
|
|
fn test_user_message_chunk_empty_message_id() {
|
|
let update = SessionUpdate::UserMessageChunk {
|
|
message_id: String::new(),
|
|
content: ContentBlock::Text {
|
|
text: "Test".to_string(),
|
|
},
|
|
_meta: None,
|
|
};
|
|
|
|
let json = serde_json::to_string(&update).unwrap();
|
|
assert!(json.contains(r#""message_id":"""#));
|
|
|
|
let deserialized: SessionUpdate = serde_json::from_str(&json).unwrap();
|
|
match deserialized {
|
|
SessionUpdate::UserMessageChunk { message_id, .. } => {
|
|
assert_eq!(message_id, "");
|
|
}
|
|
_ => panic!("Expected UserMessageChunk"),
|
|
}
|
|
}
|
|
|
|
/// Test UserMessageChunk with ResourceLink content
|
|
#[test]
|
|
fn test_user_message_chunk_with_resource_link() {
|
|
let update = SessionUpdate::UserMessageChunk {
|
|
message_id: "msg_002".to_string(),
|
|
content: ContentBlock::ResourceLink {
|
|
uri: "file:///path/to/file.txt".to_string(),
|
|
name: Some("file.txt".to_string()),
|
|
mime_type: Some("text/plain".to_string()),
|
|
},
|
|
_meta: None,
|
|
};
|
|
|
|
let json = serde_json::to_string(&update).unwrap();
|
|
assert!(json.contains(r#""type":"user_message_chunk"#));
|
|
assert!(json.contains(r#""type":"resource_link"#)); // nested type
|
|
assert!(json.contains(r#""uri":"file:///path/to/file.txt"#));
|
|
|
|
let deserialized: SessionUpdate = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(update, deserialized);
|
|
}
|
|
|
|
/// Test UserMessageChunk with _meta
|
|
#[test]
|
|
fn test_user_message_chunk_with_meta() {
|
|
let update = SessionUpdate::UserMessageChunk {
|
|
message_id: "msg_003".to_string(),
|
|
content: ContentBlock::Text {
|
|
text: "Test".to_string(),
|
|
},
|
|
_meta: Some(Meta::default()),
|
|
};
|
|
|
|
let json = serde_json::to_string(&update).unwrap();
|
|
assert!(json.contains(r#""_meta":{}"#));
|
|
|
|
let deserialized: SessionUpdate = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(update, deserialized);
|
|
}
|
|
|
|
// ===== AgentMessageChunk Tests =====
|
|
|
|
/// Test AgentMessageChunk minimal
|
|
#[test]
|
|
fn test_agent_message_chunk_minimal() {
|
|
let update = SessionUpdate::AgentMessageChunk {
|
|
message_id: "msg_agent_001".to_string(),
|
|
content: ContentBlock::Text {
|
|
text: "Agent response".to_string(),
|
|
},
|
|
_meta: None,
|
|
};
|
|
|
|
let json = serde_json::to_string(&update).unwrap();
|
|
assert!(json.contains(r#""type":"agent_message_chunk"#));
|
|
assert!(json.contains(r#""message_id":"msg_agent_001"#));
|
|
assert!(!json.contains(r#""_meta""#));
|
|
|
|
let deserialized: SessionUpdate = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(update, deserialized);
|
|
}
|
|
|
|
/// Test AgentMessageChunk with empty message_id
|
|
#[test]
|
|
fn test_agent_message_chunk_empty_message_id() {
|
|
let update = SessionUpdate::AgentMessageChunk {
|
|
message_id: String::new(),
|
|
content: ContentBlock::Text {
|
|
text: "Response".to_string(),
|
|
},
|
|
_meta: None,
|
|
};
|
|
|
|
let json = serde_json::to_string(&update).unwrap();
|
|
let deserialized: SessionUpdate = serde_json::from_str(&json).unwrap();
|
|
|
|
match deserialized {
|
|
SessionUpdate::AgentMessageChunk { message_id, .. } => {
|
|
assert_eq!(message_id, "");
|
|
}
|
|
_ => panic!("Expected AgentMessageChunk"),
|
|
}
|
|
}
|
|
|
|
/// Test AgentMessageChunk with complex meta
|
|
#[test]
|
|
fn test_agent_message_chunk_with_complex_meta() {
|
|
let mut extra = std::collections::HashMap::new();
|
|
extra.insert("timestamp".to_string(), json!("2025-11-10T12:00:00Z"));
|
|
extra.insert("duration_ms".to_string(), json!(123));
|
|
|
|
let update = SessionUpdate::AgentMessageChunk {
|
|
message_id: "msg_agent_002".to_string(),
|
|
content: ContentBlock::Text {
|
|
text: "Response".to_string(),
|
|
},
|
|
_meta: Some(Meta {
|
|
provider: None,
|
|
extra,
|
|
}),
|
|
};
|
|
|
|
let json = serde_json::to_string(&update).unwrap();
|
|
assert!(json.contains(r#""_meta""#));
|
|
assert!(json.contains(r#""timestamp""#));
|
|
assert!(json.contains(r#""duration_ms""#));
|
|
|
|
let deserialized: SessionUpdate = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(update, deserialized);
|
|
}
|
|
|
|
// ===== AgentThoughtChunk Tests =====
|
|
|
|
/// Test AgentThoughtChunk minimal
|
|
#[test]
|
|
fn test_agent_thought_chunk_minimal() {
|
|
let update = SessionUpdate::AgentThoughtChunk {
|
|
message_id: "msg_thought_001".to_string(),
|
|
content: ContentBlock::Text {
|
|
text: "Thinking...".to_string(),
|
|
},
|
|
_meta: None,
|
|
};
|
|
|
|
let json = serde_json::to_string(&update).unwrap();
|
|
assert!(json.contains(r#""type":"agent_thought_chunk"#));
|
|
assert!(json.contains(r#""message_id":"msg_thought_001"#));
|
|
assert!(!json.contains(r#""_meta""#));
|
|
|
|
let deserialized: SessionUpdate = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(update, deserialized);
|
|
}
|
|
|
|
/// Test AgentThoughtChunk with empty text
|
|
#[test]
|
|
fn test_agent_thought_chunk_empty_text() {
|
|
let update = SessionUpdate::AgentThoughtChunk {
|
|
message_id: "msg_thought_002".to_string(),
|
|
content: ContentBlock::Text {
|
|
text: String::new(),
|
|
},
|
|
_meta: None,
|
|
};
|
|
|
|
let json = serde_json::to_string(&update).unwrap();
|
|
assert!(json.contains(r#""text":"""#));
|
|
|
|
let deserialized: SessionUpdate = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(update, deserialized);
|
|
}
|
|
|
|
/// Test AgentThoughtChunk with very long text
|
|
#[test]
|
|
fn test_agent_thought_chunk_long_text() {
|
|
let long_text = "Analyzing the problem...\n".repeat(1000);
|
|
let update = SessionUpdate::AgentThoughtChunk {
|
|
message_id: "msg_thought_003".to_string(),
|
|
content: ContentBlock::Text {
|
|
text: long_text.clone(),
|
|
},
|
|
_meta: None,
|
|
};
|
|
|
|
let json = serde_json::to_string(&update).unwrap();
|
|
let deserialized: SessionUpdate = serde_json::from_str(&json).unwrap();
|
|
|
|
match deserialized {
|
|
SessionUpdate::AgentThoughtChunk { content, .. } => {
|
|
if let ContentBlock::Text { text } = content {
|
|
assert_eq!(text.len(), long_text.len());
|
|
} else {
|
|
panic!("Expected Text content");
|
|
}
|
|
}
|
|
_ => panic!("Expected AgentThoughtChunk"),
|
|
}
|
|
}
|
|
|
|
// ===== ToolCall Tests =====
|
|
|
|
/// Test ToolCall variant minimal
|
|
#[test]
|
|
fn test_tool_call_variant_minimal() {
|
|
let tool_call = ToolCall {
|
|
id: "call_001".to_string(),
|
|
tool_name: "bash".to_string(),
|
|
status: ToolCallStatus::Pending,
|
|
content: vec![],
|
|
raw_input: None,
|
|
raw_output: None,
|
|
title: None,
|
|
error: None,
|
|
metadata: None,
|
|
origin: None,
|
|
};
|
|
|
|
let update = SessionUpdate::ToolCall {
|
|
message_id: "msg_tool_001".to_string(),
|
|
tool_call,
|
|
_meta: None,
|
|
};
|
|
|
|
let json = serde_json::to_string(&update).unwrap();
|
|
assert!(json.contains(r#""type":"tool_call"#));
|
|
assert!(json.contains(r#""message_id":"msg_tool_001"#));
|
|
assert!(json.contains(r#""tool_call""#));
|
|
assert!(!json.contains(r#""_meta""#));
|
|
|
|
let deserialized: SessionUpdate = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(update, deserialized);
|
|
}
|
|
|
|
/// Test ToolCall variant with complex nested ToolCall
|
|
#[test]
|
|
fn test_tool_call_variant_complex() {
|
|
let tool_call = ToolCall {
|
|
id: "call_002".to_string(),
|
|
tool_name: "read_file".to_string(),
|
|
status: ToolCallStatus::Completed,
|
|
content: vec![
|
|
ToolCallContent::from_content_block(ContentBlock::Text {
|
|
text: "Line 1".to_string(),
|
|
}),
|
|
ToolCallContent::from_content_block(ContentBlock::Text {
|
|
text: "Line 2".to_string(),
|
|
}),
|
|
],
|
|
raw_input: Some(json!({"path": "/tmp/test.txt"})),
|
|
raw_output: Some(json!({"bytes": 1024})),
|
|
title: Some("Read file".to_string()),
|
|
error: None,
|
|
metadata: Some(json!({"duration_ms": 42})),
|
|
origin: None,
|
|
};
|
|
|
|
let update = SessionUpdate::ToolCall {
|
|
message_id: "msg_tool_002".to_string(),
|
|
tool_call,
|
|
_meta: Some(Meta::default()),
|
|
};
|
|
|
|
let json = serde_json::to_string(&update).unwrap();
|
|
assert!(json.contains(r#""type":"tool_call"#));
|
|
assert!(json.contains(r#""tool_call""#));
|
|
assert!(json.contains(r#""raw_input""#));
|
|
assert!(json.contains(r#""raw_output""#));
|
|
assert!(json.contains(r#""_meta""#));
|
|
|
|
let deserialized: SessionUpdate = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(update, deserialized);
|
|
}
|
|
|
|
/// Test ToolCall variant with Error status
|
|
#[test]
|
|
fn test_tool_call_variant_with_error() {
|
|
let tool_call = ToolCall {
|
|
id: "call_003".to_string(),
|
|
tool_name: "bash".to_string(),
|
|
status: ToolCallStatus::Error,
|
|
content: vec![],
|
|
raw_input: Some(json!({"command": "invalid"})),
|
|
raw_output: None,
|
|
title: Some("Failed command".to_string()),
|
|
error: Some("Command not found".to_string()),
|
|
metadata: None,
|
|
origin: None,
|
|
};
|
|
|
|
let update = SessionUpdate::ToolCall {
|
|
message_id: "msg_tool_003".to_string(),
|
|
tool_call,
|
|
_meta: None,
|
|
};
|
|
|
|
let json = serde_json::to_string(&update).unwrap();
|
|
assert!(json.contains(r#""status":"failed""#));
|
|
assert!(json.contains(r#""error":"Command not found""#));
|
|
|
|
let deserialized: SessionUpdate = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(update, deserialized);
|
|
}
|
|
|
|
// ===== ToolCallUpdate Tests =====
|
|
|
|
/// Test ToolCallUpdate variant minimal
|
|
#[test]
|
|
fn test_tool_call_update_minimal() {
|
|
let tool_call = ToolCall {
|
|
id: "call_004".to_string(),
|
|
tool_name: "bash".to_string(),
|
|
status: ToolCallStatus::Running,
|
|
content: vec![],
|
|
raw_input: None,
|
|
raw_output: None,
|
|
title: None,
|
|
error: None,
|
|
metadata: None,
|
|
origin: None,
|
|
};
|
|
|
|
let update = SessionUpdate::ToolCallUpdate {
|
|
message_id: "msg_update_001".to_string(),
|
|
tool_call_id: "call_004".to_string(),
|
|
tool_call,
|
|
_meta: None,
|
|
};
|
|
|
|
let json = serde_json::to_string(&update).unwrap();
|
|
assert!(json.contains(r#""type":"tool_call_update"#));
|
|
assert!(json.contains(r#""message_id":"msg_update_001"#));
|
|
assert!(json.contains(r#""tool_call_id":"call_004"#));
|
|
assert!(json.contains(r#""tool_call""#));
|
|
assert!(!json.contains(r#""_meta""#));
|
|
|
|
let deserialized: SessionUpdate = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(update, deserialized);
|
|
}
|
|
|
|
/// Test ToolCallUpdate with mismatched IDs
|
|
#[test]
|
|
fn test_tool_call_update_mismatched_ids() {
|
|
// This is technically allowed by the type system, though semantically odd
|
|
let tool_call = ToolCall {
|
|
id: "call_005".to_string(),
|
|
tool_name: "test".to_string(),
|
|
status: ToolCallStatus::Running,
|
|
content: vec![],
|
|
raw_input: None,
|
|
raw_output: None,
|
|
title: None,
|
|
error: None,
|
|
metadata: None,
|
|
origin: None,
|
|
};
|
|
|
|
let update = SessionUpdate::ToolCallUpdate {
|
|
message_id: "msg_update_002".to_string(),
|
|
tool_call_id: "call_DIFFERENT".to_string(), // Different from tool_call.id
|
|
tool_call,
|
|
_meta: None,
|
|
};
|
|
|
|
let json = serde_json::to_string(&update).unwrap();
|
|
assert!(json.contains(r#""tool_call_id":"call_DIFFERENT"#));
|
|
assert!(json.contains(r#""id":"call_005"#)); // nested id
|
|
|
|
let deserialized: SessionUpdate = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(update, deserialized);
|
|
}
|
|
|
|
/// Test ToolCallUpdate with completed status
|
|
#[test]
|
|
fn test_tool_call_update_completed() {
|
|
let tool_call = ToolCall {
|
|
id: "call_006".to_string(),
|
|
tool_name: "read".to_string(),
|
|
status: ToolCallStatus::Completed,
|
|
content: vec![ToolCallContent::from_content_block(ContentBlock::Text {
|
|
text: "File contents".to_string(),
|
|
})],
|
|
raw_input: Some(json!({"path": "/tmp/file"})),
|
|
raw_output: Some(json!({"success": true})),
|
|
title: Some("Read operation".to_string()),
|
|
error: None,
|
|
metadata: Some(json!({"lines": 42})),
|
|
origin: None,
|
|
};
|
|
|
|
let update = SessionUpdate::ToolCallUpdate {
|
|
message_id: "msg_update_003".to_string(),
|
|
tool_call_id: "call_006".to_string(),
|
|
tool_call,
|
|
_meta: Some(Meta::default()),
|
|
};
|
|
|
|
let json = serde_json::to_string(&update).unwrap();
|
|
assert!(json.contains(r#""status":"completed""#));
|
|
assert!(json.contains(r#""raw_output""#));
|
|
assert!(json.contains(r#""_meta""#));
|
|
|
|
let deserialized: SessionUpdate = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(update, deserialized);
|
|
}
|
|
|
|
// ===== Type Tag Tests =====
|
|
|
|
/// Test all variants have correct snake_case type tags
|
|
#[test]
|
|
fn test_all_type_tags_snake_case() {
|
|
let user_chunk = SessionUpdate::UserMessageChunk {
|
|
message_id: "m1".to_string(),
|
|
content: ContentBlock::Text {
|
|
text: "test".to_string(),
|
|
},
|
|
_meta: None,
|
|
};
|
|
let json = serde_json::to_string(&user_chunk).unwrap();
|
|
assert!(json.contains(r#""type":"user_message_chunk"#));
|
|
assert!(!json.contains(r#""type":"UserMessageChunk"#));
|
|
|
|
let agent_chunk = SessionUpdate::AgentMessageChunk {
|
|
message_id: "m2".to_string(),
|
|
content: ContentBlock::Text {
|
|
text: "test".to_string(),
|
|
},
|
|
_meta: None,
|
|
};
|
|
let json = serde_json::to_string(&agent_chunk).unwrap();
|
|
assert!(json.contains(r#""type":"agent_message_chunk"#));
|
|
|
|
let thought_chunk = SessionUpdate::AgentThoughtChunk {
|
|
message_id: "m3".to_string(),
|
|
content: ContentBlock::Text {
|
|
text: "test".to_string(),
|
|
},
|
|
_meta: None,
|
|
};
|
|
let json = serde_json::to_string(&thought_chunk).unwrap();
|
|
assert!(json.contains(r#""type":"agent_thought_chunk"#));
|
|
|
|
let tool_call = SessionUpdate::ToolCall {
|
|
message_id: "m4".to_string(),
|
|
tool_call: ToolCall {
|
|
id: "c1".to_string(),
|
|
tool_name: "test".to_string(),
|
|
status: ToolCallStatus::Pending,
|
|
content: vec![],
|
|
raw_input: None,
|
|
raw_output: None,
|
|
title: None,
|
|
error: None,
|
|
metadata: None,
|
|
origin: None,
|
|
},
|
|
_meta: None,
|
|
};
|
|
let json = serde_json::to_string(&tool_call).unwrap();
|
|
assert!(json.contains(r#""type":"tool_call"#));
|
|
assert!(!json.contains(r#""type":"ToolCall"#));
|
|
|
|
let tool_call_update = SessionUpdate::ToolCallUpdate {
|
|
message_id: "m5".to_string(),
|
|
tool_call_id: "c2".to_string(),
|
|
tool_call: ToolCall {
|
|
id: "c2".to_string(),
|
|
tool_name: "test".to_string(),
|
|
status: ToolCallStatus::Pending,
|
|
content: vec![],
|
|
raw_input: None,
|
|
raw_output: None,
|
|
title: None,
|
|
error: None,
|
|
metadata: None,
|
|
origin: None,
|
|
},
|
|
_meta: None,
|
|
};
|
|
let json = serde_json::to_string(&tool_call_update).unwrap();
|
|
assert!(json.contains(r#""type":"tool_call_update"#));
|
|
}
|
|
|
|
// ===== Deserialization Error Cases =====
|
|
|
|
/// Test missing type field
|
|
#[test]
|
|
fn test_missing_type_field() {
|
|
let json = r#"{
|
|
"message_id": "msg_001",
|
|
"content": {
|
|
"type": "text",
|
|
"text": "Hello"
|
|
}
|
|
}"#;
|
|
|
|
let result: Result<SessionUpdate, _> = serde_json::from_str(json);
|
|
assert!(result.is_err(), "Should fail without type field");
|
|
}
|
|
|
|
/// Test invalid type value
|
|
#[test]
|
|
fn test_invalid_type_value() {
|
|
let json = r#"{
|
|
"type": "invalid_message_chunk",
|
|
"message_id": "msg_001",
|
|
"content": {
|
|
"type": "text",
|
|
"text": "Hello"
|
|
}
|
|
}"#;
|
|
|
|
let result: Result<SessionUpdate, _> = serde_json::from_str(json);
|
|
assert!(result.is_err(), "Should fail with invalid type");
|
|
}
|
|
|
|
/// Test missing message_id
|
|
#[test]
|
|
fn test_missing_message_id() {
|
|
let json = r#"{
|
|
"type": "user_message_chunk",
|
|
"content": {
|
|
"type": "text",
|
|
"text": "Hello"
|
|
}
|
|
}"#;
|
|
|
|
let result: Result<SessionUpdate, _> = serde_json::from_str(json);
|
|
assert!(result.is_err(), "Should fail without message_id");
|
|
}
|
|
|
|
/// Test missing content field
|
|
#[test]
|
|
fn test_missing_content_field() {
|
|
let json = r#"{
|
|
"type": "user_message_chunk",
|
|
"message_id": "msg_001"
|
|
}"#;
|
|
|
|
let result: Result<SessionUpdate, _> = serde_json::from_str(json);
|
|
assert!(result.is_err(), "Should fail without content");
|
|
}
|
|
|
|
/// Test missing tool_call field
|
|
#[test]
|
|
fn test_missing_tool_call_field() {
|
|
let json = r#"{
|
|
"type": "tool_call",
|
|
"message_id": "msg_001"
|
|
}"#;
|
|
|
|
let result: Result<SessionUpdate, _> = serde_json::from_str(json);
|
|
assert!(result.is_err(), "Should fail without tool_call");
|
|
}
|
|
|
|
/// Test missing tool_call_id in ToolCallUpdate
|
|
#[test]
|
|
fn test_missing_tool_call_id() {
|
|
let json = r#"{
|
|
"type": "tool_call_update",
|
|
"message_id": "msg_001",
|
|
"tool_call": {
|
|
"id": "call_001",
|
|
"tool_name": "test",
|
|
"status": "running"
|
|
}
|
|
}"#;
|
|
|
|
let result: Result<SessionUpdate, _> = serde_json::from_str(json);
|
|
assert!(result.is_err(), "Should fail without tool_call_id");
|
|
}
|
|
|
|
/// Test null values for required fields
|
|
#[test]
|
|
fn test_null_required_values() {
|
|
let json = r#"{
|
|
"type": "user_message_chunk",
|
|
"message_id": null,
|
|
"content": {
|
|
"type": "text",
|
|
"text": "Hello"
|
|
}
|
|
}"#;
|
|
|
|
let result: Result<SessionUpdate, _> = serde_json::from_str(json);
|
|
assert!(result.is_err(), "Should fail with null message_id");
|
|
}
|
|
|
|
/// Test null _meta (should deserialize as None)
|
|
#[test]
|
|
fn test_null_meta() {
|
|
let json = r#"{
|
|
"type": "user_message_chunk",
|
|
"message_id": "msg_001",
|
|
"content": {
|
|
"type": "text",
|
|
"text": "Hello"
|
|
},
|
|
"_meta": null
|
|
}"#;
|
|
|
|
let update: SessionUpdate = serde_json::from_str(json).unwrap();
|
|
match update {
|
|
SessionUpdate::UserMessageChunk { _meta, .. } => {
|
|
assert!(_meta.is_none());
|
|
}
|
|
_ => panic!("Expected UserMessageChunk"),
|
|
}
|
|
}
|
|
|
|
// ===== Roundtrip Tests =====
|
|
|
|
/// Test roundtrip for all variants
|
|
#[test]
|
|
fn test_all_variants_roundtrip() {
|
|
let variants = vec![
|
|
SessionUpdate::UserMessageChunk {
|
|
message_id: "msg_1".to_string(),
|
|
content: ContentBlock::Text {
|
|
text: "User message".to_string(),
|
|
},
|
|
_meta: None,
|
|
},
|
|
SessionUpdate::AgentMessageChunk {
|
|
message_id: "msg_2".to_string(),
|
|
content: ContentBlock::Text {
|
|
text: "Agent response".to_string(),
|
|
},
|
|
_meta: Some(Meta::default()),
|
|
},
|
|
SessionUpdate::AgentThoughtChunk {
|
|
message_id: "msg_3".to_string(),
|
|
content: ContentBlock::Text {
|
|
text: "Thinking...".to_string(),
|
|
},
|
|
_meta: None,
|
|
},
|
|
SessionUpdate::ToolCall {
|
|
message_id: "msg_4".to_string(),
|
|
tool_call: ToolCall {
|
|
id: "call_1".to_string(),
|
|
tool_name: "bash".to_string(),
|
|
status: ToolCallStatus::Pending,
|
|
content: vec![],
|
|
raw_input: Some(json!({"cmd": "ls"})),
|
|
raw_output: None,
|
|
title: Some("List files".to_string()),
|
|
error: None,
|
|
metadata: None,
|
|
origin: None,
|
|
},
|
|
_meta: None,
|
|
},
|
|
SessionUpdate::ToolCallUpdate {
|
|
message_id: "msg_5".to_string(),
|
|
tool_call_id: "call_1".to_string(),
|
|
tool_call: ToolCall {
|
|
id: "call_1".to_string(),
|
|
tool_name: "bash".to_string(),
|
|
status: ToolCallStatus::Completed,
|
|
content: vec![ToolCallContent::from_content_block(ContentBlock::Text {
|
|
text: "file1.txt\nfile2.txt".to_string(),
|
|
})],
|
|
raw_input: Some(json!({"cmd": "ls"})),
|
|
raw_output: Some(json!({"exit_code": 0})),
|
|
title: Some("List files".to_string()),
|
|
error: None,
|
|
metadata: Some(json!({"duration_ms": 100})),
|
|
origin: None,
|
|
},
|
|
_meta: Some(Meta::default()),
|
|
},
|
|
];
|
|
|
|
for variant in variants {
|
|
let json = serde_json::to_string(&variant).unwrap();
|
|
let deserialized: SessionUpdate = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(variant, deserialized);
|
|
}
|
|
}
|
|
|
|
// ===== Edge Cases: Complex Content =====
|
|
|
|
/// Test UserMessageChunk with complex nested content
|
|
#[test]
|
|
fn test_complex_nested_content() {
|
|
let update = SessionUpdate::UserMessageChunk {
|
|
message_id: "msg_complex".to_string(),
|
|
content: ContentBlock::ResourceLink {
|
|
uri: "data:text/plain;base64,SGVsbG8gV29ybGQh".to_string(),
|
|
name: Some("embedded.txt".to_string()),
|
|
mime_type: Some("text/plain; charset=utf-8".to_string()),
|
|
},
|
|
_meta: None,
|
|
};
|
|
|
|
let json = serde_json::to_string(&update).unwrap();
|
|
let deserialized: SessionUpdate = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(update, deserialized);
|
|
}
|
|
|
|
// ===== Clone and Debug =====
|
|
|
|
/// Test SessionUpdate clone
|
|
#[test]
|
|
fn test_session_update_clone() {
|
|
let original = SessionUpdate::UserMessageChunk {
|
|
message_id: "msg_clone".to_string(),
|
|
content: ContentBlock::Text {
|
|
text: "Test".to_string(),
|
|
},
|
|
_meta: None,
|
|
};
|
|
|
|
let cloned = original.clone();
|
|
assert_eq!(original, cloned);
|
|
}
|
|
|
|
/// Test SessionUpdate debug formatting
|
|
#[test]
|
|
fn test_session_update_debug() {
|
|
let update = SessionUpdate::UserMessageChunk {
|
|
message_id: "msg_debug".to_string(),
|
|
content: ContentBlock::Text {
|
|
text: "Debug test".to_string(),
|
|
},
|
|
_meta: None,
|
|
};
|
|
|
|
let debug_str = format!("{:?}", update);
|
|
assert!(debug_str.contains("UserMessageChunk"));
|
|
assert!(debug_str.contains("msg_debug"));
|
|
}
|
|
|
|
// ===== Edge Cases: Empty Collections =====
|
|
|
|
/// Test ToolCall with empty content array persists correctly
|
|
#[test]
|
|
fn test_tool_call_empty_content_persists() {
|
|
let update = SessionUpdate::ToolCall {
|
|
message_id: "msg_empty".to_string(),
|
|
tool_call: ToolCall {
|
|
id: "call_empty".to_string(),
|
|
tool_name: "test".to_string(),
|
|
status: ToolCallStatus::Pending,
|
|
content: vec![], // Explicitly empty
|
|
raw_input: None,
|
|
raw_output: None,
|
|
title: None,
|
|
error: None,
|
|
metadata: None,
|
|
origin: None,
|
|
},
|
|
_meta: None,
|
|
};
|
|
|
|
let json = serde_json::to_string(&update).unwrap();
|
|
assert!(json.contains(r#""content":[]"#));
|
|
|
|
let deserialized: SessionUpdate = serde_json::from_str(&json).unwrap();
|
|
match deserialized {
|
|
SessionUpdate::ToolCall { tool_call, .. } => {
|
|
assert_eq!(tool_call.content.len(), 0);
|
|
}
|
|
_ => panic!("Expected ToolCall"),
|
|
}
|
|
}
|