use plugmem_core::{Config, Error, FactFault, FactId, MemStorage, Memory, RememberInput, Storage};
fn cfg() -> Config {
let mut cfg = Config::default();
cfg.shards_facts = 8;
cfg.shards_entities = 4;
cfg.shards_edges = 4;
cfg.shards_temporal = 4;
cfg.shards_postings = 16;
cfg
}
fn engine() -> (Memory<'static>, MemStorage) {
(Memory::new(cfg()).unwrap(), MemStorage::new())
}
#[test]
fn metadata_is_stored_and_read_back_in_canonical_order() {
let (mut mem, mut store) = engine();
let pairs = [
("uri", "s3://b/x"),
("mime", "application/pdf"),
("page", "3"),
];
mem.remember(
&mut store,
RememberInput {
metadata: Some(&pairs),
..RememberInput::text(100, "a scanned contract")
},
)
.unwrap();
mem.remember(&mut store, RememberInput::text(200, "no metadata"))
.unwrap();
mem.remember(
&mut store,
RememberInput {
metadata: Some(&[]),
..RememberInput::text(300, "empty map")
},
)
.unwrap();
let mut out = Vec::new();
assert!(mem.metadata_of(FactId(0), &mut out));
assert_eq!(
out,
vec![
("mime", "application/pdf"),
("page", "3"),
("uri", "s3://b/x"),
],
"keys come back sorted regardless of input order"
);
assert!(!mem.metadata_of(FactId(1), &mut out));
assert!(out.is_empty(), "no metadata clears the buffer");
assert!(!mem.metadata_of(FactId(2), &mut out), "empty map = none");
}
#[test]
fn core_pairs_and_host_btreemap_agree_on_order_and_count() {
use std::collections::BTreeMap;
let (mut mem, mut store) = engine();
let shuffled = [
("m", "1"),
("a", "2"),
("zzz", "3"),
("b", "4"),
("aa", "5"),
];
mem.remember(
&mut store,
RememberInput {
metadata: Some(&shuffled),
..RememberInput::text(100, "parity")
},
)
.unwrap();
let mut pairs = Vec::new();
assert!(mem.metadata_of(FactId(0), &mut pairs));
let map: BTreeMap<&str, &str> = pairs.iter().copied().collect();
let map_seq: Vec<(&str, &str)> = map.into_iter().collect();
assert_eq!(pairs.len(), shuffled.len(), "no key lost");
assert_eq!(pairs.len(), map_seq.len(), "core and map agree on count");
assert_eq!(pairs, map_seq, "core order == map iteration order");
assert_eq!(
pairs,
vec![
("a", "2"),
("aa", "5"),
("b", "4"),
("m", "1"),
("zzz", "3"),
]
);
}
#[test]
fn duplicate_metadata_key_is_rejected() {
let (mut mem, mut store) = engine();
let err = mem
.remember(
&mut store,
RememberInput {
metadata: Some(&[("k", "a"), ("k", "b")]),
..RememberInput::text(100, "dup")
},
)
.unwrap_err();
assert!(matches!(err, Error::Invalid(_)));
assert_eq!(mem.facts_len(), 0, "the whole op aborted");
}
#[test]
fn journal_replay_reproduces_metadata() {
let (mut mem, mut store) = engine();
let pairs = [("z", "last"), ("a", "first"), ("m", "mid")];
mem.remember(
&mut store,
RememberInput {
metadata: Some(&pairs),
..RememberInput::text(100, "journaled")
},
)
.unwrap();
let journal = store.read_journal().unwrap();
let (replayed, report) = Memory::from_bytes(None, &journal, cfg()).unwrap();
assert_eq!(report.replayed, 1);
let mut a = Vec::new();
let mut b = Vec::new();
mem.metadata_of(FactId(0), &mut a);
replayed.metadata_of(FactId(0), &mut b);
assert_eq!(a, b);
assert_eq!(a, vec![("a", "first"), ("m", "mid"), ("z", "last")]);
}
#[test]
fn snapshot_roundtrips_byte_identical_with_metadata() {
let (mut mem, mut store) = engine();
for (i, kv) in [("uri", "a"), ("uri", "b")].iter().enumerate() {
mem.remember(
&mut store,
RememberInput {
metadata: Some(&[*kv, ("n", "1")]),
..RememberInput::text(100 + i as u64, "fact")
},
)
.unwrap();
}
let first = mem.snapshot_bytes(0);
let (loaded, _) = Memory::from_bytes(Some(&first), &[], cfg()).unwrap();
assert!(loaded.verify().is_ok());
assert_eq!(first, loaded.snapshot_bytes(0), "save→load→save is stable");
}
#[test]
fn maintain_preserves_live_metadata_and_purges_the_rest() {
let (mut mem, mut store) = engine();
mem.remember(
&mut store,
RememberInput {
metadata: Some(&[("keep", "yes")]),
..RememberInput::text(100, "survivor")
},
)
.unwrap();
mem.remember(
&mut store,
RememberInput {
metadata: Some(&[("drop", "me")]),
..RememberInput::text(200, "doomed")
},
)
.unwrap();
mem.forget(&mut store, 250, FactId(1)).unwrap();
let report = mem.maintain(&mut store, 300).unwrap();
assert_eq!(report.purged, 1);
let mut out = Vec::new();
assert!(mem.metadata_of(FactId(0), &mut out));
assert_eq!(out, vec![("keep", "yes")]);
assert!(!mem.metadata_of(FactId(1), &mut out), "purged fact is gone");
assert!(mem.verify().is_ok());
}
#[test]
fn verify_and_faulty_facts_catch_corrupt_metadata() {
let (mut mem, mut store) = engine();
mem.remember(
&mut store,
RememberInput {
metadata: Some(&[("k", "UNIQUEMETAMARKER")]),
..RememberInput::text(100, "has metadata")
},
)
.unwrap();
let clean = mem.snapshot_bytes(0);
let (loaded, _) = Memory::from_bytes(Some(&clean), &[], cfg()).unwrap();
assert!(loaded.verify().is_ok(), "a clean image verifies");
let at = clean
.windows(b"UNIQUEMETAMARKER".len())
.position(|w| w == b"UNIQUEMETAMARKER")
.expect("the metadata value is stored verbatim");
let mut bad = clean.clone();
bad[at] = 0xFF;
let (loaded, _) = Memory::from_bytes(Some(&bad), &[], cfg())
.expect("the trust/sparse default open does not scan metadata");
assert!(loaded.get(FactId(0)).is_some());
let mut out = Vec::new();
assert!(
!loaded.metadata_of(FactId(0), &mut out),
"bad metadata hidden"
);
assert_eq!(
loaded.verify(),
Err(Error::Corrupt("metadata string is not UTF-8"))
);
assert_eq!(
loaded.faulty_facts(),
vec![(FactId(0), FactFault::Metadata)]
);
}