use super::*;
#[test]
fn a_signing_handoff_round_trips_to_the_same_key_on_one_line() {
let id = AgentIdentity::generate();
let line = serde_json::to_string(&SigningHandoff {
agent: "mur".into(),
key_version: 3,
secret: id.secret_bytes_for_handoff(),
})
.expect("serialize handoff");
assert!(!line.contains('\n'), "must survive read_line: {line}");
let back: SigningHandoff = serde_json::from_str(&line).expect("parse handoff");
assert_eq!(back.agent, "mur");
assert_eq!(back.key_version, 3);
assert_eq!(
AgentIdentity::from_secret_bytes(&back.secret).verifying_key_bytes(),
id.verifying_key_bytes(),
"the child must sign as the same agent, not a lookalike"
);
}
#[cfg(unix)]
#[test]
fn an_unreadable_key_is_denied_not_notfound() {
use std::os::unix::fs::PermissionsExt;
let dir = tempfile::tempdir().unwrap();
AgentIdentity::generate().save(dir.path()).unwrap();
let key = dir.path().join("identity.key");
assert!(AgentIdentity::load(dir.path()).is_ok());
std::fs::set_permissions(&key, std::fs::Permissions::from_mode(0o000)).unwrap();
let unreadable = AgentIdentity::load(dir.path()).unwrap_err();
std::fs::set_permissions(&key, std::fs::Permissions::from_mode(0o600)).unwrap();
assert!(
matches!(unreadable, IdentityError::Denied(_)),
"an unreadable key must be Denied, got {unreadable:?}"
);
std::fs::set_permissions(dir.path(), std::fs::Permissions::from_mode(0o000)).unwrap();
let unstattable = AgentIdentity::load(dir.path()).unwrap_err();
let exists_lies = !key.exists();
std::fs::set_permissions(dir.path(), std::fs::Permissions::from_mode(0o700)).unwrap();
assert!(
exists_lies,
"precondition: Path::exists() must be answering false here, or this \
case is not reproducing a sandbox deny"
);
assert!(
matches!(unstattable, IdentityError::Denied(_)),
"a key that cannot be STATted must be Denied, not NotFound, got {unstattable:?}"
);
}
#[test]
fn an_agent_key_moves_and_the_public_half_stays() {
let tmp = tempfile::tempdir().unwrap();
let mur = tmp.path();
let agent = mur.join("agents").join("pm");
AgentIdentity::generate().save(&agent).unwrap();
assert!(
mur.join("keys").join("pm").join("identity.key").exists(),
"private key must live under keys/"
);
assert!(
!agent.join("identity.key").exists(),
"the agents tree must hold no private key"
);
assert!(
agent.join("identity.pub").exists(),
"the public half must stay where peers read it"
);
}
#[test]
fn non_agent_identities_are_not_remapped() {
let tmp = tempfile::tempdir().unwrap();
let mur = tmp.path();
for dir in [
mur.to_path_buf(), mur.join("commander"), mur.join("publisher"), ] {
assert_eq!(
private_key_dir(&dir),
dir,
"{} must not be remapped",
dir.display()
);
AgentIdentity::generate().save(&dir).unwrap();
assert!(
dir.join("identity.key").exists(),
"{} lost its key to the remap",
dir.display()
);
}
}
#[test]
fn a_legacy_key_loads_only_after_migration() {
let tmp = tempfile::tempdir().unwrap();
let agent = tmp.path().join("agents").join("legacy");
std::fs::create_dir_all(&agent).unwrap();
let id = AgentIdentity::generate();
std::fs::write(agent.join("identity.key"), id.signing.to_bytes()).unwrap();
assert!(
matches!(
AgentIdentity::load(&agent).unwrap_err(),
IdentityError::NotFound
),
"a key in the legacy location must not be silently honoured"
);
assert!(migrate_private_key(&agent).unwrap());
assert_eq!(
AgentIdentity::load(&agent).unwrap().pubkey_text(),
id.pubkey_text()
);
}
#[test]
fn the_new_location_wins_over_a_leftover_legacy_key() {
let tmp = tempfile::tempdir().unwrap();
let mur = tmp.path();
let agent = mur.join("agents").join("dual");
let current = AgentIdentity::generate();
current.save(&agent).unwrap();
let stale = AgentIdentity::generate();
std::fs::create_dir_all(&agent).unwrap();
std::fs::write(agent.join("identity.key"), stale.signing.to_bytes()).unwrap();
let loaded = AgentIdentity::load(&agent).unwrap();
assert_eq!(
loaded.pubkey_text(),
current.pubkey_text(),
"the migrated key must win over the leftover"
);
}
#[test]
fn migration_moves_the_key_out_of_the_agents_tree() {
let tmp = tempfile::tempdir().unwrap();
let mur = tmp.path();
let agent = mur.join("agents").join("pm");
std::fs::create_dir_all(&agent).unwrap();
let id = AgentIdentity::generate();
std::fs::write(agent.join("identity.key"), id.signing.to_bytes()).unwrap();
assert!(migrate_private_key(&agent).unwrap());
assert!(!agent.join("identity.key").exists(), "key left in agents/");
assert!(mur.join("keys/pm/identity.key").exists());
assert_eq!(
AgentIdentity::load(&agent).unwrap().pubkey_text(),
id.pubkey_text(),
"the same identity must load after the move"
);
}
#[test]
fn migration_is_idempotent() {
let tmp = tempfile::tempdir().unwrap();
let agent = tmp.path().join("agents").join("pm");
AgentIdentity::generate().save(&agent).unwrap(); assert!(!migrate_private_key(&agent).unwrap());
assert!(!migrate_private_key(&agent).unwrap());
}
#[test]
fn migration_refuses_when_the_destination_differs() {
let tmp = tempfile::tempdir().unwrap();
let mur = tmp.path();
let agent = mur.join("agents").join("pm");
let migrated = AgentIdentity::generate();
migrated.save(&agent).unwrap();
let stray = AgentIdentity::generate();
std::fs::write(agent.join("identity.key"), stray.signing.to_bytes()).unwrap();
let err = migrate_private_key(&agent).unwrap_err();
assert!(matches!(err, IdentityError::Exists(_)), "got {err:?}");
assert_eq!(
std::fs::read(mur.join("keys/pm/identity.key")).unwrap(),
migrated.signing.to_bytes().to_vec(),
"the destination key was modified despite the refusal"
);
assert!(
agent.join("identity.key").exists(),
"the source was removed despite the refusal"
);
}
#[test]
fn migration_clears_an_identical_leftover() {
let tmp = tempfile::tempdir().unwrap();
let agent = tmp.path().join("agents").join("pm");
let id = AgentIdentity::generate();
id.save(&agent).unwrap();
std::fs::write(agent.join("identity.key"), id.signing.to_bytes()).unwrap();
assert!(!migrate_private_key(&agent).unwrap());
assert!(!agent.join("identity.key").exists());
}
#[test]
fn migration_skips_non_agent_identities() {
let tmp = tempfile::tempdir().unwrap();
let mur = tmp.path();
for dir in [
mur.to_path_buf(),
mur.join("commander"),
mur.join("publisher"),
] {
AgentIdentity::generate().save(&dir).unwrap();
assert!(!migrate_private_key(&dir).unwrap());
assert!(dir.join("identity.key").exists(), "{}", dir.display());
}
}
#[test]
fn save_refuses_to_overwrite_an_existing_key() {
let dir = tempfile::tempdir().unwrap();
let first = AgentIdentity::generate();
first.save(dir.path()).unwrap();
let original = std::fs::read(dir.path().join("identity.key")).unwrap();
let err = AgentIdentity::generate().save(dir.path()).unwrap_err();
assert!(
matches!(err, IdentityError::Exists(_)),
"expected Exists, got {err:?}"
);
assert_eq!(
std::fs::read(dir.path().join("identity.key")).unwrap(),
original,
"the existing key was modified despite the refusal"
);
}
#[test]
fn save_into_an_empty_directory_succeeds() {
let dir = tempfile::tempdir().unwrap();
let id = AgentIdentity::generate();
id.save(dir.path()).unwrap();
assert_eq!(
AgentIdentity::load(dir.path()).unwrap().pubkey_text(),
id.pubkey_text()
);
}
#[test]
fn a_missing_key_is_still_notfound() {
let dir = tempfile::tempdir().unwrap();
assert!(matches!(
AgentIdentity::load(dir.path()).unwrap_err(),
IdentityError::NotFound
));
}