use std::process::Command;
use serde_json::Value;
fn cardinal(args: &[&str]) -> std::process::Output {
let mut command = Command::new(env!("CARGO_BIN_EXE_cardinal"));
command.args(args);
for key in [
"CARDINAL_MAILDIR",
"CARDINAL_VDIR",
"CARDINAL_SMTP_HOST",
"CARDINAL_SMTP_PORT",
"CARDINAL_SMTP_FROM",
"CARDINAL_SMTP_HELO",
"CARDINAL_SMTP_USERNAME",
"CARDINAL_SMTP_PASSWORD_CMD",
"CARDINAL_SMTP_TIMEOUT_SECS",
"CARDINAL_SYNC_MAIL_CMD",
"CARDINAL_SYNC_CALENDAR_CMD",
"CARDINAL_SYNC_CONTACTS_CMD",
"CARDINAL_LOG_PATH",
"CARDINAL_LOG_STDERR",
] {
command.env_remove(key);
}
command.output().expect("failed to run cardinal")
}
#[test]
fn parse_subcommand_prints_parsed_command() {
let output = cardinal(&["parse", ":list inboxes"]);
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("List("));
assert!(stdout.contains("Inboxes"));
}
#[test]
fn parse_subcommand_returns_exit_code_2_for_invalid_command() {
let output = cardinal(&["parse", ":explode"]);
assert_eq!(output.status.code(), Some(2));
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("unknown command: explode"));
}
#[test]
fn parse_subcommand_json_success_is_machine_readable() {
let output = cardinal(&["parse", "--format", "json", ":list inboxes"]);
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: Value = serde_json::from_str(&stdout).expect("valid parse json");
assert_eq!(parsed["ok"], Value::Bool(true));
assert_eq!(parsed["command"]["kind"], Value::String("list".to_owned()));
assert_eq!(
parsed["command"]["target"],
Value::String("inboxes".to_owned())
);
assert_eq!(
parsed["isa"]["safety"],
Value::String("read-only".to_owned())
);
}
#[test]
fn parse_subcommand_json_error_is_machine_readable() {
let output = cardinal(&["parse", "--format", "json", ":explode"]);
assert_eq!(output.status.code(), Some(2));
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: Value = serde_json::from_str(&stdout).expect("valid parse error json");
assert_eq!(parsed["ok"], Value::Bool(false));
assert_eq!(
parsed["error"]["kind"],
Value::String("unknown_command".to_owned())
);
assert_eq!(
parsed["error"]["details"]["command"],
Value::String("explode".to_owned())
);
}
#[test]
fn doctor_reports_scaffold_status() {
let output = cardinal(&["doctor"]);
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("cardinal: pre-alpha scaffold"));
assert!(stdout.contains("core: command parser available"));
assert!(stdout.contains("tui: milestone 1-10 scaffold available"));
}
#[test]
fn commands_lists_examples() {
let output = cardinal(&["commands"]);
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains(":list inboxes"));
assert!(stdout.contains(":calendar today"));
}
#[test]
fn config_validate_returns_success_without_errors() {
let output = cardinal(&["config", "validate"]);
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("config:"));
}
#[test]
fn completions_subcommand_emits_script() {
let output = cardinal(&["completions", "bash"]);
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("cardinal"));
}
#[test]
fn man_subcommand_emits_manpage_content() {
let output = cardinal(&["man"]);
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains(".TH"));
assert!(stdout.to_ascii_lowercase().contains("cardinal"));
}