#![allow(clippy::unwrap_used, clippy::expect_used)]
use tempfile::TempDir;
use x0x::identity::{AgentCertificate, AgentKeypair, UserKeypair};
use x0x::{storage, Agent};
#[tokio::test]
async fn test_agent_creation_workflow() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let temp_path = temp_dir.path();
let agent1 = Agent::builder()
.with_machine_key(temp_path.join("machine1.key"))
.with_agent_key_path(temp_path.join("agent1.key"))
.build()
.await
.expect("Failed to create agent1");
let machine_id1 = agent1.machine_id();
let agent_id1 = agent1.agent_id();
assert_ne!(
machine_id1.as_bytes(),
&[0u8; 32],
"Machine ID should not be zero"
);
assert_ne!(
agent_id1.as_bytes(),
&[0u8; 32],
"Agent ID should not be zero"
);
let agent2 = Agent::builder()
.with_machine_key(temp_path.join("machine1.key"))
.with_agent_key_path(temp_path.join("agent2.key"))
.build()
.await
.expect("Failed to create agent2");
let machine_id2 = agent2.machine_id();
let agent_id2 = agent2.agent_id();
assert_eq!(
machine_id1, machine_id2,
"Machine ID should be consistent for same machine key"
);
assert_ne!(
agent_id1, agent_id2,
"Agent ID should be different for different agent keys"
);
assert_ne!(
machine_id2.as_bytes(),
&[0u8; 32],
"Machine ID should not be zero"
);
assert_ne!(
agent_id2.as_bytes(),
&[0u8; 32],
"Agent ID should not be zero"
);
}
#[tokio::test]
async fn test_portable_agent_identity() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let temp_path = temp_dir.path();
let original_agent = Agent::builder()
.with_machine_key(temp_path.join("original_machine.key"))
.with_agent_key_path(temp_path.join("original_agent.key"))
.build()
.await
.expect("Failed to create original agent");
let original_agent_id = original_agent.agent_id();
let original_machine_id = original_agent.machine_id();
let agent_key_path = temp_path.join("exported_agent.key");
storage::save_agent_keypair(original_agent.identity().agent_keypair(), &agent_key_path)
.await
.expect("Failed to save agent keypair");
let imported_keypair = storage::load_agent_keypair(&agent_key_path)
.await
.expect("Failed to load agent keypair");
let migrated_agent = Agent::builder()
.with_machine_key(temp_path.join("migrated_machine.key"))
.with_agent_key(imported_keypair)
.build()
.await
.expect("Failed to create migrated agent");
assert_eq!(
original_agent_id,
migrated_agent.agent_id(),
"Agent ID should be portable"
);
assert_ne!(
original_machine_id,
migrated_agent.machine_id(),
"Machine ID should be different for different machines"
);
assert!(!original_agent
.identity()
.machine_keypair()
.public_key()
.as_bytes()
.is_empty());
assert!(!migrated_agent
.identity()
.machine_keypair()
.public_key()
.as_bytes()
.is_empty());
}
#[tokio::test]
async fn test_agent_key_persistence() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let temp_path = temp_dir.path();
let agent_key_path = temp_path.join("persistent_agent.key");
let machine_key_path = temp_path.join("machine.key");
let agent1 = Agent::builder()
.with_machine_key(machine_key_path.clone())
.with_agent_key_path(agent_key_path.clone())
.build()
.await
.expect("Failed to create agent1");
let agent_id1 = agent1.agent_id();
assert!(
agent_key_path.exists(),
"Agent key file should be created on first build"
);
let agent2 = Agent::builder()
.with_machine_key(machine_key_path)
.with_agent_key_path(agent_key_path)
.build()
.await
.expect("Failed to create agent2");
let agent_id2 = agent2.agent_id();
assert_eq!(
agent_id1, agent_id2,
"Agent ID should persist across builds with same key path"
);
}
#[tokio::test]
async fn test_agent_keypair_storage_roundtrip() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let path = temp_dir.path().join("test_agent.key");
let original = x0x::identity::AgentKeypair::generate().expect("Failed to generate keypair");
let original_id = x0x::identity::AgentId::from_public_key(original.public_key());
storage::save_agent_keypair_to(&original, &path)
.await
.expect("Failed to save agent keypair");
let loaded = storage::load_agent_keypair_from(&path)
.await
.expect("Failed to load agent keypair");
let loaded_id = x0x::identity::AgentId::from_public_key(loaded.public_key());
assert_eq!(original_id, loaded_id, "Agent ID should survive round-trip");
}
#[tokio::test]
async fn test_three_layer_identity_workflow() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let temp_path = temp_dir.path();
let user_kp = UserKeypair::generate().expect("Failed to generate user keypair");
let expected_user_id = user_kp.user_id();
storage::save_user_keypair_to(&user_kp, temp_path.join("user.key"))
.await
.expect("Failed to save user keypair");
let agent = Agent::builder()
.with_machine_key(temp_path.join("machine.key"))
.with_agent_key_path(temp_path.join("agent.key"))
.with_user_key_path(temp_path.join("user.key"))
.build()
.await
.expect("Failed to create agent with user key");
let machine_id = agent.machine_id();
let agent_id = agent.agent_id();
let user_id = agent.user_id().expect("User ID should be present");
assert_ne!(machine_id.as_bytes(), &[0u8; 32]);
assert_ne!(agent_id.as_bytes(), &[0u8; 32]);
assert_ne!(user_id.as_bytes(), &[0u8; 32]);
assert_ne!(
machine_id.as_bytes(),
agent_id.as_bytes(),
"Machine and Agent IDs must differ"
);
assert_ne!(
agent_id.as_bytes(),
user_id.as_bytes(),
"Agent and User IDs must differ"
);
assert_ne!(
machine_id.as_bytes(),
user_id.as_bytes(),
"Machine and User IDs must differ"
);
assert_eq!(user_id, expected_user_id);
let cert = agent
.agent_certificate()
.expect("Certificate should be present");
cert.verify().expect("Certificate should be valid");
}
#[tokio::test]
async fn test_agent_certificate_issue_and_verify() {
let user_kp = UserKeypair::generate().expect("Failed to generate user keypair");
let agent_kp = AgentKeypair::generate().expect("Failed to generate agent keypair");
let cert = AgentCertificate::issue(&user_kp, &agent_kp).expect("Failed to issue certificate");
cert.verify()
.expect("Certificate verification should succeed");
assert_eq!(
cert.user_id().expect("user_id"),
user_kp.user_id(),
"Certificate user_id should match keypair"
);
assert_eq!(
cert.agent_id().expect("agent_id"),
agent_kp.agent_id(),
"Certificate agent_id should match keypair"
);
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
assert!(
cert.issued_at() <= now && cert.issued_at() > now - 60,
"Certificate timestamp should be recent"
);
}
#[tokio::test]
async fn test_agent_certificate_wrong_key_fails() {
let user_a = UserKeypair::generate().expect("Failed to generate user A");
let user_b = UserKeypair::generate().expect("Failed to generate user B");
let agent_kp = AgentKeypair::generate().expect("Failed to generate agent keypair");
let cert = AgentCertificate::issue(&user_a, &agent_kp).expect("Failed to issue certificate");
cert.verify().expect("Valid cert should verify");
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let temp_path = temp_dir.path();
storage::save_user_keypair_to(&user_b, temp_path.join("user_b.key"))
.await
.expect("Failed to save user B keypair");
let agent = Agent::builder()
.with_machine_key(temp_path.join("machine.key"))
.with_agent_key_path(temp_path.join("agent.key"))
.with_user_key_path(temp_path.join("user_b.key"))
.build()
.await
.expect("Failed to create agent");
let agent_cert = agent.agent_certificate().expect("Should have cert");
agent_cert.verify().expect("New cert should verify");
assert_eq!(
agent_cert.user_id().expect("user_id"),
user_b.user_id(),
"Cert should be for user B"
);
}
#[tokio::test]
async fn test_user_key_persistence() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let path = temp_dir.path().join("test_user.key");
let original = UserKeypair::generate().expect("Failed to generate user keypair");
let original_id = original.user_id();
storage::save_user_keypair_to(&original, &path)
.await
.expect("Failed to save user keypair");
let loaded = storage::load_user_keypair_from(&path)
.await
.expect("Failed to load user keypair");
let loaded_id = loaded.user_id();
assert_eq!(
original_id, loaded_id,
"User ID should survive save/load round-trip"
);
}
#[tokio::test]
async fn test_agent_without_user_key_is_two_layer() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let temp_path = temp_dir.path();
let agent = Agent::builder()
.with_machine_key(temp_path.join("machine.key"))
.with_agent_key_path(temp_path.join("agent.key"))
.build()
.await
.expect("Failed to create agent");
assert!(agent.user_id().is_none(), "User ID should be None");
assert!(
agent.agent_certificate().is_none(),
"Certificate should be None"
);
assert_ne!(agent.machine_id().as_bytes(), &[0u8; 32]);
assert_ne!(agent.agent_id().as_bytes(), &[0u8; 32]);
}
#[tokio::test]
async fn test_user_key_path_nonexistent_does_not_generate() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let temp_path = temp_dir.path();
let agent = Agent::builder()
.with_machine_key(temp_path.join("machine.key"))
.with_agent_key_path(temp_path.join("agent.key"))
.with_user_key_path(temp_path.join("nonexistent_user.key"))
.build()
.await
.expect("Failed to create agent");
assert!(
agent.user_id().is_none(),
"User ID should be None when key file doesn't exist"
);
assert!(
!temp_path.join("nonexistent_user.key").exists(),
"User key file should not be auto-generated"
);
}