//! Integration tests for terminal operations. use dirigent_tools::config::TerminalConfig; use dirigent_tools::terminal::{ create_terminal, get_terminal_output, kill_terminal, release_terminal, wait_for_terminal_exit, CreateTerminalRequest, KillTerminalCommandRequest, ReleaseTerminalRequest, TerminalOutputRequest, WaitForTerminalExitRequest, }; #[tokio::test] async fn test_terminal_echo_command() { let config = TerminalConfig { enabled: true, default_cwd: Some(std::env::current_dir().unwrap()), env_allowlist: vec![], command_blocklist: vec![], output_byte_limit: 10_000, max_runtime_secs: 30, }; // Create terminal with echo command #[cfg(windows)] let request = CreateTerminalRequest { command: "cmd".to_string(), args: vec!["/C".to_string(), "echo".to_string(), "Hello World".to_string()], cwd: None, env: None, output_byte_limit: None, }; #[cfg(not(windows))] let request = CreateTerminalRequest { command: "echo".to_string(), args: vec!["Hello World".to_string()], cwd: None, env: None, output_byte_limit: None, }; let create_response = create_terminal(request, &config).await.unwrap(); let terminal_id = create_response.terminal_id; // Wait for command to complete tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; // Get output let output_response = get_terminal_output( TerminalOutputRequest { terminal_id: terminal_id.clone(), }, &config, ) .await .unwrap(); assert!(output_response.output.contains("Hello World")); // Release terminal let _ = release_terminal( ReleaseTerminalRequest { terminal_id: terminal_id.clone(), }, &config, ) .await; } #[tokio::test] async fn test_terminal_wait_for_exit() { let config = TerminalConfig { enabled: true, default_cwd: Some(std::env::current_dir().unwrap()), env_allowlist: vec![], command_blocklist: vec![], output_byte_limit: 10_000, max_runtime_secs: 30, }; // Create terminal with a quick command #[cfg(windows)] let request = CreateTerminalRequest { command: "cmd".to_string(), args: vec!["/C".to_string(), "exit".to_string(), "0".to_string()], cwd: None, env: None, output_byte_limit: None, }; #[cfg(not(windows))] let request = CreateTerminalRequest { command: "true".to_string(), args: vec![], cwd: None, env: None, output_byte_limit: None, }; let create_response = create_terminal(request, &config).await.unwrap(); let terminal_id = create_response.terminal_id; // Wait for exit let wait_response = wait_for_terminal_exit( WaitForTerminalExitRequest { terminal_id: terminal_id.clone(), }, &config, ) .await .unwrap(); assert_eq!(wait_response.exit_status, 0); // Release terminal let _ = release_terminal( ReleaseTerminalRequest { terminal_id: terminal_id.clone(), }, &config, ) .await; } #[tokio::test] async fn test_terminal_kill() { let config = TerminalConfig { enabled: true, default_cwd: Some(std::env::current_dir().unwrap()), env_allowlist: vec![], command_blocklist: vec![], output_byte_limit: 10_000, max_runtime_secs: 30, }; // Create terminal with a long-running command #[cfg(windows)] let request = CreateTerminalRequest { command: "cmd".to_string(), args: vec!["/C".to_string(), "timeout".to_string(), "/t".to_string(), "10".to_string()], cwd: None, env: None, output_byte_limit: None, }; #[cfg(not(windows))] let request = CreateTerminalRequest { command: "sleep".to_string(), args: vec!["10".to_string()], cwd: None, env: None, output_byte_limit: None, }; let create_response = create_terminal(request, &config).await.unwrap(); let terminal_id = create_response.terminal_id; // Wait a bit to ensure process is running tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; // Kill the terminal let kill_response = kill_terminal( KillTerminalCommandRequest { terminal_id: terminal_id.clone(), }, &config, ) .await; assert!(kill_response.is_ok()); // Release terminal let _ = release_terminal( ReleaseTerminalRequest { terminal_id: terminal_id.clone(), }, &config, ) .await; } #[tokio::test] async fn test_terminal_disabled() { let config = TerminalConfig { enabled: false, default_cwd: Some(std::env::current_dir().unwrap()), env_allowlist: vec![], command_blocklist: vec![], output_byte_limit: 10_000, max_runtime_secs: 30, }; let request = CreateTerminalRequest { command: "echo".to_string(), args: vec!["test".to_string()], cwd: None, env: None, output_byte_limit: None, }; let result = create_terminal(request, &config).await; assert!(result.is_err()); } #[tokio::test] async fn test_terminal_command_blocklist() { let config = TerminalConfig { enabled: true, default_cwd: Some(std::env::current_dir().unwrap()), env_allowlist: vec![], command_blocklist: vec!["rm".to_string(), "del".to_string()], output_byte_limit: 10_000, max_runtime_secs: 30, }; let request = CreateTerminalRequest { command: "rm".to_string(), args: vec!["-rf".to_string(), "/".to_string()], cwd: None, env: None, output_byte_limit: None, }; let result = create_terminal(request, &config).await; assert!(result.is_err()); } #[tokio::test] async fn test_terminal_output_truncation() { let config = TerminalConfig { enabled: true, default_cwd: Some(std::env::current_dir().unwrap()), env_allowlist: vec![], command_blocklist: vec![], output_byte_limit: 100, // Very small buffer max_runtime_secs: 30, }; // Create terminal that generates lots of output #[cfg(windows)] let request = CreateTerminalRequest { command: "cmd".to_string(), args: vec![ "/C".to_string(), "for".to_string(), "/L".to_string(), "%i".to_string(), "in".to_string(), "(1,1,100)".to_string(), "do".to_string(), "@echo".to_string(), "Line %i".to_string(), ], cwd: None, env: None, output_byte_limit: Some(100), }; #[cfg(not(windows))] let request = CreateTerminalRequest { command: "sh".to_string(), args: vec![ "-c".to_string(), "for i in $(seq 1 100); do echo Line $i; done".to_string(), ], cwd: None, env: None, output_byte_limit: Some(100), }; let create_response = create_terminal(request, &config).await.unwrap(); let terminal_id = create_response.terminal_id; // Wait for command to complete tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; // Get output let output_response = get_terminal_output( TerminalOutputRequest { terminal_id: terminal_id.clone(), }, &config, ) .await .unwrap(); // Buffer should be truncated assert!(output_response.truncated || output_response.output.len() <= 100); // Release terminal let _ = release_terminal( ReleaseTerminalRequest { terminal_id: terminal_id.clone(), }, &config, ) .await; }