#![allow(clippy::unwrap_used, clippy::expect_used)]
use tempfile::TempDir;
use x0x::contacts::{Contact, ContactStore, IdentityType, MachineRecord, TrustLevel};
use x0x::identity::{AgentId, AgentKeypair, MachineId, MachineKeypair};
use x0x::trust::{TrustContext, TrustDecision, TrustEvaluator};
fn fresh_agent_id() -> AgentId {
AgentKeypair::generate().unwrap().agent_id()
}
fn fresh_machine_id() -> MachineId {
MachineKeypair::generate().unwrap().machine_id()
}
fn empty_store(dir: &TempDir) -> ContactStore {
ContactStore::new(dir.path().join("contacts.json"))
}
fn store_with(dir: &TempDir, trust: TrustLevel, id_type: IdentityType) -> (ContactStore, AgentId) {
let mut store = empty_store(dir);
let aid = fresh_agent_id();
store.add(Contact {
agent_id: aid,
trust_level: trust,
label: None,
added_at: 0,
last_seen: None,
identity_type: id_type,
machines: Vec::new(),
});
(store, aid)
}
#[test]
fn unknown_agent_yields_unknown_decision() {
let dir = TempDir::new().unwrap();
let store = empty_store(&dir);
let evaluator = TrustEvaluator::new(&store);
let decision = evaluator.evaluate(&TrustContext {
agent_id: &fresh_agent_id(),
machine_id: &fresh_machine_id(),
});
assert_eq!(decision, TrustDecision::Unknown);
}
#[test]
fn blocked_agent_yields_reject_blocked() {
let dir = TempDir::new().unwrap();
let (store, aid) = store_with(&dir, TrustLevel::Blocked, IdentityType::Anonymous);
let evaluator = TrustEvaluator::new(&store);
let decision = evaluator.evaluate(&TrustContext {
agent_id: &aid,
machine_id: &fresh_machine_id(),
});
assert_eq!(decision, TrustDecision::RejectBlocked);
}
#[test]
fn trusted_non_pinned_agent_yields_accept() {
let dir = TempDir::new().unwrap();
let (store, aid) = store_with(&dir, TrustLevel::Trusted, IdentityType::Trusted);
let evaluator = TrustEvaluator::new(&store);
let decision = evaluator.evaluate(&TrustContext {
agent_id: &aid,
machine_id: &fresh_machine_id(),
});
assert_eq!(decision, TrustDecision::Accept);
}
#[test]
fn known_agent_yields_accept_with_flag() {
let dir = TempDir::new().unwrap();
let (store, aid) = store_with(&dir, TrustLevel::Known, IdentityType::Known);
let evaluator = TrustEvaluator::new(&store);
let decision = evaluator.evaluate(&TrustContext {
agent_id: &aid,
machine_id: &fresh_machine_id(),
});
assert_eq!(decision, TrustDecision::AcceptWithFlag);
}
#[test]
fn pinned_agent_correct_machine_yields_accept() {
let dir = TempDir::new().unwrap();
let mut store = empty_store(&dir);
let aid = fresh_agent_id();
let mid = fresh_machine_id();
store.add(Contact {
agent_id: aid,
trust_level: TrustLevel::Trusted,
label: None,
added_at: 0,
last_seen: None,
identity_type: IdentityType::Anonymous,
machines: Vec::new(),
});
store.add_machine(&aid, MachineRecord::new(mid, Some("laptop".into())));
store.pin_machine(&aid, &mid);
let evaluator = TrustEvaluator::new(&store);
let decision = evaluator.evaluate(&TrustContext {
agent_id: &aid,
machine_id: &mid,
});
assert_eq!(decision, TrustDecision::Accept);
}
#[test]
fn pinned_agent_wrong_machine_yields_reject_mismatch() {
let dir = TempDir::new().unwrap();
let mut store = empty_store(&dir);
let aid = fresh_agent_id();
let mid = fresh_machine_id();
let other_mid = fresh_machine_id();
store.add(Contact {
agent_id: aid,
trust_level: TrustLevel::Trusted,
label: None,
added_at: 0,
last_seen: None,
identity_type: IdentityType::Anonymous,
machines: Vec::new(),
});
store.add_machine(&aid, MachineRecord::new(mid, None));
store.pin_machine(&aid, &mid);
let evaluator = TrustEvaluator::new(&store);
let decision = evaluator.evaluate(&TrustContext {
agent_id: &aid,
machine_id: &other_mid,
});
assert_eq!(decision, TrustDecision::RejectMachineMismatch);
}
#[test]
fn blocked_takes_priority_over_machine_mismatch() {
let dir = TempDir::new().unwrap();
let mut store = empty_store(&dir);
let aid = fresh_agent_id();
let mid = fresh_machine_id();
let other_mid = fresh_machine_id();
store.add(Contact {
agent_id: aid,
trust_level: TrustLevel::Blocked,
label: None,
added_at: 0,
last_seen: None,
identity_type: IdentityType::Anonymous,
machines: Vec::new(),
});
store.add_machine(&aid, MachineRecord::new(mid, None));
store.pin_machine(&aid, &mid);
let evaluator = TrustEvaluator::new(&store);
let decision = evaluator.evaluate(&TrustContext {
agent_id: &aid,
machine_id: &other_mid, });
assert_eq!(
decision,
TrustDecision::RejectBlocked,
"blocked must take priority over machine mismatch"
);
}
#[test]
fn unpin_machine_removes_constraint() {
let dir = TempDir::new().unwrap();
let mut store = empty_store(&dir);
let aid = fresh_agent_id();
let mid = fresh_machine_id();
store.add(Contact {
agent_id: aid,
trust_level: TrustLevel::Trusted,
label: None,
added_at: 0,
last_seen: None,
identity_type: IdentityType::Anonymous,
machines: Vec::new(),
});
store.add_machine(&aid, MachineRecord::new(mid, None));
store.pin_machine(&aid, &mid);
store.unpin_machine(&aid, &mid);
let evaluator = TrustEvaluator::new(&store);
let different_mid = fresh_machine_id();
let decision = evaluator.evaluate(&TrustContext {
agent_id: &aid,
machine_id: &different_mid,
});
assert_eq!(
decision,
TrustDecision::Accept,
"after unpinning, trust level should govern"
);
}
#[test]
fn multiple_machines_only_one_pinned() {
let dir = TempDir::new().unwrap();
let mut store = empty_store(&dir);
let aid = fresh_agent_id();
let mid1 = fresh_machine_id();
let mid2 = fresh_machine_id();
store.add(Contact {
agent_id: aid,
trust_level: TrustLevel::Trusted,
label: None,
added_at: 0,
last_seen: None,
identity_type: IdentityType::Anonymous,
machines: Vec::new(),
});
store.add_machine(&aid, MachineRecord::new(mid1, Some("desktop".into())));
store.add_machine(&aid, MachineRecord::new(mid2, Some("laptop".into())));
store.pin_machine(&aid, &mid1);
let evaluator = TrustEvaluator::new(&store);
assert_eq!(
evaluator.evaluate(&TrustContext {
agent_id: &aid,
machine_id: &mid1,
}),
TrustDecision::Accept
);
assert_eq!(
evaluator.evaluate(&TrustContext {
agent_id: &aid,
machine_id: &mid2,
}),
TrustDecision::RejectMachineMismatch
);
}
#[test]
fn set_trust_updates_existing_contact() {
let dir = TempDir::new().unwrap();
let mut store = empty_store(&dir);
let aid = fresh_agent_id();
store.add(Contact {
agent_id: aid,
trust_level: TrustLevel::Unknown,
label: None,
added_at: 0,
last_seen: None,
identity_type: IdentityType::Anonymous,
machines: Vec::new(),
});
{
let evaluator = TrustEvaluator::new(&store);
assert_eq!(
evaluator.evaluate(&TrustContext {
agent_id: &aid,
machine_id: &fresh_machine_id(),
}),
TrustDecision::Unknown
);
}
store.set_trust(&aid, TrustLevel::Trusted);
{
let evaluator = TrustEvaluator::new(&store);
assert_eq!(
evaluator.evaluate(&TrustContext {
agent_id: &aid,
machine_id: &fresh_machine_id(),
}),
TrustDecision::Accept
);
}
store.set_trust(&aid, TrustLevel::Blocked);
{
let evaluator = TrustEvaluator::new(&store);
assert_eq!(
evaluator.evaluate(&TrustContext {
agent_id: &aid,
machine_id: &fresh_machine_id(),
}),
TrustDecision::RejectBlocked
);
}
}
#[test]
fn machines_accessor_returns_correct_records() {
let dir = TempDir::new().unwrap();
let mut store = empty_store(&dir);
let aid = fresh_agent_id();
let mid1 = fresh_machine_id();
let mid2 = fresh_machine_id();
store.add(Contact {
agent_id: aid,
trust_level: TrustLevel::Known,
label: None,
added_at: 0,
last_seen: None,
identity_type: IdentityType::Anonymous,
machines: Vec::new(),
});
store.add_machine(&aid, MachineRecord::new(mid1, None));
store.add_machine(&aid, MachineRecord::new(mid2, Some("server".into())));
let machines = store.machines(&aid);
assert_eq!(machines.len(), 2);
assert!(machines.iter().any(|m| m.machine_id == mid1));
assert!(machines.iter().any(|m| m.machine_id == mid2));
}
#[test]
fn remove_machine_removes_correct_record() {
let dir = TempDir::new().unwrap();
let mut store = empty_store(&dir);
let aid = fresh_agent_id();
let mid1 = fresh_machine_id();
let mid2 = fresh_machine_id();
store.add(Contact {
agent_id: aid,
trust_level: TrustLevel::Trusted,
label: None,
added_at: 0,
last_seen: None,
identity_type: IdentityType::Anonymous,
machines: Vec::new(),
});
store.add_machine(&aid, MachineRecord::new(mid1, None));
store.add_machine(&aid, MachineRecord::new(mid2, None));
assert_eq!(store.machines(&aid).len(), 2);
store.remove_machine(&aid, &mid1);
assert_eq!(store.machines(&aid).len(), 1);
assert_eq!(store.machines(&aid)[0].machine_id, mid2);
}
#[test]
fn remove_only_pinned_machine_removes_constraint() {
let dir = TempDir::new().unwrap();
let mut store = empty_store(&dir);
let aid = fresh_agent_id();
let mid = fresh_machine_id();
store.add(Contact {
agent_id: aid,
trust_level: TrustLevel::Trusted,
label: None,
added_at: 0,
last_seen: None,
identity_type: IdentityType::Anonymous,
machines: Vec::new(),
});
store.add_machine(&aid, MachineRecord::new(mid, None));
store.pin_machine(&aid, &mid);
assert!(store.remove_machine(&aid, &mid));
assert!(store.machines(&aid).is_empty());
assert_eq!(
store.get(&aid).map(|contact| contact.identity_type),
Some(IdentityType::Known)
);
let evaluator = TrustEvaluator::new(&store);
assert_eq!(
evaluator.evaluate(&TrustContext {
agent_id: &aid,
machine_id: &fresh_machine_id(),
}),
TrustDecision::Accept
);
}
#[test]
fn remove_one_pinned_machine_keeps_remaining_pin() {
let dir = TempDir::new().unwrap();
let mut store = empty_store(&dir);
let aid = fresh_agent_id();
let mid1 = fresh_machine_id();
let mid2 = fresh_machine_id();
store.add(Contact {
agent_id: aid,
trust_level: TrustLevel::Trusted,
label: None,
added_at: 0,
last_seen: None,
identity_type: IdentityType::Anonymous,
machines: Vec::new(),
});
store.add_machine(&aid, MachineRecord::new(mid1, Some("desktop".into())));
store.add_machine(&aid, MachineRecord::new(mid2, Some("laptop".into())));
store.pin_machine(&aid, &mid1);
store.pin_machine(&aid, &mid2);
assert!(store.remove_machine(&aid, &mid1));
let machines = store.machines(&aid);
assert_eq!(machines.len(), 1);
assert_eq!(machines[0].machine_id, mid2);
assert!(machines[0].pinned);
assert_eq!(
store.get(&aid).map(|contact| contact.identity_type),
Some(IdentityType::Pinned)
);
let evaluator = TrustEvaluator::new(&store);
assert_eq!(
evaluator.evaluate(&TrustContext {
agent_id: &aid,
machine_id: &mid2,
}),
TrustDecision::Accept
);
assert_eq!(
evaluator.evaluate(&TrustContext {
agent_id: &aid,
machine_id: &mid1,
}),
TrustDecision::RejectMachineMismatch
);
}
#[test]
fn contact_store_in_memory_state_round_trip() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("contacts.json");
let aid = fresh_agent_id();
let mid = fresh_machine_id();
let mut store = ContactStore::new(path);
store.add(Contact {
agent_id: aid,
trust_level: TrustLevel::Trusted,
label: Some("Alice".into()),
added_at: 1_000,
last_seen: Some(2_000),
identity_type: IdentityType::Anonymous,
machines: Vec::new(),
});
store.add_machine(&aid, MachineRecord::new(mid, Some("workstation".into())));
store.pin_machine(&aid, &mid);
let contact = store.get(&aid).expect("contact should be present");
assert_eq!(contact.trust_level, TrustLevel::Trusted);
assert_eq!(contact.label.as_deref(), Some("Alice"));
let machines = store.machines(&aid);
assert_eq!(machines.len(), 1);
assert_eq!(machines[0].machine_id, mid);
assert!(machines[0].pinned, "machine should be pinned");
assert_eq!(machines[0].label.as_deref(), Some("workstation"));
}