use mentedb::prelude::{AgentId, MemoryNode, MemoryType, UserId};
use mentedb::{CognitiveConfig, MenteDb};
fn config() -> CognitiveConfig {
CognitiveConfig {
hidden_count_tags: vec!["turn".to_string(), "action".to_string()],
..Default::default()
}
}
fn open(dir: &std::path::Path) -> MenteDb {
MenteDb::open_with_config(dir, config()).expect("open")
}
fn node(user: u128, agent: u128, ty: MemoryType, content: &str, tags: &[&str]) -> MemoryNode {
let mut n = MemoryNode::new(
AgentId(uuid::Uuid::from_u128(agent)),
ty,
content.to_string(),
Vec::new(),
);
n.user_id = UserId(uuid::Uuid::from_u128(user));
n.tags = tags.iter().map(|t| t.to_string()).collect();
n
}
#[test]
fn counts_by_type_owner_scope_excluding_hidden() {
let dir = tempfile::tempdir().unwrap();
let db = open(dir.path());
db.store(node(1, 1, MemoryType::Semantic, "a", &[]))
.unwrap();
db.store(node(
1,
1,
MemoryType::Procedural,
"b",
&["scope:project:apex"],
))
.unwrap();
db.store(node(1, 2, MemoryType::Semantic, "c", &[]))
.unwrap();
db.store(node(0, 0, MemoryType::Semantic, "d", &["scope:always"]))
.unwrap();
db.store(node(1, 1, MemoryType::Episodic, "raw turn", &["turn"]))
.unwrap();
let c = db.scope_counts();
assert_eq!(c.total, 4, "the raw turn is excluded");
assert_eq!(c.global, 1, "the nil-owner memory");
assert_eq!(c.always, 1);
assert_eq!(c.by_type.get("semantic"), Some(&3));
assert_eq!(c.by_type.get("procedural"), Some(&1));
assert_eq!(
c.by_type.get("episodic"),
None,
"the only episodic is hidden"
);
assert_eq!(
c.by_user.get(&UserId(uuid::Uuid::from_u128(1)).to_string()),
Some(&3)
);
assert_eq!(
c.by_agent
.get(&AgentId(uuid::Uuid::from_u128(1)).to_string()),
Some(&2)
);
assert_eq!(
c.by_agent
.get(&AgentId(uuid::Uuid::from_u128(2)).to_string()),
Some(&1)
);
assert_eq!(c.by_project.get("apex"), Some(&1));
let growth_total: u64 = c.growth.values().sum();
assert_eq!(growth_total, 4, "growth sums to the non-hidden total");
}
#[test]
fn growth_is_keyed_by_creation_month() {
let dir = tempfile::tempdir().unwrap();
let db = open(dir.path());
let mut jan = node(1, 1, MemoryType::Semantic, "jan", &[]);
jan.created_at = 1_610_668_800_000_000; let mut mar = node(1, 1, MemoryType::Semantic, "mar", &[]);
mar.created_at = 1_616_198_400_000_000; db.store(jan).unwrap();
db.store(mar).unwrap();
let c = db.scope_counts();
assert_eq!(c.growth.get("2021-01"), Some(&1));
assert_eq!(c.growth.get("2021-03"), Some(&1));
}
#[test]
fn stats_snapshot_materializes_health_usage_and_persists() {
let dir = tempfile::tempdir().unwrap();
{
let db = open(dir.path());
let mut a = node(1, 1, MemoryType::Semantic, "a", &["scope:project:apex"]);
a.access_count = 3;
let b = node(1, 1, MemoryType::Procedural, "b", &["scope:project:apex"]);
db.store(a).unwrap();
db.store(b).unwrap();
db.store(node(1, 1, MemoryType::Episodic, "turn", &["turn"]))
.unwrap();
assert!(db.stats_snapshot().is_none());
let snap = db.refresh_stats_snapshot();
assert_eq!(snap.count, 2, "hidden turn excluded from health count");
assert_eq!(snap.project_usage.get("apex"), Some(&3), "recalls summed");
assert_eq!(snap.health_distribution.iter().sum::<u64>(), 2);
assert!(snap.avg_health > 0.0 && snap.avg_health <= 1.0);
}
let db = open(dir.path());
let snap = db
.stats_snapshot()
.expect("snapshot persisted across reopen");
assert_eq!(snap.count, 2);
assert_eq!(snap.project_usage.get("apex"), Some(&3));
}
#[test]
fn forget_decrements_the_counts() {
let dir = tempfile::tempdir().unwrap();
let db = open(dir.path());
let a = node(1, 1, MemoryType::Semantic, "keep", &[]);
let b = node(
1,
1,
MemoryType::Procedural,
"drop",
&["scope:project:apex"],
);
let b_id = b.id;
db.store(a).unwrap();
db.store(b).unwrap();
assert_eq!(db.scope_counts().total, 2);
db.forget(b_id).unwrap();
let c = db.scope_counts();
assert_eq!(c.total, 1);
assert_eq!(c.by_type.get("procedural"), Some(&0));
assert_eq!(c.by_project.get("apex"), Some(&0));
assert_eq!(c.by_type.get("semantic"), Some(&1));
}
#[test]
fn first_read_grounds_writes_made_before_it() {
let dir = tempfile::tempdir().unwrap();
let db = open(dir.path());
db.store(node(1, 1, MemoryType::Semantic, "a", &[]))
.unwrap();
db.store(node(1, 1, MemoryType::Semantic, "b", &[]))
.unwrap();
db.store(node(1, 1, MemoryType::Episodic, "turn", &["action"]))
.unwrap();
assert_eq!(
db.scope_counts().total,
2,
"grounded from the store, hidden excluded"
);
}
#[test]
fn counts_and_blobs_persist_across_reopen() {
let dir = tempfile::tempdir().unwrap();
{
let db = MenteDb::open_with_config(dir.path(), config()).expect("open");
db.store(node(1, 1, MemoryType::Semantic, "a", &[]))
.unwrap();
db.store(node(
1,
1,
MemoryType::Procedural,
"b",
&["scope:project:apex"],
))
.unwrap();
db.store(node(1, 1, MemoryType::Episodic, "raw turn", &["turn"]))
.unwrap();
assert_eq!(db.scope_counts().total, 2, "grounds, hidden excluded");
db.write_blob("stats", b"cached-stats-payload");
db.flush_full().expect("flush");
}
let db = MenteDb::open_with_config(dir.path(), config()).expect("reopen");
let c = db.scope_counts();
assert_eq!(c.total, 2, "persisted counts survive a cold reopen");
assert_eq!(c.by_type.get("procedural"), Some(&1));
assert_eq!(c.by_project.get("apex"), Some(&1));
assert_eq!(
db.read_blob("stats").as_deref(),
Some(&b"cached-stats-payload"[..])
);
assert_eq!(db.read_blob("never-written"), None);
}
#[test]
fn reopen_serves_persisted_counts_without_a_recount() {
let dir = tempfile::tempdir().unwrap();
{
let db = open(dir.path());
for i in 0..3 {
db.store(node(1, 1, MemoryType::Semantic, &format!("fact {i}"), &[]))
.unwrap();
}
assert_eq!(db.scope_counts().total, 3);
db.flush_full().unwrap();
db.store(node(1, 1, MemoryType::Semantic, "a later fact", &[]))
.unwrap();
db.flush().unwrap();
}
let db = open(dir.path());
let (approx, grounded) = db.scope_counts_approx();
assert!(!grounded, "writes after the snapshot leave it ungrounded");
assert_eq!(approx.total, 3, "the persisted value is served, not zero");
assert_eq!(db.scope_counts().total, 4);
let (exact, grounded) = db.scope_counts_approx();
assert!(grounded);
assert_eq!(exact.total, 4);
}
#[test]
fn concurrent_first_reads_agree() {
let dir = tempfile::tempdir().unwrap();
{
let db = open(dir.path());
for i in 0..50 {
db.store(node(1, 1, MemoryType::Semantic, &format!("fact {i}"), &[]))
.unwrap();
}
db.flush().unwrap();
}
let db = std::sync::Arc::new(open(dir.path()));
let totals: Vec<u64> = (0..8)
.map(|_| {
let db = db.clone();
std::thread::spawn(move || db.scope_counts().total)
})
.collect::<Vec<_>>()
.into_iter()
.map(|h| h.join().unwrap())
.collect();
assert!(totals.iter().all(|t| *t == 50), "{totals:?}");
}