//! End-to-end integration tests for the typed-item core. use relicario_core::{ crypto::KdfParams, derive_master_key, encrypt_item, decrypt_item, encrypt_manifest, decrypt_manifest, encrypt_settings, decrypt_settings, Field, FieldValue, Item, ItemCore, Manifest, Section, VaultSettings, }; use relicario_core::item_types::{LoginCore, SecureNoteCore}; use url::Url; use zeroize::Zeroizing; fn fast_params() -> KdfParams { KdfParams { argon2_m: 256, argon2_t: 1, argon2_p: 1 } } #[test] fn full_workflow_login_and_note() { let salt = [0xAAu8; 32]; let img = [0xBBu8; 32]; let key = derive_master_key(b"correct horse battery staple", &img, &salt, &fast_params()).unwrap(); let mut manifest = Manifest::new(); let settings = VaultSettings::default(); // Add a Login let login = Item::new("GitHub".into(), ItemCore::Login(LoginCore { username: Some("alice".into()), password: Some(Zeroizing::new("hunter2".into())), url: Some(Url::parse("https://github.com").unwrap()), totp: None, })); manifest.upsert(&login); let login_blob = encrypt_item(&login, &key).unwrap(); // Add a SecureNote let note = Item::new("recovery".into(), ItemCore::SecureNote(SecureNoteCore { body: Zeroizing::new("recovery codes go here".into()), })); manifest.upsert(¬e); let note_blob = encrypt_item(¬e, &key).unwrap(); // Encrypt manifest + settings let manifest_blob = encrypt_manifest(&manifest, &key).unwrap(); let settings_blob = encrypt_settings(&settings, &key).unwrap(); // Decrypt + verify let m = decrypt_manifest(&manifest_blob, &key).unwrap(); assert_eq!(m.items.len(), 2); let l: Item = decrypt_item(&login_blob, &key).unwrap(); let n: Item = decrypt_item(¬e_blob, &key).unwrap(); let s: VaultSettings = decrypt_settings(&settings_blob, &key).unwrap(); assert_eq!(l.title, "GitHub"); assert_eq!(n.title, "recovery"); assert_eq!(s.attachment_caps.per_attachment_max_bytes, 10 * 1024 * 1024); } #[test] fn two_factor_independence() { // Same passphrase, different image_secret → different keys. let salt = [0u8; 32]; let img_a = [0x01u8; 32]; let img_b = [0x02u8; 32]; let key_a = derive_master_key(b"same-passphrase", &img_a, &salt, &fast_params()).unwrap(); let key_b = derive_master_key(b"same-passphrase", &img_b, &salt, &fast_params()).unwrap(); assert_ne!(*key_a, *key_b); // Different passphrase, same image_secret → different keys. let key_c = derive_master_key(b"other-passphrase", &img_a, &salt, &fast_params()).unwrap(); assert_ne!(*key_a, *key_c); } #[test] fn field_history_persists_through_round_trip() { let salt = [0u8; 32]; let img = [0u8; 32]; let key = derive_master_key(b"x", &img, &salt, &fast_params()).unwrap(); let mut item = Item::new("x".into(), ItemCore::Login(LoginCore::default())); let f = Field::new("p".into(), FieldValue::Password(Zeroizing::new("v0".into()))); let fid = f.id.clone(); item.sections.push(Section { name: None, fields: vec![f] }); item.set_field_value(&fid, FieldValue::Password(Zeroizing::new("v1".into()))).unwrap(); item.set_field_value(&fid, FieldValue::Password(Zeroizing::new("v2".into()))).unwrap(); let blob = encrypt_item(&item, &key).unwrap(); let decoded = decrypt_item(&blob, &key).unwrap(); let hist = decoded.field_history.get(&fid).unwrap(); assert_eq!(hist.len(), 2); assert_eq!(hist[0].value.as_str(), "v0"); assert_eq!(hist[1].value.as_str(), "v1"); } #[test] fn wrong_key_fails_with_opaque_decrypt() { use relicario_core::RelicarioError; let salt = [0u8; 32]; let img = [0u8; 32]; let right = derive_master_key(b"correct", &img, &salt, &fast_params()).unwrap(); let wrong = derive_master_key(b"wrong", &img, &salt, &fast_params()).unwrap(); let item = Item::new("x".into(), ItemCore::SecureNote(SecureNoteCore::default())); let blob = encrypt_item(&item, &right).unwrap(); let err = decrypt_item(&blob, &wrong); assert!(matches!(err, Err(RelicarioError::Decrypt))); }