use alaya::*;
mod common;
fn episode(content: &str, session: &str, ts: i64) -> NewEpisode {
NewEpisode {
content: content.to_string(),
role: Role::User,
session_id: session.to_string(),
timestamp: ts,
context: EpisodeContext::default(),
embedding: None,
}
}
fn store_n_episodes(store: &Alaya, session: &str, count: usize, base_ts: i64) -> Vec<EpisodeId> {
(0..count)
.map(|i| {
store
.episodes()
.store(&episode(
&format!("Episode {i} in session {session} about Rust programming"),
session,
base_ts + (i as i64) * 100,
))
.unwrap()
})
.collect()
}
#[test]
fn test_persistence_across_open_close() {
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("persistence_test.db");
{
let store = Alaya::open(&db_path).unwrap();
store
.episodes()
.store(&episode(
"Rust has zero-cost abstractions",
"persist-s1",
1000,
))
.unwrap();
store
.episodes()
.store(&episode(
"Ownership prevents data races",
"persist-s1",
2000,
))
.unwrap();
store
.episodes()
.store(&episode(
"The borrow checker catches bugs at compile time",
"persist-s1",
3000,
))
.unwrap();
let status = store.admin().status().unwrap();
assert_eq!(status.episode_count, 3);
}
{
let store = Alaya::open(&db_path).unwrap();
let status = store.admin().status().unwrap();
assert_eq!(
status.episode_count, 3,
"episodes should persist across open/close"
);
let results = store.knowledge().query(&Query::simple("Rust")).unwrap();
assert!(
!results.is_empty(),
"query should return persisted episodes after reopen"
);
let has_zero_cost = results.iter().any(|m| m.content.contains("zero-cost"));
assert!(has_zero_cost, "persisted content should be retrievable");
}
}
#[test]
fn test_learn_creates_knowledge() {
let store = Alaya::open_in_memory().unwrap();
let ep_ids = store_n_episodes(&store, "learn-s1", 5, 1_000);
let nodes = vec![
NewSemanticNode {
content: "User programs in Rust".to_string(),
node_type: SemanticType::Fact,
confidence: 0.9,
source_episodes: vec![ep_ids[0], ep_ids[1], ep_ids[2]],
embedding: None,
},
NewSemanticNode {
content: "User prefers functional style".to_string(),
node_type: SemanticType::Fact,
confidence: 0.85,
source_episodes: vec![ep_ids[3], ep_ids[4]],
embedding: None,
},
];
let report = store.knowledge().learn(nodes).unwrap();
assert_eq!(report.nodes_created, 2, "should create 2 semantic nodes");
assert_eq!(
report.links_created, 5,
"should create 5 Causal links (3 + 2 source episodes)"
);
let knowledge = store.knowledge().filter(None).unwrap();
assert_eq!(knowledge.len(), 2, "should have 2 semantic nodes");
let contents: Vec<&str> = knowledge.iter().map(|n| n.content.as_str()).collect();
assert!(contents.contains(&"User programs in Rust"));
assert!(contents.contains(&"User prefers functional style"));
let status = store.admin().status().unwrap();
assert_eq!(status.semantic_node_count, 2);
assert_eq!(status.episode_count, 5);
}
#[test]
fn test_learn_creates_causal_links() {
let store = Alaya::open_in_memory().unwrap();
let ep_ids = store_n_episodes(&store, "causal-s1", 3, 1_000);
let nodes = vec![NewSemanticNode {
content: "User discusses Rust ownership".to_string(),
node_type: SemanticType::Fact,
confidence: 0.8,
source_episodes: ep_ids.clone(),
embedding: None,
}];
let report = store.knowledge().learn(nodes).unwrap();
assert_eq!(report.nodes_created, 1);
assert_eq!(report.links_created, 3);
let knowledge = store.knowledge().filter(None).unwrap();
assert_eq!(knowledge.len(), 1);
let node_id = knowledge[0].id;
let neighbors = store
.graph()
.neighbors(NodeRef::Semantic(node_id), 1)
.unwrap();
assert!(
!neighbors.is_empty(),
"semantic node should have episode neighbors via Causal links"
);
for ep_id in &ep_ids {
let has_ep = neighbors
.iter()
.any(|(nr, _)| *nr == NodeRef::Episode(*ep_id));
assert!(
has_ep,
"episode {ep_id:?} should be a neighbor of the semantic node",
);
}
}
#[test]
fn test_learn_marks_episodes_consolidated() {
let store = Alaya::open_in_memory().unwrap();
let ep_ids = store_n_episodes(&store, "consol-s1", 5, 1_000);
let uncons = store.episodes().unconsolidated(100).unwrap();
assert_eq!(
uncons.len(),
5,
"all 5 episodes should be unconsolidated before learn()"
);
let nodes = vec![NewSemanticNode {
content: "Fact from first three episodes".to_string(),
node_type: SemanticType::Fact,
confidence: 0.8,
source_episodes: vec![ep_ids[0], ep_ids[1], ep_ids[2]],
embedding: None,
}];
store.knowledge().learn(nodes).unwrap();
let uncons = store.episodes().unconsolidated(100).unwrap();
assert_eq!(
uncons.len(),
2,
"only 2 episodes (3 and 4) should remain unconsolidated"
);
let uncons_ids: Vec<EpisodeId> = uncons.iter().map(|e| e.id).collect();
assert!(uncons_ids.contains(&ep_ids[3]));
assert!(uncons_ids.contains(&ep_ids[4]));
}
#[test]
fn test_learn_empty_vec() {
let store = Alaya::open_in_memory().unwrap();
let report = store.knowledge().learn(vec![]).unwrap();
assert_eq!(report.nodes_created, 0);
assert_eq!(report.links_created, 0);
assert_eq!(report.categories_assigned, 0);
assert_eq!(report.episodes_processed, 0);
}