tool_result_integration.rs (5284B)
1 //! Integration test for tool result metadata display 2 //! 3 //! Tests that tool results are captured from the message stream 4 //! by correlating ToolUse and ToolResult content blocks. 5 6 use claude_agent_sdk_rs::{ContentBlock, ToolResultBlock, ToolResultContent, ToolUseBlock}; 7 use std::collections::HashMap; 8 9 /// Unit test that verifies ToolUse and ToolResult correlation logic 10 #[test] 11 fn test_tool_use_result_correlation() { 12 // Simulate the pending_tools tracking 13 let mut pending_tools: HashMap<String, (String, serde_json::Value)> = HashMap::new(); 14 let mut tool_results: Vec<(String, String, serde_json::Value)> = Vec::new(); 15 16 // Simulate receiving a ToolUse block in an Assistant message 17 let tool_use = ToolUseBlock { 18 id: "toolu_123".to_string(), 19 name: "Read".to_string(), 20 input: serde_json::json!({"file_path": "/etc/hostname"}), 21 }; 22 23 // Store the tool use (as the main code does) 24 pending_tools.insert( 25 tool_use.id.clone(), 26 (tool_use.name.clone(), tool_use.input.clone()), 27 ); 28 29 assert_eq!(pending_tools.len(), 1); 30 assert!(pending_tools.contains_key("toolu_123")); 31 32 // Simulate receiving a ToolResult block in a User message 33 let tool_result = ToolResultBlock { 34 tool_use_id: "toolu_123".to_string(), 35 content: Some(ToolResultContent::Text("hostname content".to_string())), 36 is_error: Some(false), 37 }; 38 39 // Correlate the result (as the main code does) 40 if let Some((tool_name, _tool_input)) = pending_tools.remove(&tool_result.tool_use_id) { 41 let response = match &tool_result.content { 42 Some(ToolResultContent::Text(s)) => serde_json::Value::String(s.clone()), 43 Some(ToolResultContent::Blocks(blocks)) => { 44 serde_json::Value::Array(blocks.iter().cloned().collect()) 45 } 46 None => serde_json::Value::Null, 47 }; 48 tool_results.push((tool_name, tool_result.tool_use_id.clone(), response)); 49 } 50 51 // Verify correlation worked 52 assert!( 53 pending_tools.is_empty(), 54 "Tool should be removed after correlation" 55 ); 56 assert_eq!(tool_results.len(), 1); 57 assert_eq!(tool_results[0].0, "Read"); 58 assert_eq!(tool_results[0].1, "toolu_123"); 59 assert_eq!( 60 tool_results[0].2, 61 serde_json::Value::String("hostname content".to_string()) 62 ); 63 } 64 65 /// Test that unmatched tool results don't cause issues 66 #[test] 67 fn test_unmatched_tool_result() { 68 let mut pending_tools: HashMap<String, (String, serde_json::Value)> = HashMap::new(); 69 let mut tool_results: Vec<(String, String)> = Vec::new(); 70 71 // ToolResult without a matching ToolUse 72 let tool_result = ToolResultBlock { 73 tool_use_id: "toolu_unknown".to_string(), 74 content: Some(ToolResultContent::Text("some content".to_string())), 75 is_error: None, 76 }; 77 78 // Try to correlate - should not find a match 79 if let Some((tool_name, _tool_input)) = pending_tools.remove(&tool_result.tool_use_id) { 80 tool_results.push((tool_name, tool_result.tool_use_id.clone())); 81 } 82 83 // No results should be added 84 assert!(tool_results.is_empty()); 85 } 86 87 /// Test multiple tools in sequence 88 #[test] 89 fn test_multiple_tools_correlation() { 90 let mut pending_tools: HashMap<String, (String, serde_json::Value)> = HashMap::new(); 91 let mut tool_results: Vec<String> = Vec::new(); 92 93 // Add multiple tool uses 94 pending_tools.insert( 95 "toolu_1".to_string(), 96 ("Read".to_string(), serde_json::json!({})), 97 ); 98 pending_tools.insert( 99 "toolu_2".to_string(), 100 ("Bash".to_string(), serde_json::json!({})), 101 ); 102 pending_tools.insert( 103 "toolu_3".to_string(), 104 ("Grep".to_string(), serde_json::json!({})), 105 ); 106 107 assert_eq!(pending_tools.len(), 3); 108 109 // Process results in different order 110 for tool_use_id in ["toolu_2", "toolu_1", "toolu_3"] { 111 if let Some((tool_name, _)) = pending_tools.remove(tool_use_id) { 112 tool_results.push(tool_name); 113 } 114 } 115 116 assert!(pending_tools.is_empty()); 117 assert_eq!(tool_results, vec!["Bash", "Read", "Grep"]); 118 } 119 120 /// Test ContentBlock pattern matching 121 #[test] 122 fn test_content_block_matching() { 123 let blocks: Vec<ContentBlock> = vec![ 124 ContentBlock::Text(claude_agent_sdk_rs::TextBlock { 125 text: "Some text".to_string(), 126 }), 127 ContentBlock::ToolUse(ToolUseBlock { 128 id: "tool_1".to_string(), 129 name: "Read".to_string(), 130 input: serde_json::json!({"file_path": "/test"}), 131 }), 132 ContentBlock::ToolResult(ToolResultBlock { 133 tool_use_id: "tool_1".to_string(), 134 content: Some(ToolResultContent::Text("result".to_string())), 135 is_error: None, 136 }), 137 ]; 138 139 let mut tool_uses = Vec::new(); 140 let mut tool_results = Vec::new(); 141 142 for block in &blocks { 143 match block { 144 ContentBlock::ToolUse(tu) => { 145 tool_uses.push(tu.name.clone()); 146 } 147 ContentBlock::ToolResult(tr) => { 148 tool_results.push(tr.tool_use_id.clone()); 149 } 150 _ => {} 151 } 152 } 153 154 assert_eq!(tool_uses, vec!["Read"]); 155 assert_eq!(tool_results, vec!["tool_1"]); 156 }