use predicates::str::contains;
mod common;
use common::fez_plain as fez;
#[test]
fn prints_version() {
fez()
.arg("--version")
.assert()
.success()
.stdout(contains("fez"));
}
#[test]
fn help_lists_command_groups() {
fez()
.arg("--help")
.assert()
.success()
.stdout(contains("services"))
.stdout(contains("capabilities"))
.stdout(contains("describe"));
}
#[test]
fn global_flags_present() {
fez()
.args(["services", "list", "--help"])
.assert()
.success()
.stdout(contains("--host"))
.stdout(contains("--json"));
}
#[test]
fn capabilities_lists_service_ids() {
fez()
.arg("capabilities")
.assert()
.success()
.stdout(contains("services.list"))
.stdout(contains("services.logs"));
}
#[test]
fn describe_emits_envelope_json() {
fez()
.args(["describe", "services.status", "--json"])
.assert()
.success()
.stdout(contains("\"apiVersion\":\"fez/v1\""))
.stdout(contains("ServiceStatus"));
}
#[test]
fn describe_unknown_exits_4() {
fez().args(["describe", "nope"]).assert().code(4);
}
#[test]
fn capabilities_lists_mutation_ids() {
fez()
.arg("capabilities")
.assert()
.success()
.stdout(contains("services.start"))
.stdout(contains("services.stop"))
.stdout(contains("services.restart"))
.stdout(contains("services.reload"))
.stdout(contains("services.enable"))
.stdout(contains("services.disable"));
}
#[test]
fn describe_start_is_privileged() {
fez()
.args(["describe", "services.start", "--json"])
.assert()
.success()
.stdout(contains("\"privileged\":true"))
.stdout(contains("\"output_kind\":\"ServiceMutation\""))
.stdout(contains("--dry-run"))
.stdout(contains("--force"));
}
#[test]
fn describe_enable_lists_now_flag() {
fez()
.args(["describe", "services.enable", "--json"])
.assert()
.success()
.stdout(contains("\"output_kind\":\"ServiceEnablement\""))
.stdout(contains("--now"));
}
#[test]
fn services_help_lists_mutation_verbs() {
fez()
.args(["services", "--help"])
.assert()
.success()
.stdout(contains("start"))
.stdout(contains("enable"));
}
#[test]
fn help_lists_mcp_subcommand() {
fez()
.arg("--help")
.assert()
.success()
.stdout(contains("mcp"));
}
#[test]
fn capabilities_json_emits_envelope() {
fez()
.args(["capabilities", "--json"])
.assert()
.success()
.stdout(contains("\"apiVersion\":\"fez/v1\""))
.stdout(contains("CapabilityList"))
.stdout(contains("services.list"));
}
#[test]
fn describe_human_output_includes_example() {
fez()
.args(["describe", "services.status"])
.assert()
.success()
.stdout(contains("services.status"))
.stdout(contains("examples:"))
.stdout(contains("fez services status"));
}
#[test]
fn describe_unknown_json_still_exits_4() {
fez()
.args(["describe", "nope", "--json"])
.assert()
.code(4)
.stderr(contains("unknown capability"));
}
#[test]
fn services_start_help_shows_examples_and_long() {
fez()
.args(["services", "start", "--help"])
.assert()
.success()
.stdout(contains("Examples:"))
.stdout(contains("--force"));
}
#[test]
fn guide_text_mentions_discovery_loop_and_exit_codes() {
fez()
.arg("guide")
.assert()
.success()
.stdout(contains("capabilities"))
.stdout(contains("describe"))
.stdout(contains("protected-unit"))
.stdout(contains("fez/v1"));
}
#[test]
fn guide_json_emits_agent_guide_envelope() {
fez()
.args(["guide", "--json"])
.assert()
.success()
.stdout(contains("\"apiVersion\":\"fez/v1\""))
.stdout(contains("AgentGuide"))
.stdout(contains("exitCodes"));
}
#[test]
fn describe_text_shows_long_and_all_examples() {
fez()
.args(["describe", "services.enable"])
.assert()
.success()
.stdout(contains("--now"))
.stdout(contains("boot"));
}
#[test]
fn completions_bash_emits_script() {
fez()
.args(["completions", "bash"])
.assert()
.success()
.stdout(contains("_fez"));
}
#[test]
fn man_emits_roff() {
fez()
.arg("man")
.assert()
.success()
.stdout(contains(".TH"))
.stdout(contains("fez"));
}
#[test]
fn every_capability_id_has_a_clap_path() {
let cmd = fez::cli::command();
for d in fez::capability::registry() {
let parts: Vec<&str> = d.id.split('.').collect();
let mut node = &cmd;
let mut found = true;
for p in &parts {
match node.get_subcommands().find(|c| c.get_name() == *p) {
Some(c) => node = c,
None => {
found = false;
break;
}
}
}
assert!(found, "capability {} has no clap path", d.id);
}
}
#[test]
fn describe_example_matches_help_after_help() {
let cmd = fez::cli::command();
let d = fez::capability::find("services.start").unwrap();
let services = cmd
.get_subcommands()
.find(|c| c.get_name() == "services")
.unwrap();
let start = services
.get_subcommands()
.find(|c| c.get_name() == "start")
.unwrap();
let after = start.get_after_help().unwrap().to_string();
assert!(after.contains(&d.examples[0]));
}
#[cfg(unix)]
#[test]
fn broken_pipe_does_not_panic() {
use std::io::Read;
use std::process::{Command as StdCommand, Stdio};
let exe = assert_cmd::cargo::cargo_bin("fez");
let mut child = StdCommand::new(exe)
.args(["completions", "zsh"])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn fez");
{
let mut stdout = child.stdout.take().expect("child stdout");
let mut one = [0u8; 1];
let _ = stdout.read(&mut one);
}
let output = child.wait_with_output().expect("wait for fez");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.contains("panicked"),
"fez panicked on broken pipe: {stderr}"
);
assert_ne!(
output.status.code(),
Some(101),
"fez exited 101 (Rust panic) on broken pipe"
);
}