use super::{corrupt, KEYSET_MAGIC, KEYSET_VERSION};
use crate::coordinate::Coordinate;
use crate::event::EventKind;
use crate::id::EventId;
use crate::store::file_classification::KEYSET_FILENAME;
use crate::store::keyscope::{scope_for, KeyScope, KeyScopeGranularity, KeyStore};
use crate::store::platform::fs::{read as fs_read, write_derivative_file_atomically};
use crate::store::StoreError;
const GRAN: KeyScopeGranularity = KeyScopeGranularity::PerEntity;
const NONCE: [u8; 24] = [0x33; 24];
fn scope(entity: &str) -> KeyScope {
let coord = Coordinate::new(entity, "scope:persist").expect("coordinate");
scope_for(
GRAN,
&coord,
EventKind::custom(0xF, 1),
EventId::from(1u128),
)
}
#[test]
fn flush_then_load_recovers_keys_and_a_pre_flush_ciphertext_opens() {
let dir = tempfile::tempdir().expect("tmpdir");
let scope_a = scope("entity:a");
let scope_b = scope("entity:b");
let mut store = KeyStore::new(GRAN);
let ciphertext = store
.get_or_create(&scope_a)
.expect("mint A")
.seal(&NONCE, b"aad", b"secret payload")
.expect("seal under A");
let _ = store.get_or_create(&scope_b).expect("mint B");
store.flush(dir.path()).expect("flush keyset");
let reloaded = KeyStore::load(dir.path(), GRAN).expect("load keyset");
assert_eq!(reloaded.granularity(), GRAN, "granularity round-trips");
assert!(reloaded.get(&scope_b).is_some(), "key B recovered");
let recovered = reloaded
.get(&scope_a)
.expect("key A recovered")
.open(&NONCE, b"aad", &ciphertext)
.expect("reloaded key A opens the pre-flush ciphertext");
assert_eq!(recovered.as_slice(), b"secret payload");
}
#[test]
fn absent_keyset_loads_an_empty_store_not_a_corruption() {
let dir = tempfile::tempdir().expect("tmpdir");
let loaded = KeyStore::load(dir.path(), GRAN).expect("absent keyset → empty store");
assert!(
loaded.get(&scope("entity:none")).is_none(),
"an absent keyset rehydrates empty"
);
}
#[test]
fn shred_survives_restart_destroyed_key_is_absent_and_ciphertext_unrecoverable() {
let dir = tempfile::tempdir().expect("tmpdir");
let target = scope("entity:shred-me");
let keep = scope("entity:keep");
let mut store = KeyStore::new(GRAN);
let ciphertext = store
.get_or_create(&target)
.expect("mint target")
.seal(&NONCE, b"aad", b"data to shred")
.expect("seal target");
let _ = store.get_or_create(&keep).expect("mint keep");
store.flush(dir.path()).expect("flush v1");
assert!(store.destroy(&target), "destroy removes the target key");
store.flush(dir.path()).expect("flush shredded keyset");
let reloaded = KeyStore::load(dir.path(), GRAN).expect("reload after shred");
assert!(
reloaded.get(&target).is_none(),
"PROPERTY: the shredded scope's key is absent after restart"
);
assert!(
reloaded.get(&keep).is_some(),
"PROPERTY: an untouched key survives the shred flush"
);
let mut reloaded = reloaded;
let refreshed = reloaded.get_or_create(&target).expect("re-mint target");
assert!(
matches!(
refreshed.open(&NONCE, b"aad", &ciphertext),
Err(crate::store::KeyStoreError::Open)
),
"PROPERTY: post-shred ciphertext is permanently unrecoverable across restart"
);
}
fn write_raw_keyset(dir: &std::path::Path, bytes: &[u8]) {
write_derivative_file_atomically(dir, &dir.join(KEYSET_FILENAME), "test-forge-keyset", bytes)
.expect("write raw keyset");
}
#[test]
fn corrupt_keyset_fails_closed_rather_than_starting_empty() {
let garbage_dir = tempfile::tempdir().expect("tmpdir");
write_raw_keyset(garbage_dir.path(), b"not a real keyset file at all");
assert!(
matches!(
KeyStore::load(garbage_dir.path(), GRAN),
Err(StoreError::KeysetCorrupt { .. })
),
"PROPERTY: a garbage keyset fails closed (KeysetCorrupt), never silently empty"
);
let short_dir = tempfile::tempdir().expect("tmpdir");
write_raw_keyset(short_dir.path(), &KEYSET_MAGIC[..3]);
assert!(
matches!(
KeyStore::load(short_dir.path(), GRAN),
Err(StoreError::KeysetCorrupt { .. })
),
"PROPERTY: a truncated keyset header fails closed"
);
let crc_dir = tempfile::tempdir().expect("tmpdir");
let mut store = KeyStore::new(GRAN);
let _ = store.get_or_create(&scope("entity:crc")).expect("mint");
store.flush(crc_dir.path()).expect("flush");
let path = crc_dir.path().join(KEYSET_FILENAME);
let mut raw = fs_read(&path).expect("read flushed keyset");
raw[8] ^= 0xFF;
write_derivative_file_atomically(crc_dir.path(), &path, "test-corrupt-crc", &raw)
.expect("rewrite with corrupted crc");
assert!(
matches!(
KeyStore::load(crc_dir.path(), GRAN),
Err(StoreError::KeysetCorrupt { .. })
),
"PROPERTY: a CRC mismatch fails closed"
);
let gran_dir = tempfile::tempdir().expect("tmpdir");
let mut store = KeyStore::new(GRAN);
let _ = store.get_or_create(&scope("entity:gran")).expect("mint");
store.flush(gran_dir.path()).expect("flush");
assert!(
matches!(
KeyStore::load(gran_dir.path(), KeyScopeGranularity::PerEvent),
Err(StoreError::KeysetCorrupt { .. })
),
"PROPERTY: a configured-vs-persisted granularity mismatch fails closed"
);
}
#[test]
fn corrupt_helper_and_header_constants_are_stable() {
assert_eq!(KEYSET_MAGIC, b"FBATKS");
assert_eq!(KEYSET_VERSION, 1);
assert!(matches!(
corrupt("x".to_owned()),
StoreError::KeysetCorrupt { .. }
));
let rendered = corrupt("boom-reason".to_owned()).to_string();
assert!(
rendered.contains("boom-reason") && rendered.contains("refusing to open"),
"KeysetCorrupt Display must carry the reason and the fail-closed posture: {rendered}"
);
}