use age_vault::{Error, Result, Role, Vault};
use std::env;
use std::fs;
use uuid::Uuid;
fn temp_db_path() -> String {
let mut dir = env::temp_dir();
let file_name = format!("test_vault_{}.ndbx", Uuid::new_v4());
dir.push(file_name);
dir.to_string_lossy().into_owned()
}
struct TestVault {
vault: Vault,
path: String,
}
impl TestVault {
fn new(password: &str) -> Result<Self> {
let path = temp_db_path();
let vault = Vault::create(&path, password)?;
Ok(Self { vault, path })
}
fn open(path: &str, password: &str) -> Result<Vault> {
Vault::open(path, password)
}
}
impl Drop for TestVault {
fn drop(&mut self) {
let _ = fs::remove_file(&self.path);
}
}
#[test]
fn create_and_open_vault_success() {
let tv = TestVault::new("strongpassword123").expect("create vault");
let _vault = TestVault::open(&tv.path, "strongpassword123").expect("open vault");
}
#[test]
fn create_vault_with_short_password_fails() {
let path = temp_db_path();
let result = Vault::create(&path, "short");
assert!(result.is_err());
let _ = fs::remove_file(&path);
}
#[test]
fn open_nonexistent_vault_fails() {
let path = temp_db_path();
let result = Vault::open(&path, "anything");
assert!(matches!(result, Err(Error::Db(_)) | Err(Error::Io(_))));
}
#[test]
fn open_vault_with_wrong_password_fails() {
let tv = TestVault::new("correct_password").unwrap();
let result = Vault::open(&tv.path, "wrong_password");
assert!(result.is_err());
}
#[test]
fn add_and_list_accounts() {
let mut tv = TestVault::new("master123").unwrap();
tv.vault.add_account("alice", Role::User).unwrap();
tv.vault.add_account("bob", Role::Admin).unwrap();
let accounts = tv.vault.list_accounts().unwrap();
assert_eq!(accounts.len(), 2);
let names: Vec<&str> = accounts.iter().map(|a| a.name.as_str()).collect();
assert!(names.contains(&"alice"));
assert!(names.contains(&"bob"));
}
#[test]
fn add_duplicate_account_fails() {
let mut tv = TestVault::new("master123").unwrap();
tv.vault.add_account("alice", Role::User).unwrap();
let result = tv.vault.add_account("alice", Role::Admin);
assert!(matches!(result, Err(Error::AccountExists(name)) if name == "alice"));
}
#[test]
fn encrypt_and_decrypt_with_account() {
let mut tv = TestVault::new("master123").unwrap();
tv.vault.add_account("alice", Role::User).unwrap();
let plaintext = b"secret data for alice";
let ciphertext = tv.vault.encrypt_for(&["alice"], plaintext).unwrap();
let decrypted = tv.vault.decrypt_with("alice", &ciphertext).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn encrypt_for_multiple_recipients() {
let mut tv = TestVault::new("master123").unwrap();
tv.vault.add_account("alice", Role::User).unwrap();
tv.vault.add_account("bob", Role::Admin).unwrap();
let plaintext = b"multi-recipient secret";
let ciphertext = tv.vault.encrypt_for(&["alice", "bob"], plaintext).unwrap();
let dec_alice = tv.vault.decrypt_with("alice", &ciphertext).unwrap();
let dec_bob = tv.vault.decrypt_with("bob", &ciphertext).unwrap();
assert_eq!(dec_alice, plaintext);
assert_eq!(dec_bob, plaintext);
}
#[test]
fn decrypt_with_wrong_account_fails() {
let mut tv = TestVault::new("master123").unwrap();
tv.vault.add_account("alice", Role::User).unwrap();
tv.vault.add_account("eve", Role::User).unwrap();
let ciphertext = tv.vault.encrypt_for(&["alice"], b"only alice").unwrap();
let result = tv.vault.decrypt_with("eve", &ciphertext);
assert!(result.is_err());
assert!(matches!(result, Err(Error::DecryptionFailed(_))));
}
#[test]
fn encrypt_for_nonexistent_account_fails() {
let tv = TestVault::new("master123").unwrap();
let result = tv.vault.encrypt_for(&["ghost"], b"data");
assert!(matches!(result, Err(Error::AccountNotFound(name)) if name == "ghost"));
}
#[test]
fn decrypt_with_nonexistent_account_fails() {
let tv = TestVault::new("master123").unwrap();
let ciphertext = vec![0u8; 10];
let result = tv.vault.decrypt_with("ghost", &ciphertext);
assert!(matches!(result, Err(Error::AccountNotFound(name)) if name == "ghost"));
}
#[test]
fn remove_account_success() {
let mut tv = TestVault::new("master123").unwrap();
tv.vault.add_account("alice", Role::User).unwrap();
tv.vault.remove_account("alice").unwrap();
let accounts = tv.vault.list_accounts().unwrap();
assert!(accounts.is_empty());
let result = tv.vault.encrypt_for(&["alice"], b"data");
assert!(result.is_err());
}
#[test]
fn remove_nonexistent_account_fails() {
let mut tv = TestVault::new("master123").unwrap();
let result = tv.vault.remove_account("ghost");
assert!(matches!(result, Err(Error::AccountNotFound(name)) if name == "ghost"));
}
#[test]
fn encrypt_with_empty_recipients_fails() {
let tv = TestVault::new("master123").unwrap();
let result = tv.vault.encrypt_for(&[], b"data");
assert!(result.is_err());
}
#[test]
fn account_has_valid_public_key_format() {
let mut tv = TestVault::new("master123").unwrap();
let acc = tv.vault.add_account("alice", Role::User).unwrap();
assert!(acc.public_key.starts_with("age1"));
assert!(!acc.public_key.is_empty());
}
#[test]
fn account_secret_key_not_stored_in_plaintext() {
let mut tv = TestVault::new("master123").unwrap();
let acc = tv.vault.add_account("alice", Role::User).unwrap();
assert!(!acc.encrypted_secret_key.is_empty());
let enc_str = String::from_utf8_lossy(&acc.encrypted_secret_key);
assert!(!enc_str.contains("AGE-SECRET-KEY-1"));
}