use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::thread;
use std::time::Duration;
use tempfile::TempDir;
fn binary() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_keyhog"))
}
#[test]
fn watch_help_documents_arguments() {
let output = Command::new(binary())
.arg("watch")
.arg("--help")
.output()
.expect("spawn keyhog watch --help");
assert_eq!(output.status.code(), Some(0), "watch --help should exit 0");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("watch") || stdout.contains("PATH") || stdout.contains("--quiet"),
"help should document watch subcommand arguments; got: {stdout}"
);
assert!(
stdout.contains("--detectors") || stdout.contains("--quiet"),
"help should mention --detectors and --quiet flags; got: {stdout}"
);
}
#[test]
fn watch_path_starts_watching_directory() {
let dir = TempDir::new().expect("create tempdir");
let watch_path = dir.path();
let mut child = Command::new(binary())
.arg("watch")
.arg(watch_path)
.arg("--quiet")
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn keyhog watch");
thread::sleep(Duration::from_millis(500));
match child.try_wait() {
Ok(None) => {
let _ = child.kill();
}
Ok(Some(status)) => {
panic!(
"watch process exited prematurely with status: {status}. \
This may indicate watch failed to start."
);
}
Err(e) => {
panic!("failed to check watch status: {e}");
}
}
}
#[test]
fn watch_quiet_flag_suppresses_status_messages() {
let dir = TempDir::new().expect("create tempdir");
let mut noisy = Command::new(binary())
.arg("watch")
.arg(dir.path())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn keyhog watch (noisy)");
thread::sleep(Duration::from_millis(300));
let _ = noisy.kill();
let noisy_output = noisy.wait_with_output().expect("capture noisy output");
let noisy_stdout = String::from_utf8_lossy(&noisy_output.stdout);
let noisy_stderr = String::from_utf8_lossy(&noisy_output.stderr);
let noisy_combined = format!("{noisy_stdout}\n{noisy_stderr}");
let mut quiet = Command::new(binary())
.arg("watch")
.arg(dir.path())
.arg("--quiet")
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn keyhog watch --quiet");
thread::sleep(Duration::from_millis(300));
let _ = quiet.kill();
let quiet_output = quiet.wait_with_output().expect("capture quiet output");
let quiet_stdout = String::from_utf8_lossy(&quiet_output.stdout);
let quiet_stderr = String::from_utf8_lossy(&quiet_output.stderr);
let quiet_combined = format!("{quiet_stdout}\n{quiet_stderr}");
assert!(
quiet_combined.len() <= noisy_combined.len() + 100, "quiet mode should produce similar or less output than noisy mode"
);
}
#[test]
fn watch_detectors_flag_overrides_detector_directory() {
let dir = TempDir::new().expect("create tempdir");
let nonexistent = dir.path().join("nonexistent-detectors");
let output = Command::new(binary())
.arg("watch")
.arg(dir.path())
.arg("--detectors")
.arg(&nonexistent)
.output()
.expect("spawn keyhog watch --detectors <invalid>");
let code = output.status.code();
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
code != Some(0),
"watch with invalid --detectors should fail; stderr: {stderr}"
);
assert!(
stderr.to_lowercase().contains("detector")
|| stderr.to_lowercase().contains("not found")
|| stderr.to_lowercase().contains("corpus"),
"error should identify the missing detector corpus; stderr: {stderr}"
);
}
#[test]
fn watch_default_path_is_current_directory() {
let dir = TempDir::new().expect("create tempdir");
let output = Command::new(binary())
.arg("watch")
.output()
.expect("spawn keyhog watch (no path)");
let code = output.status.code();
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
code == Some(2) || code == Some(0) || code == Some(3),
"watch without explicit path should handle gracefully; code: {code:?}"
);
}