use assert_cmd::prelude::*;
use std::path::{Path, PathBuf};
use std::process::Command;
fn net() -> Command {
Command::cargo_bin("net-mesh").expect("net-mesh binary")
}
fn keygen(dir: &Path, name: &str) -> PathBuf {
let key = dir.join(name);
net()
.args(["org", "keygen", "--out"])
.arg(&key)
.assert()
.code(0);
key
}
fn gen_identity(dir: &Path, name: &str) -> PathBuf {
let id = dir.join(name);
net()
.args(["identity", "generate", "--out"])
.arg(&id)
.assert()
.code(0);
id
}
fn toml_field(path: &Path, key: &str) -> Option<String> {
let text = std::fs::read_to_string(path).ok()?;
for line in text.lines() {
if let Some(rest) = line.trim().strip_prefix(key) {
let rest = rest.trim_start();
if let Some(rest) = rest.strip_prefix('=') {
return Some(rest.trim().trim_matches('"').to_string());
}
}
}
None
}
fn has_stage_temp(dir: &Path) -> bool {
std::fs::read_dir(dir)
.map(|rd| {
rd.filter_map(Result::ok)
.any(|e| e.file_name().to_string_lossy().contains(".stage."))
})
.unwrap_or(false)
}
#[test]
fn identity_generate_force_refuses_to_replace_the_org_root_key() {
let dir = tempfile::tempdir().expect("tempdir");
let key = keygen(dir.path(), "org.toml");
let org_id_before = toml_field(&key, "org_id_hex").expect("org key has org_id_hex");
let seed_before = toml_field(&key, "seed_hex").expect("org key has seed_hex");
let assert = net()
.args(["identity", "generate", "--force", "--out"])
.arg(&key)
.assert()
.failure();
let stderr = String::from_utf8_lossy(&assert.get_output().stderr).to_string();
assert!(
stderr.contains("org root key material"),
"the refusal must name WHAT it found, so the operator knows the \
--out drifted rather than that the command is broken; got: {stderr}"
);
assert_eq!(
toml_field(&key, "org_id_hex").as_deref(),
Some(org_id_before.as_str()),
"the org key's identity changed — it was replaced",
);
assert_eq!(
toml_field(&key, "seed_hex").as_deref(),
Some(seed_before.as_str()),
"the org root SEED changed — this is the §2 end state",
);
assert!(
toml_field(&key, "operator_id_hex").is_none(),
"an operator identity was written over the org key",
);
assert!(!has_stage_temp(dir.path()), "refusal left a staging temp");
}
#[test]
fn identity_generate_force_still_rotates_an_identity() {
let dir = tempfile::tempdir().expect("tempdir");
let id = gen_identity(dir.path(), "operator.toml");
let seed_before = toml_field(&id, "seed_hex").expect("identity has seed_hex");
net()
.args(["identity", "generate", "--force", "--out"])
.arg(&id)
.assert()
.code(0);
let seed_after = toml_field(&id, "seed_hex").expect("identity still has seed_hex");
assert_ne!(
seed_before, seed_after,
"--force over an identity must actually rotate it",
);
assert!(!has_stage_temp(dir.path()), "rotation left a staging temp");
}
#[test]
fn org_keygen_force_refuses_to_replace_an_operator_identity() {
let dir = tempfile::tempdir().expect("tempdir");
let id = gen_identity(dir.path(), "operator.toml");
let seed_before = toml_field(&id, "seed_hex").expect("identity has seed_hex");
let assert = net()
.args(["org", "keygen", "--force", "--out"])
.arg(&id)
.assert()
.failure();
let stderr = String::from_utf8_lossy(&assert.get_output().stderr).to_string();
assert!(
stderr.contains("operator identity key material"),
"the refusal must name what it found; got: {stderr}"
);
assert_eq!(
toml_field(&id, "seed_hex").as_deref(),
Some(seed_before.as_str()),
"the operator identity seed was replaced",
);
assert!(
toml_field(&id, "org_id_hex").is_none(),
"an org key was written over the operator identity",
);
}
#[test]
fn org_keygen_force_replaces_its_own_org_key() {
let dir = tempfile::tempdir().expect("tempdir");
let key = keygen(dir.path(), "org.toml");
let id_before = toml_field(&key, "org_id_hex").expect("org key has org_id_hex");
net()
.args(["org", "keygen", "--force", "--out"])
.arg(&key)
.assert()
.code(0);
let id_after = toml_field(&key, "org_id_hex").expect("org key still has org_id_hex");
assert_ne!(
id_before, id_after,
"--force over an org key must mint a NEW org",
);
assert!(!has_stage_temp(dir.path()), "replace left a staging temp");
}
#[test]
fn org_keygen_without_force_never_clobbers() {
let dir = tempfile::tempdir().expect("tempdir");
let key = keygen(dir.path(), "org.toml");
let seed_before = toml_field(&key, "seed_hex").expect("org key has seed_hex");
net()
.args(["org", "keygen", "--out"])
.arg(&key)
.assert()
.failure();
assert_eq!(
toml_field(&key, "seed_hex").as_deref(),
Some(seed_before.as_str()),
"a refused keygen still replaced the key",
);
assert!(!has_stage_temp(dir.path()), "refusal left a staging temp");
}
#[cfg(unix)]
#[test]
fn org_keygen_does_not_write_through_a_symlink() {
let dir = tempfile::tempdir().expect("tempdir");
let real = keygen(dir.path(), "real-org.toml");
let seed_before = toml_field(&real, "seed_hex").expect("org key has seed_hex");
let link = dir.path().join("link.toml");
std::os::unix::fs::symlink(&real, &link).expect("symlink");
net()
.args(["org", "keygen", "--out"])
.arg(&link)
.assert()
.failure();
assert_eq!(
toml_field(&real, "seed_hex").as_deref(),
Some(seed_before.as_str()),
"keygen wrote through the symlink and replaced the target org key",
);
}