use assert_cmd::prelude::*;
use predicates::prelude::*;
use std::process::Command;
fn fake_socket(dir: &tempfile::TempDir) -> std::path::PathBuf {
dir.path().join("does-not-exist.sock")
}
fn cli() -> Command {
Command::cargo_bin("sysknife").expect("sysknife binary builds")
}
#[test]
fn help_lists_every_top_level_subcommand() {
let output = cli()
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("doctor"))
.stdout(predicate::str::contains("history"))
.stdout(predicate::str::contains("audit"))
.stdout(predicate::str::contains("mcp-server"))
.stdout(predicate::str::contains("completions"));
drop(output);
}
#[test]
fn unknown_subcommand_via_clap_usage_error() {
cli()
.arg("--no-such-flag")
.assert()
.failure()
.stderr(predicate::str::contains("unexpected").or(predicate::str::contains("unknown")));
}
#[test]
fn doctor_fails_loudly_when_daemon_socket_is_unreachable() {
let dir = tempfile::tempdir().unwrap();
cli()
.env("SYSKNIFE_SOCKET", fake_socket(&dir))
.arg("doctor")
.assert()
.failure()
.stderr(predicate::str::contains("daemon"))
.stdout(predicate::str::is_empty());
}
#[test]
fn doctor_json_failure_goes_to_stdout_not_stderr() {
let dir = tempfile::tempdir().unwrap();
cli()
.env("SYSKNIFE_SOCKET", fake_socket(&dir))
.args(["doctor", "--json"])
.assert()
.failure()
.stdout(predicate::str::contains("\"ok\":false"));
}
#[test]
fn history_rejects_unparseable_since_timestamp() {
let dir = tempfile::tempdir().unwrap();
cli()
.env("SYSKNIFE_SOCKET", fake_socket(&dir))
.arg("history")
.arg("--since")
.arg("not-a-timestamp")
.assert()
.code(4);
}
#[test]
fn audit_verify_exits_with_code_2_when_the_key_file_is_missing() {
let dir = tempfile::tempdir().unwrap();
cli()
.env("SYSKNIFE_SOCKET", fake_socket(&dir))
.env("XDG_DATA_HOME", dir.path())
.env("HOME", dir.path())
.args(["audit", "verify", "--pubkey"])
.arg(dir.path().join("no-such-key.pub"))
.assert()
.code(2);
}
#[test]
fn completions_subcommand_emits_a_shell_script() {
cli()
.arg("completions")
.arg("bash")
.assert()
.success()
.stdout(predicate::str::contains("_sysknife").or(predicate::str::starts_with("#")));
}
#[test]
fn timeout_flag_bounds_a_daemon_that_accepts_but_never_replies() {
use std::os::unix::net::UnixListener;
let dir = tempfile::tempdir().unwrap();
let socket_path = dir.path().join("silent.sock");
let listener = UnixListener::bind(&socket_path).expect("bind test socket");
std::thread::spawn(move || {
let mut held = Vec::new();
while let Ok((stream, _)) = listener.accept() {
held.push(stream);
}
});
let started = std::time::Instant::now();
cli()
.env("SYSKNIFE_SOCKET", &socket_path)
.args(["--timeout", "1", "doctor"])
.assert()
.code(2)
.stderr(predicate::str::contains("timed out"));
let elapsed = started.elapsed();
assert!(
elapsed < std::time::Duration::from_secs(30),
"--timeout 1 must give up promptly against a silent daemon, took {elapsed:?}"
);
}