use assert_cmd::Command; use serde_json::Value; use std::fs; use tempfile::tempdir; #[test] fn print_json_has_session_id_and_stats() { let mut cmd = Command::cargo_bin("owlen").unwrap(); cmd.arg("--output-format") .arg("json") .arg("Say hello"); let output = cmd.assert().success(); let stdout = String::from_utf8_lossy(&output.get_output().stdout); // Parse JSON output let json: Value = serde_json::from_str(&stdout).expect("Output should be valid JSON"); // Verify session_id exists assert!(json.get("session_id").is_some(), "JSON output should have session_id"); let session_id = json["session_id"].as_str().unwrap(); assert!(!session_id.is_empty(), "session_id should not be empty"); // Verify stats exist assert!(json.get("stats").is_some(), "JSON output should have stats"); let stats = &json["stats"]; // Check for token counts assert!(stats.get("total_tokens").is_some(), "stats should have total_tokens"); // Check for messages assert!(json.get("messages").is_some(), "JSON output should have messages"); } #[test] fn stream_json_sequence_is_well_formed() { let mut cmd = Command::cargo_bin("owlen").unwrap(); cmd.arg("--output-format") .arg("stream-json") .arg("Say hello"); let output = cmd.assert().success(); let stdout = String::from_utf8_lossy(&output.get_output().stdout); // Stream-JSON is NDJSON - each line should be valid JSON let lines: Vec<&str> = stdout.lines().filter(|l| !l.is_empty()).collect(); assert!(!lines.is_empty(), "Stream-JSON should produce at least one event"); // Each line should be valid JSON for (i, line) in lines.iter().enumerate() { let json: Value = serde_json::from_str(line) .expect(&format!("Line {} should be valid JSON: {}", i, line)); // Each event should have a type assert!(json.get("type").is_some(), "Event should have a type field"); } // First event should be session_start let first: Value = serde_json::from_str(lines[0]).unwrap(); assert_eq!(first["type"].as_str().unwrap(), "session_start"); assert!(first.get("session_id").is_some()); // Last event should be session_end or complete let last: Value = serde_json::from_str(lines[lines.len() - 1]).unwrap(); let last_type = last["type"].as_str().unwrap(); assert!( last_type == "session_end" || last_type == "complete", "Last event should be session_end or complete, got: {}", last_type ); } #[test] fn text_format_is_default() { let mut cmd = Command::cargo_bin("owlen").unwrap(); cmd.arg("Say hello"); let output = cmd.assert().success(); let stdout = String::from_utf8_lossy(&output.get_output().stdout); // Text format should not be JSON assert!(serde_json::from_str::(&stdout).is_err(), "Default output should be text, not JSON"); } #[test] fn json_format_with_tool_execution() { let dir = tempdir().unwrap(); let file = dir.path().join("test.txt"); fs::write(&file, "hello world").unwrap(); let mut cmd = Command::cargo_bin("owlen").unwrap(); cmd.arg("--mode") .arg("code") .arg("--output-format") .arg("json") .arg("read") .arg(file.to_str().unwrap()); let output = cmd.assert().success(); let stdout = String::from_utf8_lossy(&output.get_output().stdout); let json: Value = serde_json::from_str(&stdout).expect("Output should be valid JSON"); // Should have result assert!(json.get("result").is_some()); // Should have tool info assert!(json.get("tool").is_some()); assert_eq!(json["tool"].as_str().unwrap(), "Read"); } #[test] fn stream_json_includes_chunk_events() { let mut cmd = Command::cargo_bin("owlen").unwrap(); cmd.arg("--output-format") .arg("stream-json") .arg("Say hello"); let output = cmd.assert().success(); let stdout = String::from_utf8_lossy(&output.get_output().stdout); let lines: Vec<&str> = stdout.lines().filter(|l| !l.is_empty()).collect(); // Should have chunk events between session_start and session_end let chunk_events: Vec<&str> = lines.iter() .filter(|line| { if let Ok(json) = serde_json::from_str::(line) { json["type"].as_str() == Some("chunk") } else { false } }) .copied() .collect(); assert!(!chunk_events.is_empty(), "Should have at least one chunk event"); // Each chunk should have content for chunk_line in chunk_events { let chunk: Value = serde_json::from_str(chunk_line).unwrap(); assert!(chunk.get("content").is_some(), "Chunk should have content"); } }