use assert_cmd::Command;
use serde_json::Value;
fn ign_with_isolated_config(args: &[&str]) -> std::process::Output {
let cfg = tempfile::NamedTempFile::new().expect("tempfile");
Command::cargo_bin("ign")
.expect("binary 'ign' not found")
.env("IGNITION_CLI_CONFIG", cfg.path())
.args(args)
.output()
.expect("spawn ign")
}
fn stdout_for_golden(out: &std::process::Output) -> &str {
let stdout = std::str::from_utf8(&out.stdout).expect("utf-8 stdout");
stdout.strip_suffix('\n').unwrap_or(stdout)
}
#[test]
fn version_json_envelope_shape() {
let out = ign_with_isolated_config(&["version", "--json"]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
snapbox::Assert::new().action_env("SNAPSHOTS").eq(
stdout_for_golden(&out),
snapbox::str![[r#"
{
"ok": true,
"profile": null,
"data": {
"cli_version": "[..]"
}
}
"#]],
);
}
#[test]
fn version_compact_one_line() {
let out = ign_with_isolated_config(&["version", "--compact"]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
!stdout.trim_end().contains('\n'),
"compact output must be a single line: {stdout}"
);
let value: Value = serde_json::from_str(&stdout).expect("compact output parses as JSON");
let mut keys: Vec<&str> = value
.as_object()
.expect("envelope is a JSON object")
.keys()
.map(String::as_str)
.collect();
keys.sort_unstable();
assert_eq!(keys, ["data", "ok", "profile"], "exact top-level field set");
assert_eq!(value["ok"], Value::Bool(true));
assert_eq!(value["profile"], Value::Null);
assert_eq!(
value["data"]["cli_version"],
Value::String(env!("CARGO_PKG_VERSION").to_string())
);
}
#[test]
fn version_compact_implies_json() {
let out = ign_with_isolated_config(&["version", "--compact"]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
snapbox::Assert::new().action_env("SNAPSHOTS").eq(
stdout_for_golden(&out),
snapbox::str![r#"{"ok":true,"profile":null,"data":{"cli_version":"[..]"}}"#],
);
}
#[test]
fn usage_error_exit_2_from_clap() {
let out = ign_with_isolated_config(&["--bogus"]);
assert_eq!(out.status.code(), Some(2));
assert!(out.stdout.is_empty(), "usage errors never touch stdout");
assert!(!out.stderr.is_empty(), "clap renders the usage error");
}
#[test]
fn human_mode_is_not_json() {
let out = ign_with_isolated_config(&["version"]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
serde_json::from_str::<Value>(stdout.trim_end()).is_err(),
"human mode must not parse as JSON: {stdout}"
);
}
#[test]
fn version_json_envelope_with_config() {
let dir = tempfile::tempdir().expect("tempdir");
let config = dir.path().join("config.toml");
std::fs::write(
&config,
r#"
active = "dev"
[profiles.dev]
url = "http://127.0.0.1:1/"
"#,
)
.expect("write config");
let out = Command::cargo_bin("ign")
.expect("binary 'ign' not found")
.env("IGNITION_CLI_CONFIG", &config)
.args(["version", "--json"])
.output()
.expect("spawn ign");
assert!(
out.status.success(),
"unreachable gateway must exit 0 (LOCKED); stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
snapbox::Assert::new().action_env("SNAPSHOTS").eq(
stdout_for_golden(&out),
snapbox::str![[r#"
{
"ok": true,
"profile": "dev",
"data": {
"cli_version": "[..]",
"warnings": [
"gateway unreachable: http://127.0.0.1:1/data/api/v1/gateway-info"
]
}
}
"#]],
);
}