notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

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 }