#![cfg(feature = "integration-tests")]
use claude_agents_sdk::{query, ClaudeAgentOptions, ClaudeClient, PermissionMode};
use std::time::Duration;
use tokio_stream::StreamExt;
use crate::integration::helpers::*;
#[tokio::test]
async fn test_special_characters_no_panic() {
let valid_prompts = [
"{\"test\": \"valid\"}", "```json\n{}\n```", "Hello\r\nWorld\r\n", "🎉🎊🎁", ];
let edge_case_prompts = [
"\x00\x01\x02", "\n\n\n", "\t\t\t", ];
for prompt in valid_prompts {
let result = collect_messages(prompt, default_options()).await;
match result {
Ok(messages) => {
assert!(
get_result(&messages).is_some(),
"Should have result message for prompt: {:?}",
prompt
);
}
Err(e) => {
panic!("Valid prompt {:?} failed unexpectedly: {}", prompt, e);
}
}
}
for prompt in edge_case_prompts {
let result = collect_messages(prompt, default_options()).await;
match result {
Ok(messages) => {
eprintln!(
"Edge case prompt {:?} succeeded with {} messages",
prompt,
messages.len()
);
}
Err(e) => {
eprintln!("Edge case prompt {:?} failed gracefully: {}", prompt, e);
}
}
}
}
#[tokio::test]
async fn test_empty_prompt() {
let result = collect_messages("", default_options()).await;
match result {
Ok(messages) => {
eprintln!("Empty prompt succeeded with {} messages", messages.len());
if get_result(&messages).is_none() {
eprintln!("Note: Empty prompt completed but no result message");
}
}
Err(e) => {
eprintln!("Empty prompt error (acceptable): {}", e);
}
}
}
#[tokio::test]
async fn test_very_long_prompt() {
let long_text = "word ".repeat(2000);
let prompt = format!("Count roughly how many words are here: {}", long_text);
let result = collect_messages(&prompt, default_options()).await;
match result {
Ok(messages) => {
let result = get_result(&messages).expect("Should have result");
assert!(!result.is_error, "Long prompt should be handled");
}
Err(e) => {
assert!(
e.contains("too long")
|| e.contains("limit")
|| e.contains("size")
|| !e.is_empty(),
"Error should be descriptive: {}",
e
);
}
}
}
#[tokio::test]
async fn test_connection_error_reporting() {
let mut client = ClaudeClient::new(Some(default_options()));
let connect_result = client.connect().await;
match connect_result {
Ok(_) => {
client.disconnect().await.ok();
}
Err(e) => {
let error_str = e.to_string();
assert!(!error_str.is_empty(), "Error message should not be empty");
eprintln!("Connection error (for reference): {}", error_str);
}
}
}
#[tokio::test]
async fn test_query_without_connect() {
let mut client = ClaudeClient::new(Some(default_options()));
let query_result = client.query("Hello").await;
assert!(query_result.is_err(), "Query without connect should fail");
let error = query_result.unwrap_err();
eprintln!("Query without connect error: {}", error);
}
#[tokio::test]
async fn test_double_disconnect() {
let mut client = ClaudeClient::new(Some(default_options()));
client.connect().await.expect("Failed to connect");
client.disconnect().await.expect("First disconnect failed");
let second_result = client.disconnect().await;
match second_result {
Ok(_) => eprintln!("Double disconnect succeeded (idempotent)"),
Err(e) => eprintln!("Double disconnect error (acceptable): {}", e),
}
}
#[tokio::test]
async fn test_double_connect() {
let mut client = ClaudeClient::new(Some(default_options()));
client.connect().await.expect("First connect failed");
let second_result = client.connect().await;
match second_result {
Ok(_) => eprintln!("Double connect succeeded (reconnect)"),
Err(e) => eprintln!("Double connect error (acceptable): {}", e),
}
client.disconnect().await.ok();
}
#[tokio::test]
async fn test_stream_error_propagation() {
let options = ClaudeAgentOptions::new()
.with_max_turns(1)
.with_permission_mode(PermissionMode::Default);
let stream_result = query("Hello", Some(options)).await;
match stream_result {
Ok(mut stream) => {
let mut had_error = false;
let mut had_result = false;
while let Some(msg) = stream.next().await {
match msg {
Ok(m) => {
if m.is_result() {
had_result = true;
}
}
Err(e) => {
had_error = true;
eprintln!("Stream error: {}", e);
}
}
}
assert!(
had_result || had_error,
"Stream should complete with result or error"
);
}
Err(e) => {
eprintln!("Query start error: {}", e);
}
}
}
#[tokio::test]
async fn test_short_timeout_behavior() {
let options = ClaudeAgentOptions::new()
.with_timeout_secs(1) .with_max_turns(10) .with_permission_mode(PermissionMode::Default);
let start = std::time::Instant::now();
let result = tokio::time::timeout(
Duration::from_secs(30), collect_messages(
"Explain the entire history of computer science in detail.",
options,
),
)
.await;
let elapsed = start.elapsed();
match result {
Ok(Ok(messages)) => {
let result = get_result(&messages);
eprintln!(
"Completed in {:?} with result: {:?}",
elapsed,
result.is_some()
);
}
Ok(Err(e)) => {
eprintln!("SDK error after {:?}: {}", elapsed, e);
if e.to_lowercase().contains("timeout") {
assert!(
elapsed.as_secs() < 30,
"Timeout should occur reasonably quickly"
);
}
}
Err(_) => {
panic!("Test timeout hit - SDK timeout may not be working");
}
}
}
#[tokio::test]
#[ignore]
async fn test_authentication_failure() {
let options = ClaudeAgentOptions::new()
.with_permission_mode(PermissionMode::Default)
.with_max_turns(1);
let result = collect_messages("test", options).await;
assert!(result.is_err(), "Should fail with invalid credentials");
let err = result.unwrap_err();
assert!(
err.to_lowercase().contains("auth")
|| err.to_lowercase().contains("api")
|| err.to_lowercase().contains("key")
|| err.to_lowercase().contains("unauthorized")
|| err.to_lowercase().contains("401"),
"Error should relate to authentication: {}",
err
);
}