use crate::CodememEngine;
use codemem_core::{Edge, GraphNode, MemoryNode, NodeKind, RelationshipType};
use std::collections::HashMap;
fn make_memory(id: &str, content: &str) -> MemoryNode {
let mut m = MemoryNode::test_default(content);
m.id = id.to_string();
m.importance = 0.7;
m.confidence = 0.9;
m.tags = vec!["test".to_string()];
m
}
#[test]
fn dirty_flag_lifecycle() {
let engine = CodememEngine::for_testing();
assert!(!engine.is_dirty(), "engine should start clean");
let m1 = make_memory("dirty-1", "dirty flag test memory one");
engine.persist_memory_no_save(&m1).unwrap();
assert!(
engine.is_dirty(),
"should be dirty after persist_memory_no_save"
);
engine.save_index();
assert!(!engine.is_dirty(), "should be clean after save_index");
let m2 = make_memory("dirty-2", "dirty flag test memory two");
engine.persist_memory_no_save(&m2).unwrap();
assert!(
engine.is_dirty(),
"should be dirty again after second persist_memory_no_save"
);
let m3 = make_memory("dirty-3", "dirty flag test memory three");
engine.persist_memory(&m3).unwrap();
assert!(
!engine.is_dirty(),
"should be clean after persist_memory with save=true"
);
}
#[test]
fn recall_camel_case_query() {
let engine = CodememEngine::for_testing();
let mem = make_memory("camel-1", "processRequest handles incoming HTTP data");
engine.persist_memory(&mem).unwrap();
let results = engine
.recall(&crate::recall::RecallQuery::new("processRequest", 5))
.unwrap();
assert!(
!results.is_empty(),
"recall with camelCase query should find the memory"
);
assert_eq!(results[0].memory.id, "camel-1");
let results2 = engine
.recall(&crate::recall::RecallQuery::new("process_request", 5))
.unwrap();
assert!(
!results2.is_empty(),
"recall with snake_case query should also find the memory"
);
assert_eq!(results2[0].memory.id, "camel-1");
}
#[test]
fn betweenness_lazy_compute_on_recall() {
let engine = CodememEngine::for_testing();
let now = chrono::Utc::now();
{
let mut graph = engine.lock_graph().unwrap();
for (id, kind) in &[
("sym:AlphaFunc", NodeKind::Function),
("sym:BetaFunc", NodeKind::Function),
("sym:GammaFunc", NodeKind::Function),
] {
let node = GraphNode {
id: id.to_string(),
kind: *kind,
label: id.to_string(),
payload: HashMap::new(),
centrality: 0.0,
memory_id: None,
namespace: None,
valid_from: None,
valid_to: None,
};
graph.add_node(node).unwrap();
}
for (id, src, dst) in &[
("e1", "sym:AlphaFunc", "sym:BetaFunc"),
("e2", "sym:BetaFunc", "sym:GammaFunc"),
] {
let edge = Edge {
id: id.to_string(),
src: src.to_string(),
dst: dst.to_string(),
relationship: RelationshipType::Calls,
weight: 0.8,
properties: HashMap::new(),
created_at: now,
valid_from: None,
valid_to: None,
};
graph.add_edge(edge).unwrap();
}
graph.recompute_centrality_with_options(false);
for id in &["sym:AlphaFunc", "sym:BetaFunc", "sym:GammaFunc"] {
assert_eq!(
graph.get_betweenness(id),
0.0,
"betweenness for {id} should be 0.0 after recompute_centrality_with_options(false)"
);
}
}
let mem = make_memory("between-1", "alpha beta gamma function calls");
engine.persist_memory(&mem).unwrap();
let _results = engine
.recall(&crate::recall::RecallQuery::new("alpha function", 5))
.unwrap();
let graph = engine.lock_graph().unwrap();
let beta_betweenness = graph.get_betweenness("sym:BetaFunc");
assert!(
beta_betweenness > 0.0,
"BetaFunc betweenness should be > 0 after recall triggers lazy compute, got {beta_betweenness}"
);
}
#[test]
fn batch_persist_then_single_save() {
let engine = CodememEngine::for_testing();
let m1 = make_memory("batch-1", "first batch memory about rust ownership");
let m2 = make_memory("batch-2", "second batch memory about borrowing rules");
let m3 = make_memory("batch-3", "third batch memory about lifetime annotations");
engine.persist_memory_no_save(&m1).unwrap();
engine.persist_memory_no_save(&m2).unwrap();
engine.persist_memory_no_save(&m3).unwrap();
assert!(
engine.is_dirty(),
"should be dirty after batch persist_memory_no_save"
);
engine.save_index();
assert!(!engine.is_dirty(), "should be clean after save_index");
for id in &["batch-1", "batch-2", "batch-3"] {
let mem = engine.storage.get_memory(id).unwrap();
assert!(mem.is_some(), "memory {id} should be in storage");
}
let bm25 = engine.lock_bm25().unwrap();
assert!(
bm25.score("rust ownership", "batch-1") > 0.0,
"batch-1 should be in BM25 index"
);
assert!(
bm25.score("borrowing rules", "batch-2") > 0.0,
"batch-2 should be in BM25 index"
);
assert!(
bm25.score("lifetime annotations", "batch-3") > 0.0,
"batch-3 should be in BM25 index"
);
}