use rexpect::spawn_bash;
use std::time::Duration;
const TIMEOUT_MS: u64 = 30000;
fn sage_binary() -> String {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
format!("{}/../../target/debug/sage", manifest_dir)
}
#[test]
fn test_sage_starts_with_header() {
let mut p = spawn_bash(Some(TIMEOUT_MS)).expect("Failed to spawn bash");
p.send_line(&format!("{} -p 'echo hello'", sage_binary()))
.expect("Failed to send command");
p.exp_regex(r"(?i)(sage|thinking|hello)")
.expect("Should see sage output");
}
#[test]
fn test_invalid_provider_shows_error() {
let mut p = spawn_bash(Some(TIMEOUT_MS)).expect("Failed to spawn bash");
p.send_line("export ANTHROPIC_API_KEY=invalid_key_12345")
.expect("Failed to set env");
p.send_line(&format!("{} -p 'hello'", sage_binary()))
.expect("Failed to send command");
let result = p.exp_regex(r"(?i)(error|failed|invalid|unauthorized|401)");
match result {
Ok(_) => println!("Error message displayed correctly"),
Err(_) => println!("No error (API might be valid or different error path)"),
}
}
#[test]
fn test_ctrl_c_cancellation() {
let mut p = spawn_bash(Some(TIMEOUT_MS)).expect("Failed to spawn bash");
p.send_line(&sage_binary().to_string())
.expect("Failed to start sage");
std::thread::sleep(Duration::from_millis(500));
p.send_control('c').expect("Failed to send Ctrl+C");
std::thread::sleep(Duration::from_millis(500));
}
#[test]
fn test_read_nonexistent_file_error() {
let mut p = spawn_bash(Some(TIMEOUT_MS)).expect("Failed to spawn bash");
p.send_line(&format!(
"{} -p 'read the file /nonexistent/path/file_that_does_not_exist_12345.txt'",
sage_binary()
))
.expect("Failed to send command");
let result = p.exp_regex(r"(?i)(not found|does not exist|no such file|error|cannot)");
match result {
Ok(_) => println!("File error displayed correctly"),
Err(e) => println!("Test result: {:?}", e),
}
}
#[test]
fn test_help_command() {
let mut p = spawn_bash(Some(TIMEOUT_MS)).expect("Failed to spawn bash");
p.send_line(&format!("{} --help", sage_binary()))
.expect("Failed to send command");
p.exp_regex(r"(?i)(usage|options|sage|cli)")
.expect("Should see help output");
}
#[test]
fn test_version_command() {
let mut p = spawn_bash(Some(TIMEOUT_MS)).expect("Failed to spawn bash");
p.send_line(&format!("{} --version", sage_binary()))
.expect("Failed to send command");
p.exp_regex(r"\d+\.\d+\.\d+")
.expect("Should see version number");
}
#[test]
fn test_doctor_command() {
let mut p = spawn_bash(Some(TIMEOUT_MS)).expect("Failed to spawn bash");
p.send_line(&format!("{} doctor", sage_binary()))
.expect("Failed to send command");
p.exp_regex(r"(?i)(health|check|config|provider|summary)")
.expect("Should see doctor output");
}
#[cfg(test)]
mod ui_state_tests {
use parking_lot::RwLock;
use std::sync::Arc;
#[derive(Default)]
struct MockUiState {
is_busy: bool,
error_displayed: bool,
error_message: Option<String>,
}
#[test]
fn test_error_state_sets_flags() {
let state = Arc::new(RwLock::new(MockUiState::default()));
{
let mut s = state.write();
s.is_busy = false;
s.error_displayed = true;
s.error_message = Some("Test error".to_string());
}
let s = state.read();
assert!(!s.is_busy);
assert!(s.error_displayed);
assert_eq!(s.error_message, Some("Test error".to_string()));
}
#[test]
fn test_error_flag_resets_on_new_task() {
let state = Arc::new(RwLock::new(MockUiState {
is_busy: false,
error_displayed: true,
error_message: Some("Old error".to_string()),
}));
{
let mut s = state.write();
s.is_busy = true;
s.error_displayed = false;
s.error_message = None;
}
let s = state.read();
assert!(s.is_busy);
assert!(!s.error_displayed);
assert!(s.error_message.is_none());
}
}