use anyhow::Result;
use claude_agent_sdk::{
ClaudeAgentOptions, ClaudeClient, ContentBlock, McpServerConfig, McpToolResultContent, Message,
PermissionMode, ToolResult, create_sdk_mcp_server, query, query_stream, tool,
};
use futures::stream::StreamExt;
use serde_json::json;
use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<()> {
println!("=== Complete Integration Test Example ===\n");
let mut passed = 0;
let mut failed = 0;
println!("Running: Basic Query");
match test_basic_query().await {
Ok(_) => { println!(" ✓ PASSED\n"); passed += 1; },
Err(e) => { println!(" ✗ FAILED: {}\n", e); failed += 1; },
}
println!("Running: Streaming Query");
match test_streaming_query().await {
Ok(_) => { println!(" ✓ PASSED\n"); passed += 1; },
Err(e) => { println!(" ✗ FAILED: {}\n", e); failed += 1; },
}
println!("Running: Custom Tools");
match test_custom_tools().await {
Ok(_) => { println!(" ✓ PASSED\n"); passed += 1; },
Err(e) => { println!(" ✗ FAILED: {}\n", e); failed += 1; },
}
println!("Running: Bidirectional Client");
match test_bidirectional_client().await {
Ok(_) => { println!(" ✓ PASSED\n"); passed += 1; },
Err(e) => { println!(" ✗ FAILED: {}\n", e); failed += 1; },
}
println!("Running: Error Handling");
match test_error_handling().await {
Ok(_) => { println!(" ✓ PASSED\n"); passed += 1; },
Err(e) => { println!(" ✗ FAILED: {}\n", e); failed += 1; },
}
println!("Running: Permission System");
match test_permission_system().await {
Ok(_) => { println!(" ✓ PASSED\n"); passed += 1; },
Err(e) => { println!(" ✗ FAILED: {}\n", e); failed += 1; },
}
println!("Running: Hooks");
match test_hooks().await {
Ok(_) => { println!(" ✓ PASSED\n"); passed += 1; },
Err(e) => { println!(" ✗ FAILED: {}\n", e); failed += 1; },
}
println!("Running: Budget Control");
match test_budget_control().await {
Ok(_) => { println!(" ✓ PASSED\n"); passed += 1; },
Err(e) => { println!(" ✗ FAILED: {}\n", e); failed += 1; },
}
println!("Running: Session Management");
match test_session_management().await {
Ok(_) => { println!(" ✓ PASSED\n"); passed += 1; },
Err(e) => { println!(" ✗ FAILED: {}\n", e); failed += 1; },
}
println!("Running: Concurrent Operations");
match test_concurrent_operations().await {
Ok(_) => { println!(" ✓ PASSED\n"); passed += 1; },
Err(e) => { println!(" ✗ FAILED: {}\n", e); failed += 1; },
}
let total = passed + failed;
println!("=== Test Summary ===");
println!("Total: {}", total);
println!("Passed: {}", passed);
println!("Failed: {}", failed);
if failed > 0 {
println!("\n⚠️ Some tests failed");
std::process::exit(1);
} else {
println!("\n✅ All tests passed!");
}
Ok(())
}
async fn test_basic_query() -> Result<()> {
let messages = query("What is 2 + 2?", None).await?;
assert!(!messages.is_empty(), "Should receive messages");
let has_response = messages.iter().any(|m| matches!(m, Message::Assistant(_)));
assert!(has_response, "Should have assistant response");
Ok(())
}
async fn test_streaming_query() -> Result<()> {
let mut stream = query_stream("What is 2 + 2?", None).await?;
let mut count = 0;
while let Some(result) = stream.next().await {
let _msg = result?;
count += 1;
}
assert!(count > 0, "Should receive messages via stream");
Ok(())
}
async fn test_custom_tools() -> Result<()> {
async fn calculator_tool(args: serde_json::Value) -> Result<ToolResult> {
let a = args["a"].as_i64().unwrap_or(0);
let b = args["b"].as_i64().unwrap_or(0);
Ok(ToolResult {
content: vec![McpToolResultContent::Text {
text: format!("{} + {} = {}", a, b, a + b),
}],
is_error: false,
})
}
let calc_tool = tool!(
"calculator",
"Add two numbers",
json!({
"type": "object",
"properties": {
"a": {"type": "integer"},
"b": {"type": "integer"}
},
"required": ["a", "b"]
}),
calculator_tool
);
let server = create_sdk_mcp_server("calc-server", "1.0.0", vec![calc_tool]);
let mut mcp_servers = std::collections::HashMap::new();
mcp_servers.insert("calc-server".to_string(), McpServerConfig::Sdk(server));
let options = ClaudeAgentOptions::builder()
.mcp_servers(claude_agent_sdk::McpServers::Dict(mcp_servers))
.allowed_tools(vec!["mcp__calc-server__calculator".to_string()])
.permission_mode(PermissionMode::BypassPermissions)
.build();
let _messages = query("Use the calculator to add 5 and 3", Some(options)).await?;
Ok(())
}
async fn test_bidirectional_client() -> Result<()> {
let mut client = ClaudeClient::new(ClaudeAgentOptions::default());
client.connect().await?;
client.query("What is 2 + 2?").await?;
let mut response_count = 0;
{
let mut stream = client.receive_response();
while let Some(result) = stream.next().await {
let msg = result?;
if matches!(msg, Message::Result(_)) {
break;
}
response_count += 1;
}
}
client.disconnect().await?;
assert!(response_count > 0, "Should receive responses");
Ok(())
}
async fn test_error_handling() -> Result<()> {
let result = query("", None).await;
match result {
Ok(_) => Ok(()),
Err(_) => Ok(()), }
}
async fn test_permission_system() -> Result<()> {
let options = ClaudeAgentOptions::builder()
.permission_mode(PermissionMode::BypassPermissions)
.allowed_tools(vec!["Read".to_string()])
.build();
let _messages = query("List files in current directory", Some(options)).await?;
Ok(())
}
async fn test_hooks() -> Result<()> {
use claude_agent_sdk::{HookContext, HookInput, HookJsonOutput, HookMatcher, Hooks};
use std::sync::Arc;
async fn test_hook(
_input: HookInput,
_tool_use_id: Option<String>,
_context: HookContext,
) -> HookJsonOutput {
HookJsonOutput::Sync(Default::default())
}
let mut hooks = Hooks::new();
hooks.add_pre_tool_use_with_matcher("Read", test_hook);
let options = ClaudeAgentOptions::builder()
.hooks(hooks.build())
.build();
let _messages = query("Read README.md", Some(options)).await?;
Ok(())
}
async fn test_budget_control() -> Result<()> {
let options = ClaudeAgentOptions::builder()
.max_budget_usd(0.01) .max_turns(2)
.build();
let _messages = query("What is 2 + 2?", Some(options)).await?;
Ok(())
}
async fn test_session_management() -> Result<()> {
let options1 = ClaudeAgentOptions::builder()
.resume("test-session-1".to_string())
.build();
let options2 = ClaudeAgentOptions::builder()
.resume("test-session-2".to_string())
.build();
let _msg1 = query("Remember: X = 1", Some(options1)).await?;
let _msg2 = query("Remember: X = 2", Some(options2)).await?;
let options_check = ClaudeAgentOptions::builder()
.resume("test-session-1".to_string())
.continue_conversation(true)
.build();
let msg_check = query("What is X?", Some(options_check)).await?;
let response_text = extract_response_text(&msg_check);
let has_correct_value = response_text.contains("1");
assert!(has_correct_value, "Session should maintain separate state");
Ok(())
}
async fn test_concurrent_operations() -> Result<()> {
use futures::stream::{self, StreamExt, TryStreamExt};
let queries = vec!["What is 2 + 2?", "What is 3 + 3?", "What is 4 + 4?"];
let results: Vec<_> = stream::iter(queries)
.map(|q| async move { query(q, None).await })
.buffer_unordered(3)
.try_collect()
.await?;
assert_eq!(results.len(), 3, "Should complete all queries");
Ok(())
}
async fn test_multimodal_input() -> Result<()> {
use claude_agent_sdk::{UserContentBlock, query_with_content};
let base64_data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==";
let messages = query_with_content(
vec![
UserContentBlock::text("What color is this image?"),
UserContentBlock::image_base64("image/png", base64_data)?,
],
None,
)
.await?;
assert!(!messages.is_empty(), "Should handle multimodal input");
Ok(())
}
async fn test_fallback_model() -> Result<()> {
let options = ClaudeAgentOptions::builder()
.model("claude-opus-4-5")
.fallback_model("claude-sonnet-4-5".to_string())
.build();
let _messages = query("What is 2 + 2?", Some(options)).await?;
Ok(())
}
async fn test_extended_thinking() -> Result<()> {
let options = ClaudeAgentOptions::builder()
.max_thinking_tokens(10000)
.build();
let _messages = query("Solve this complex problem", Some(options)).await?;
Ok(())
}
async fn test_tool_restrictions() -> Result<()> {
let options = ClaudeAgentOptions::builder()
.allowed_tools(vec!["Read".to_string()])
.disallowed_tools(vec!["Write".to_string()])
.build();
let _messages = query("Read README.md", Some(options)).await?;
Ok(())
}
async fn test_system_prompts() -> Result<()> {
use claude_agent_sdk::SystemPrompt;
let options = ClaudeAgentOptions::builder()
.system_prompt(SystemPrompt::Text(
"You are a helpful assistant focused on brevity.".to_string(),
))
.build();
let _messages = query("What is 2 + 2? Be brief.", Some(options)).await?;
Ok(())
}
async fn test_cli_path_config() -> Result<()> {
use std::path::PathBuf;
let options = ClaudeAgentOptions::builder()
.cli_path(PathBuf::from("claude"))
.build();
let _messages = query("What is 2 + 2?", Some(options)).await?;
Ok(())
}
async fn test_working_directory() -> Result<()> {
use std::path::PathBuf;
let options = ClaudeAgentOptions::builder()
.cwd(PathBuf::from("/tmp"))
.build();
let _messages = query("What is the current directory?", Some(options)).await?;
Ok(())
}
async fn test_environment_variables() -> Result<()> {
use std::collections::HashMap;
let mut env = HashMap::new();
env.insert("TEST_VAR".to_string(), "test_value".to_string());
let options = ClaudeAgentOptions::builder().env(env).build();
let _messages = query("What is the value of TEST_VAR?", Some(options)).await?;
Ok(())
}
async fn test_fork_session() -> Result<()> {
let options = ClaudeAgentOptions::builder().fork_session(true).build();
let _messages = query("What is 2 + 2?", Some(options)).await?;
let options2 = ClaudeAgentOptions::builder().fork_session(true).build();
let _messages2 = query("What did I ask before?", Some(options2)).await?;
Ok(())
}
async fn test_max_turns_enforcement() -> Result<()> {
let options = ClaudeAgentOptions::builder().max_turns(1).build();
let messages = query("What is 2 + 2?", Some(options)).await?;
assert!(!messages.is_empty(), "Should get at least one message");
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 ContentBlock::Text(content) = block {
text.push_str(&content.text);
}
}
}
}
text
}
async fn test_performance_assertions() -> Result<()> {
use std::time::Instant;
let start = Instant::now();
let _messages = query("What is 2 + 2?", None).await?;
let elapsed = start.elapsed();
assert!(
elapsed.as_secs() < 30,
"Query should complete within 30 seconds"
);
println!(" Performance: {:?}", elapsed);
Ok(())
}
async fn test_memory_leak() -> Result<()> {
for i in 0..10 {
let _messages = query(&format!("Query {}", i), None).await?;
}
Ok(())
}
async fn test_cleanup() -> Result<()> {
let mut client = ClaudeClient::new(ClaudeAgentOptions::default());
client.connect().await?;
client.disconnect().await?;
Ok(())
}