use anyhow::Result;
use claude_agent_sdk::{
ClaudeAgentOptions, McpServerConfig, McpToolResultContent, Message, ToolResult,
create_sdk_mcp_server, query, tool,
};
use serde_json::json;
#[tokio::main]
async fn main() -> Result<()> {
println!("=== Testing Patterns Example ===\n");
println!("1. Mock Tool Testing:");
mock_tool_testing().await?;
println!("\n2. Deterministic Testing:");
deterministic_testing().await?;
println!("\n3. Integration Testing:");
integration_testing_patterns().await?;
println!("\n4. Property-Based Testing:");
property_based_testing().await?;
println!("\n5. Performance Testing:");
performance_testing().await?;
println!("\n6. Error Scenario Testing:");
error_scenario_testing().await?;
println!("\n7. Snapshot Testing:");
snapshot_testing().await?;
println!("\n8. Test Data Builders:");
test_data_builders().await?;
Ok(())
}
async fn mock_tool_testing() -> Result<()> {
async fn mock_tool_handler(args: serde_json::Value) -> Result<ToolResult> {
Ok(ToolResult {
content: vec![McpToolResultContent::Text {
text: format!("Mock response to: {}", args),
}],
is_error: false,
})
}
let mock_tool = tool!(
"mock_tool",
"A mock tool for testing",
json!({
"type": "object",
"properties": {
"input": { "type": "string" }
}
}),
mock_tool_handler
);
let server = create_sdk_mcp_server("test-tools", "1.0.0", vec![mock_tool]);
let mut mcp_servers = std::collections::HashMap::new();
mcp_servers.insert("test-tools".to_string(), McpServerConfig::Sdk(server));
let options = ClaudeAgentOptions::builder()
.mcp_servers(claude_agent_sdk::McpServers::Dict(mcp_servers))
.allowed_tools(vec!["mcp__test-tools__mock_tool".to_string()])
.permission_mode(claude_agent_sdk::PermissionMode::BypassPermissions)
.build();
println!(" Mock tool configured and ready for testing");
let _messages = query("Use the mock tool with 'test input'", Some(options)).await?;
println!(" ✓ Mock tool test passed");
Ok(())
}
async fn deterministic_testing() -> Result<()> {
let test_cases = vec![
("2 + 2", "4"),
("capital of France", "Paris"),
("Rust ownership", "ownership system"),
];
for (query_text, expected_keyword) in test_cases {
let options = ClaudeAgentOptions::builder()
.model("claude-sonnet-4-5")
.max_turns(1)
.build();
let messages = query(query_text, Some(options)).await?;
let response_text = extract_response_text(&messages);
let contains = response_text
.to_lowercase()
.contains(&expected_keyword.to_lowercase());
println!(
" Query: '{}' -> Contains '{}': {}",
query_text, expected_keyword, contains
);
if !contains {
println!(" ⚠️ Warning: Expected keyword not found");
}
}
println!(" ✓ Deterministic testing complete");
Ok(())
}
async fn integration_testing_patterns() -> Result<()> {
struct TestSuite {
name: String,
query: String,
expected_tools: Vec<String>,
min_turns: u32,
max_turns: u32,
}
let test_cases = vec![
TestSuite {
name: "File Reading".to_string(),
query: "Read the README.md file and summarize it".to_string(),
expected_tools: vec!["Read".to_string()],
min_turns: 1,
max_turns: 5,
},
TestSuite {
name: "Code Analysis".to_string(),
query: "Analyze the code in src/lib.rs".to_string(),
expected_tools: vec!["Read".to_string()],
min_turns: 1,
max_turns: 3,
},
];
for test in test_cases {
println!(" Running test: {}", test.name);
let options = ClaudeAgentOptions::builder()
.max_turns(test.max_turns)
.allowed_tools(test.expected_tools.clone())
.build();
match query(&test.query, Some(options)).await {
Ok(messages) => {
let turn_count = messages.len() as u32;
let valid_turns =
turn_count >= test.min_turns && turn_count <= test.max_turns;
println!(" Turn count: {} (valid: {})", turn_count, valid_turns);
if !valid_turns {
println!(" ⚠️ Warning: Turn count outside expected range");
}
},
Err(e) => {
println!(" ✗ Test failed: {}", e);
},
}
}
println!(" ✓ Integration testing complete");
Ok(())
}
async fn property_based_testing() -> Result<()> {
let test_inputs = vec!["What is 2 + 2?", "What is 5 + 3?", "What is 10 + 15?"];
println!(" Testing property: Non-empty responses");
for input in test_inputs {
let messages = query(input, None).await?;
let response_text = extract_response_text(&messages);
let property_holds = !response_text.trim().is_empty();
println!(" Input: '{}' -> Non-empty: {}", input, property_holds);
if !property_holds {
println!(" ✗ Property violation: Empty response");
return Err(anyhow::anyhow!("Property violation detected"));
}
}
println!(" ✓ All properties verified");
Ok(())
}
async fn performance_testing() -> Result<()> {
use std::time::Instant;
let test_query = "What is 2 + 2?";
let iterations = 5;
println!(" Running {} iterations...", iterations);
let mut total_time = std::time::Duration::from_secs(0);
for i in 0..iterations {
let start = Instant::now();
let _messages = query(test_query, None).await?;
let elapsed = start.elapsed();
total_time += elapsed;
println!(" Iteration {}: {:?}", i + 1, elapsed);
}
let avg_time = total_time / iterations;
println!(" Average time: {:?}", avg_time);
let max_acceptable = std::time::Duration::from_secs(10);
if avg_time > max_acceptable {
println!(" ⚠️ Warning: Average time exceeds {:?}", max_acceptable);
} else {
println!(" ✓ Performance within acceptable range");
}
Ok(())
}
async fn error_scenario_testing() -> Result<()> {
let error_scenarios = vec![
("Empty query", ""),
("Invalid tool usage", "Use a non-existent tool XYZ123"),
("Malformed input", "Query with malformed \x00 input"),
];
for (scenario, query_text) in error_scenarios {
println!(" Testing: {}", scenario);
match query(query_text, None).await {
Ok(messages) => {
println!(" Unexpected success ({} messages)", messages.len());
},
Err(e) => {
println!(" Expected error: {}", e);
},
}
}
println!(" ✓ Error scenario testing complete");
Ok(())
}
async fn snapshot_testing() -> Result<()> {
let query_text = "What is 2 + 2?";
let options = ClaudeAgentOptions::builder()
.model("claude-sonnet-4-5")
.max_turns(1)
.build();
let messages = query(query_text, Some(options)).await?;
let response_text = extract_response_text(&messages);
println!(" Generated snapshot:");
println!(" ```");
println!("{}", response_text);
println!(" ```");
let expected_contains = "4";
let matches = response_text.contains(expected_contains);
println!(" Contains '{}': {}", expected_contains, matches);
if matches {
println!(" ✓ Snapshot matches expected content");
} else {
println!(" ⚠️ Snapshot differs from expected");
}
Ok(())
}
async fn test_data_builders() -> Result<()> {
struct TestDataBuilder {
query: String,
max_turns: Option<u32>,
model: Option<String>,
}
impl TestDataBuilder {
fn new() -> Self {
Self {
query: String::new(),
max_turns: None,
model: None,
}
}
fn query(mut self, query: impl Into<String>) -> Self {
self.query = query.into();
self
}
fn max_turns(mut self, turns: u32) -> Self {
self.max_turns = Some(turns);
self
}
fn model(mut self, model: impl Into<String>) -> Self {
self.model = Some(model.into());
self
}
fn build(self) -> (String, ClaudeAgentOptions) {
let options = match (self.max_turns, self.model) {
(Some(max_turns), Some(model)) => {
ClaudeAgentOptions::builder()
.max_turns(max_turns)
.model(model)
.build()
}
(Some(max_turns), None) => {
ClaudeAgentOptions::builder()
.max_turns(max_turns)
.build()
}
(None, Some(model)) => {
ClaudeAgentOptions::builder()
.model(model)
.build()
}
(None, None) => {
ClaudeAgentOptions::builder().build()
}
};
(self.query, options)
}
}
let (query_text, options) = TestDataBuilder::new()
.query("What is 2 + 2?")
.max_turns(2)
.model("claude-sonnet-4-5")
.build();
println!(" Built test data:");
println!(" Query: {}", query_text);
println!(" Max turns: {:?}", options.max_turns);
println!(" Model: {:?}", options.model);
let _messages = query(&query_text, Some(options)).await?;
println!(" ✓ Test data builder pattern works");
Ok(())
}
fn extract_response_text(messages: &[Message]) -> String {
let mut text = String::new();
for msg in messages {
if let Message::Assistant(assistant_msg) = msg {
for block in &assistant_msg.message.content {
if let claude_agent_sdk::ContentBlock::Text(content) = block {
text.push_str(&content.text);
}
}
}
}
text
}
async fn custom_assertions() -> Result<()> {
let messages = query("List numbers 1-5", None).await?;
let response = extract_response_text(&messages);
let numbers = vec!["1", "2", "3", "4", "5"];
let all_present = numbers.iter().all(|n| response.contains(n));
println!(
" Custom assertion: All numbers present -> {}",
all_present
);
if !all_present {
println!(" Missing numbers:");
for n in numbers {
if !response.contains(n) {
println!(" - {}", n);
}
}
}
assert!(all_present, "Not all numbers present in response");
println!(" ✓ Custom assertion passed");
Ok(())
}
async fn test_isolation() -> Result<()> {
let session_1 = "test-session-1";
let session_2 = "test-session-2";
let options1 = ClaudeAgentOptions::builder()
.resume(session_1.to_string())
.build();
let options2 = ClaudeAgentOptions::builder()
.resume(session_2.to_string())
.build();
let _msg1 = query("Remember: X = 1", Some(options1)).await?;
let _msg2 = query("Remember: X = 2", Some(options2)).await?;
let options1_check = ClaudeAgentOptions::builder()
.resume(session_1.to_string())
.continue_conversation(true)
.build();
let msg_check = query("What is X?", Some(options1_check)).await?;
let response = extract_response_text(&msg_check);
let isolated = response.contains("1") && !response.contains("2");
println!(" Session isolation: {}", isolated);
if isolated {
println!(" ✓ Sessions are properly isolated");
} else {
println!(" ⚠️ Warning: Session isolation may be compromised");
}
Ok(())
}