use claude_code_sdk::{ClaudeCodeOptions, SafetyLimits};
use claude_code_sdk::transport::subprocess_cli::SubprocessCLITransport;
#[tokio::test]
async fn test_single_line_json_parsing() {
let options = ClaudeCodeOptions::default();
let mut transport = SubprocessCLITransport::new("test", options, Some("nonexistent"))
.expect("Should create transport");
let single_line_json = r#"{"type": "message", "content": "Hello World"}"#;
let result = transport.process_line(single_line_json.to_string());
assert!(result.is_some(), "Should parse single-line JSON");
let parsed_result = result.unwrap();
assert!(parsed_result.is_ok(), "Single-line JSON should parse successfully");
let data = parsed_result.unwrap();
assert_eq!(data.get("type").unwrap().as_str().unwrap(), "message");
assert_eq!(data.get("content").unwrap().as_str().unwrap(), "Hello World");
}
#[tokio::test]
async fn test_multiline_json_reconstruction() {
let options = ClaudeCodeOptions::default();
let mut transport = SubprocessCLITransport::new("test", options, Some("nonexistent"))
.expect("Should create transport");
let json_lines = vec![
r#"{"#,
r#" "type": "assistant_message","#,
r#" "content": ["#,
r#" {"#,
r#" "type": "text","#,
r#" "text": "This is a multi-line response""#,
r#" }"#,
r#" ],"#,
r#" "turn": 1"#,
r#"}"#,
];
let mut final_result = None;
for (i, line) in json_lines.iter().enumerate() {
println!("Processing line {}: {}", i, line);
let result = transport.process_line(line.to_string());
println!("Result for line {}: {:?}", i, result.is_some());
if i < json_lines.len() - 1 {
if result.is_some() {
println!("Unexpected result at line {}: {:?}", i, result);
}
assert!(result.is_none(), "Line {} should not yet produce a complete JSON result", i);
} else {
if result.is_none() {
println!("No result for final line, trying to manually parse buffer");
let manual_result = transport.try_parse_json_buffer();
println!("Manual parse result: {:?}", manual_result.is_some());
final_result = manual_result;
} else {
final_result = result;
}
assert!(final_result.is_some(), "Final line should complete the JSON");
}
}
let parsed_result = final_result.unwrap();
assert!(parsed_result.is_ok(), "Multiline JSON should parse successfully");
let data = parsed_result.unwrap();
assert_eq!(data.get("type").unwrap().as_str().unwrap(), "assistant_message");
assert_eq!(data.get("turn").unwrap().as_u64().unwrap(), 1);
}
#[tokio::test]
async fn test_malformed_json_handling() {
let options = ClaudeCodeOptions::default();
let mut transport = SubprocessCLITransport::new("test", options, Some("nonexistent"))
.expect("Should create transport");
let result1 = transport.process_line(r#"{"#.to_string());
assert!(result1.is_none(), "Should wait for more JSON data");
let result2 = transport.process_line(r#" "type": "message""#.to_string());
assert!(result2.is_none(), "Should still wait for more data");
let result3 = transport.process_line(r#" "invalid": syntax"#.to_string());
assert!(result3.is_none(), "Should still be accumulating");
let parse_result = transport.try_parse_json_buffer();
assert!(parse_result.is_none(), "Malformed JSON should not parse");
}
#[tokio::test]
async fn test_non_json_lines_ignored() {
let options = ClaudeCodeOptions::default();
let mut transport = SubprocessCLITransport::new("test", options, Some("nonexistent"))
.expect("Should create transport");
let result1 = transport.process_line("Some debug output".to_string());
assert!(result1.is_none(), "Non-JSON line should be ignored");
let result2 = transport.process_line("Another log message".to_string());
assert!(result2.is_none(), "Non-JSON line should be ignored");
let result3 = transport.process_line(r#"{"type": "test"}"#.to_string());
assert!(result3.is_some(), "Valid JSON should be parsed");
assert!(result3.unwrap().is_ok(), "Valid JSON should parse successfully");
}
#[tokio::test]
async fn test_multiple_json_objects() {
let options = ClaudeCodeOptions::default();
let mut transport = SubprocessCLITransport::new("test", options, Some("nonexistent"))
.expect("Should create transport");
let result1 = transport.process_line(r#"{"type": "start", "id": 1}"#.to_string());
assert!(result1.is_some(), "First JSON should be parsed");
let data1 = result1.unwrap().unwrap();
assert_eq!(data1.get("type").unwrap().as_str().unwrap(), "start");
let result2 = transport.process_line(r#"{"type": "end", "id": 2}"#.to_string());
assert!(result2.is_some(), "Second JSON should be parsed");
let data2 = result2.unwrap().unwrap();
assert_eq!(data2.get("type").unwrap().as_str().unwrap(), "end");
}
#[tokio::test]
async fn test_json_buffer_safety_limits() {
let options = ClaudeCodeOptions::default();
let conservative_limits = SafetyLimits::conservative();
let mut transport = SubprocessCLITransport::new("test", options, Some("nonexistent"))
.expect("Should create transport")
.with_safety_limits(conservative_limits);
let result1 = transport.process_line(r#"{"#.to_string());
assert!(result1.is_none(), "Should start accumulating JSON");
let large_text = "A".repeat(2_000_000); let large_line = format!(r#" "large_field": "{}""#, large_text);
let result2 = transport.process_line(large_line);
assert!(result2.is_some(), "Should trigger safety limit");
let error_result = result2.unwrap();
assert!(error_result.is_err(), "Should be a safety error");
}
#[tokio::test]
async fn test_empty_lines_handling() {
let options = ClaudeCodeOptions::default();
let mut transport = SubprocessCLITransport::new("test", options, Some("nonexistent"))
.expect("Should create transport");
let result1 = transport.process_line("".to_string());
assert!(result1.is_none(), "Empty line should be ignored");
let result2 = transport.process_line(" ".to_string()); assert!(result2.is_none(), "Whitespace-only line should be ignored");
let result3 = transport.process_line(r#"{"test": true}"#.to_string());
assert!(result3.is_some(), "Valid JSON should be parsed after empty lines");
}
#[tokio::test]
async fn test_mixed_single_and_multiline_json() {
let options = ClaudeCodeOptions::default();
let mut transport = SubprocessCLITransport::new("test", options, Some("nonexistent"))
.expect("Should create transport");
let result1 = transport.process_line(r#"{"type": "single", "line": true}"#.to_string());
assert!(result1.is_some(), "Single-line JSON should parse");
let result2 = transport.process_line(r#"{"#.to_string());
assert!(result2.is_none(), "Should start multiline accumulation");
let result3 = transport.process_line(r#" "type": "multi","#.to_string());
assert!(result3.is_none(), "Should continue accumulation");
let result4 = transport.process_line(r#" "line": false"#.to_string());
assert!(result4.is_none(), "Should continue accumulation");
let result5 = transport.process_line(r#"}"#.to_string());
assert!(result5.is_some(), "Multiline JSON should complete");
let result6 = transport.process_line(r#"{"type": "another", "single": true}"#.to_string());
assert!(result6.is_some(), "Another single-line JSON should parse");
}