use palace::db;
use palace::knowledge_graph::*;
fn test_db() -> rusqlite::Connection {
db::open_in_memory().unwrap()
}
#[test]
fn add_entity_and_query() {
let conn = test_db();
let id = add_entity(&conn, "Alice", "person", None).unwrap();
assert_eq!(id, "alice");
}
#[test]
fn add_and_query_triple() {
let conn = test_db();
let tid = add_triple(
&conn,
"Alice",
"loves",
"Coffee",
Some("2025-01-01"),
None,
1.0,
None,
None,
)
.unwrap();
assert!(!tid.is_empty());
let facts = query_entity(&conn, "Alice", None, "outgoing").unwrap();
assert_eq!(facts.len(), 1);
assert_eq!(facts[0].predicate, "loves");
assert_eq!(facts[0].object, "Coffee");
assert!(facts[0].current);
}
#[test]
fn invalidate_ends_fact() {
let conn = test_db();
add_triple(
&conn,
"Bob",
"works_at",
"Acme",
Some("2020-01-01"),
None,
1.0,
None,
None,
)
.unwrap();
invalidate(&conn, "Bob", "works_at", "Acme", Some("2024-06-01")).unwrap();
let facts = query_entity(&conn, "Bob", None, "both").unwrap();
assert_eq!(facts.len(), 1);
assert!(!facts[0].current);
assert_eq!(facts[0].valid_to, Some("2024-06-01".to_string()));
}
#[test]
fn timeline_returns_ordered_facts() {
let conn = test_db();
add_triple(
&conn,
"Eve",
"joined",
"Team",
Some("2020-01-01"),
None,
1.0,
None,
None,
)
.unwrap();
add_triple(
&conn,
"Eve",
"promoted",
"Senior",
Some("2022-06-01"),
None,
1.0,
None,
None,
)
.unwrap();
let tl = timeline(&conn, Some("Eve")).unwrap();
assert_eq!(tl.len(), 2);
assert_eq!(tl[0].predicate, "joined");
assert_eq!(tl[1].predicate, "promoted");
}
#[test]
fn stats_shows_counts() {
let conn = test_db();
add_triple(&conn, "X", "rel1", "Y", None, None, 1.0, None, None).unwrap();
add_triple(&conn, "A", "rel2", "B", None, None, 1.0, None, None).unwrap();
let s = stats(&conn).unwrap();
assert_eq!(s.entities, 4);
assert_eq!(s.triples, 2);
assert_eq!(s.current_facts, 2);
assert_eq!(s.expired_facts, 0);
assert!(s.relationship_types.contains(&"rel1".to_string()));
}
#[test]
fn deduplication_prevents_duplicate_active_triples() {
let conn = test_db();
let id1 = add_triple(&conn, "A", "knows", "B", None, None, 1.0, None, None).unwrap();
let id2 = add_triple(&conn, "A", "knows", "B", None, None, 1.0, None, None).unwrap();
assert_eq!(id1, id2, "duplicate active triple should return same id");
}
#[test]
fn seed_agent_adoption_facts_is_idempotent() {
let conn = test_db();
let first = seed_agent_adoption_facts(&conn, "palace-rs").unwrap();
let second = seed_agent_adoption_facts(&conn, "palace-rs").unwrap();
assert!(first.inserted >= 10);
assert_eq!(second.inserted, 0);
assert!(second.unchanged >= first.inserted);
let palace = query_entity(&conn, "Palace", None, "outgoing").unwrap();
assert!(palace
.iter()
.any(|fact| fact.predicate == "supports_client" && fact.object == "Codex"));
assert!(palace.iter().any(
|fact| fact.predicate == "requires_protocol_step" && fact.object == "diary warm-start"
));
}
#[test]
fn seed_agent_adoption_fact_update_invalidates_old_fact() {
let conn = test_db();
seed_agent_adoption_facts(&conn, "palace-rs").unwrap();
let report = seed_or_update_fact(
&conn,
"palace-rs",
"has_role",
"Rust memory engine for coding agents",
"local-first memory engine",
)
.unwrap();
assert_eq!(report.inserted, 1);
assert_eq!(report.invalidated, 1);
let facts = query_entity(&conn, "palace-rs", None, "outgoing").unwrap();
let old = facts
.iter()
.find(|fact| fact.object == "Rust memory engine for coding agents")
.expect("old fact");
let new = facts
.iter()
.find(|fact| fact.object == "local-first memory engine")
.expect("new fact");
assert!(!old.current);
assert!(new.current);
}