use std::path::PathBuf;
use std::process::Command;
fn bin() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_cardanowall"))
}
fn cmd(config_path: &std::path::Path, home: &std::path::Path) -> Command {
let mut c = Command::new(bin());
c.env("CARDANOWALL_CONFIG_PATH", config_path)
.env("HOME", home)
.env_remove("CARDANOWALL_BASE_URL")
.env_remove("CARDANOWALL_API_KEY")
.env_remove("CARDANOWALL_SEED")
.env_remove("CARDANOWALL_RECIPIENT_KEY")
.env_remove("NO_COLOR")
.env_remove("CLICOLOR_FORCE");
c
}
#[test]
fn gateway_profiles_round_trip_with_0600_and_preserved_fields() {
let dir = tempfile::tempdir().unwrap();
let config = dir.path().join("config.toml");
let home = dir.path().join("home");
std::fs::create_dir_all(&home).unwrap();
std::fs::write(
&config,
"cardano_gateway = \"https://koios.example/api/v1\"\nconfirmation_depth_threshold = 9\n",
)
.unwrap();
let out = cmd(&config, &home)
.args(["gateway", "add", "prod", "--base-url", "https://gw.example"])
.arg("--api-key-stdin")
.stdin(piped("super-secret-key\n"))
.output()
.unwrap();
assert!(out.status.success(), "gateway add failed: {out:?}");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mode = std::fs::metadata(&config).unwrap().permissions().mode() & 0o777;
assert_eq!(mode, 0o600, "config.toml must be written 0600");
}
let body = std::fs::read_to_string(&config).unwrap();
assert!(
body.contains("cardano_gateway") && body.contains("koios.example"),
"existing cardano_gateway must round-trip; got:\n{body}"
);
assert!(body.contains("confirmation_depth_threshold"));
assert!(body.contains("super-secret-key"));
let list = cmd(&config, &home)
.args(["gateway", "list", "--json"])
.output()
.unwrap();
assert!(list.status.success());
let v: serde_json::Value = serde_json::from_slice(&list.stdout).unwrap();
let prof = &v["gateways"][0];
assert_eq!(prof["name"], "prod");
assert_eq!(prof["base_url"], "https://gw.example");
assert_eq!(prof["api_key"], "********", "list must mask the API key");
assert_eq!(prof["has_api_key"], true);
assert_eq!(prof["is_default"], true);
let add2 = cmd(&config, &home)
.args([
"gateway",
"add",
"staging",
"--base-url",
"https://stg.example",
])
.arg("--api-key-stdin")
.stdin(piped("")) .output()
.unwrap();
assert!(add2.status.success());
let use_out = cmd(&config, &home)
.args(["gateway", "use", "staging"])
.output()
.unwrap();
assert!(use_out.status.success());
let show = cmd(&config, &home)
.args(["gateway", "show", "staging", "--json"])
.output()
.unwrap();
let sv: serde_json::Value = serde_json::from_slice(&show.stdout).unwrap();
assert_eq!(sv["is_default"], true);
assert_eq!(sv["has_api_key"], false);
let rm = cmd(&config, &home)
.args(["gateway", "remove", "staging"])
.output()
.unwrap();
assert!(rm.status.success());
let after = std::fs::read_to_string(&config).unwrap();
assert!(
!after.contains("staging"),
"removed profile must be gone; got:\n{after}"
);
}
#[test]
fn unknown_gateway_profile_exits_4() {
let dir = tempfile::tempdir().unwrap();
let config = dir.path().join("config.toml");
let home = dir.path().join("home");
std::fs::create_dir_all(&home).unwrap();
let out = cmd(&config, &home)
.args(["gateway", "use", "nope"])
.output()
.unwrap();
assert_eq!(out.status.code(), Some(4));
}
#[test]
fn json_error_object_on_failing_command() {
let dir = tempfile::tempdir().unwrap();
let config = dir.path().join("config.toml");
let home = dir.path().join("home");
std::fs::create_dir_all(&home).unwrap();
let out = cmd(&config, &home)
.args(["submit", "--json"])
.output()
.unwrap();
assert_eq!(out.status.code(), Some(4));
let stderr = String::from_utf8(out.stderr).unwrap();
let v: serde_json::Value =
serde_json::from_str(stderr.trim()).expect("stderr must be a JSON error object");
assert_eq!(v["error"]["code"], 4);
assert_eq!(v["error"]["command"], "submit");
assert!(v["error"]["message"].as_str().unwrap().contains("submit"));
assert!(out.stdout.is_empty());
}
#[test]
fn global_json_flag_also_triggers_structured_error() {
let dir = tempfile::tempdir().unwrap();
let config = dir.path().join("config.toml");
let home = dir.path().join("home");
std::fs::create_dir_all(&home).unwrap();
let out = cmd(&config, &home)
.args(["--json", "verify", "not-a-hex"])
.output()
.unwrap();
assert_eq!(out.status.code(), Some(4));
let stderr = String::from_utf8(out.stderr).unwrap();
let v: serde_json::Value = serde_json::from_str(stderr.trim()).unwrap();
assert_eq!(v["error"]["command"], "verify");
}
#[test]
fn completion_emits_nonempty_script_for_each_shell() {
let dir = tempfile::tempdir().unwrap();
let config = dir.path().join("config.toml");
let home = dir.path().join("home");
std::fs::create_dir_all(&home).unwrap();
for shell in ["bash", "zsh", "fish", "powershell"] {
let out = cmd(&config, &home)
.args(["completion", shell])
.output()
.unwrap();
assert!(out.status.success(), "completion {shell} failed");
let script = String::from_utf8(out.stdout).unwrap();
assert!(!script.is_empty(), "completion {shell} was empty");
assert!(
script.contains("cardanowall"),
"completion {shell} must reference the binary name"
);
}
}
#[test]
fn seed_from_stdin_drives_identity_json() {
let dir = tempfile::tempdir().unwrap();
let config = dir.path().join("config.toml");
let home = dir.path().join("home");
std::fs::create_dir_all(&home).unwrap();
let seed = "ab".repeat(32);
let out = cmd(&config, &home)
.args(["identity", "--seed-stdin", "--json"])
.stdin(piped(&format!("{seed}\n")))
.output()
.unwrap();
assert!(
out.status.success(),
"identity --seed-stdin failed: {out:?}"
);
let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
assert!(v["fingerprint"].as_str().unwrap().contains('-'));
assert!(v["age_recipient"].as_str().unwrap().starts_with("age1"));
}
#[test]
fn seed_from_env_drives_identity() {
let dir = tempfile::tempdir().unwrap();
let config = dir.path().join("config.toml");
let home = dir.path().join("home");
std::fs::create_dir_all(&home).unwrap();
let out = cmd(&config, &home)
.args(["identity", "--json"])
.env("CARDANOWALL_SEED", "cd".repeat(32))
.output()
.unwrap();
assert!(out.status.success(), "identity via env failed: {out:?}");
}
#[test]
fn cardano_gateway_flag_and_alias_both_parse() {
let dir = tempfile::tempdir().unwrap();
let config = dir.path().join("config.toml");
let home = dir.path().join("home");
std::fs::create_dir_all(&home).unwrap();
let tx = "0".repeat(64);
let new = cmd(&config, &home)
.args(["verify", &tx, "--cardano-gateway", "not-a-url"])
.output()
.unwrap();
assert_eq!(new.status.code(), Some(4));
let alias = cmd(&config, &home)
.args(["verify", &tx, "--gateway", "not-a-url"])
.output()
.unwrap();
assert_eq!(alias.status.code(), Some(4));
}
#[test]
fn no_color_help_runs() {
let dir = tempfile::tempdir().unwrap();
let config = dir.path().join("config.toml");
let home = dir.path().join("home");
std::fs::create_dir_all(&home).unwrap();
let out = cmd(&config, &home)
.args(["--help"])
.env("NO_COLOR", "1")
.output()
.unwrap();
assert!(out.status.success());
}
fn piped(content: &str) -> std::process::Stdio {
use std::io::Write;
let mut f = tempfile::tempfile().unwrap();
f.write_all(content.as_bytes()).unwrap();
use std::io::{Seek, SeekFrom};
f.seek(SeekFrom::Start(0)).unwrap();
std::process::Stdio::from(f)
}