test(core/org): full org lifecycle integration tests
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJo44YM3UbBjro2fG6NrKy
This commit is contained in:
120
crates/relicario-core/tests/org.rs
Normal file
120
crates/relicario-core/tests/org.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
use relicario_core::{
|
||||
generate_org_key, wrap_org_key, unwrap_org_key,
|
||||
encrypt_org_manifest, decrypt_org_manifest,
|
||||
OrgManifest, OrgManifestEntry, OrgMember, OrgMembers, OrgRole,
|
||||
MemberId, ItemId,
|
||||
};
|
||||
use relicario_core::item_types::ItemType;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::RngCore;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
fn make_member_keypair() -> (Zeroizing<[u8; 32]>, String) {
|
||||
let mut seed = [0u8; 32];
|
||||
OsRng.fill_bytes(&mut seed);
|
||||
let signing_key = ed25519_dalek::SigningKey::from_bytes(&seed);
|
||||
let pubkey_openssh = ssh_key::PrivateKey::from(
|
||||
ssh_key::private::Ed25519Keypair::from(&signing_key),
|
||||
)
|
||||
.public_key()
|
||||
.to_openssh()
|
||||
.expect("openssh");
|
||||
(Zeroizing::new(seed), pubkey_openssh)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn org_key_wrap_unwrap_round_trip() {
|
||||
let (seed, pubkey) = make_member_keypair();
|
||||
let org_key = generate_org_key();
|
||||
let wrapped = wrap_org_key(&org_key, &pubkey).expect("wrap");
|
||||
let unwrapped = unwrap_org_key(&wrapped, &seed).expect("unwrap");
|
||||
assert_eq!(*org_key, *unwrapped);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn revoked_member_cannot_decrypt_after_rotation() {
|
||||
// Alice and Bob both get access
|
||||
let (alice_seed, alice_pubkey) = make_member_keypair();
|
||||
let (_bob_seed, bob_pubkey) = make_member_keypair();
|
||||
|
||||
let org_key = generate_org_key();
|
||||
let _alice_wrapped = wrap_org_key(&org_key, &alice_pubkey).expect("wrap alice");
|
||||
let _bob_wrapped = wrap_org_key(&org_key, &bob_pubkey).expect("wrap bob");
|
||||
|
||||
// Rotate: new key, only Bob gets re-wrapped
|
||||
let new_org_key = generate_org_key();
|
||||
let new_bob_wrapped = wrap_org_key(&new_org_key, &bob_pubkey).expect("wrap bob new");
|
||||
|
||||
// Alice tries to use old org_key — she can still decrypt old items,
|
||||
// but new_bob_wrapped was encrypted with new_org_key, not org_key.
|
||||
// Verify: unwrapping new_bob_wrapped with Alice's seed fails.
|
||||
let result = unwrap_org_key(&new_bob_wrapped, &alice_seed);
|
||||
assert!(result.is_err(), "Alice should not be able to unwrap Bob's new key blob");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn org_manifest_filter_restricts_to_granted_collections() {
|
||||
let mut manifest = OrgManifest::new();
|
||||
for (title, collection) in &[("A", "prod"), ("B", "dev"), ("C", "prod")] {
|
||||
manifest.entries.push(OrgManifestEntry {
|
||||
id: ItemId::new(),
|
||||
r#type: ItemType::SecureNote,
|
||||
title: title.to_string(),
|
||||
tags: vec![],
|
||||
modified: 0,
|
||||
trashed_at: None,
|
||||
collection: collection.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let member = OrgMember {
|
||||
member_id: MemberId::new(),
|
||||
display_name: "Alice".into(),
|
||||
role: OrgRole::Member,
|
||||
ed25519_pubkey: String::new(),
|
||||
collections: vec!["prod".into()],
|
||||
added_at: 0,
|
||||
added_by: MemberId::new(),
|
||||
};
|
||||
|
||||
let filtered = manifest.filter_for_member(&member);
|
||||
assert_eq!(filtered.entries.len(), 2);
|
||||
assert!(filtered.entries.iter().all(|e| e.collection == "prod"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn org_manifest_encrypt_decrypt_round_trip() {
|
||||
let key = generate_org_key();
|
||||
let mut manifest = OrgManifest::new();
|
||||
manifest.entries.push(OrgManifestEntry {
|
||||
id: ItemId::new(),
|
||||
r#type: ItemType::Login,
|
||||
title: "GitHub".into(),
|
||||
tags: vec!["work".into()],
|
||||
modified: 1748000000,
|
||||
trashed_at: None,
|
||||
collection: "eng-tools".into(),
|
||||
});
|
||||
|
||||
let encrypted = encrypt_org_manifest(&manifest, &key).expect("encrypt");
|
||||
let decrypted = decrypt_org_manifest(&encrypted, &key).expect("decrypt");
|
||||
|
||||
assert_eq!(decrypted.entries.len(), 1);
|
||||
assert_eq!(decrypted.entries[0].title, "GitHub");
|
||||
assert_eq!(decrypted.entries[0].collection, "eng-tools");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn members_validation_rejects_invalid_id() {
|
||||
let mut members = OrgMembers::new();
|
||||
members.members.push(OrgMember {
|
||||
member_id: MemberId("not-hex-lol!!".to_string()),
|
||||
display_name: "Bad".into(),
|
||||
role: OrgRole::Member,
|
||||
ed25519_pubkey: String::new(),
|
||||
collections: vec![],
|
||||
added_at: 0,
|
||||
added_by: MemberId::new(),
|
||||
});
|
||||
assert!(members.validate().is_err());
|
||||
}
|
||||
Reference in New Issue
Block a user