use dejadb_core::types::{Event, Fact, Grain};
use dejadb_store::{EmbedBackend, DejaDB};
use tempfile::TempDir;
struct TrigramEmbed;
impl EmbedBackend for TrigramEmbed {
fn dim(&self) -> usize {
64
}
fn embed(&self, text: &str) -> dejadb_core::error::Result<Vec<f32>> {
let chars: Vec<char> = text.chars().collect();
let mut v = vec![0f32; 64];
for w in chars.windows(3) {
let mut h: u64 = 1469598103934665603;
for c in w {
h ^= *c as u64;
h = h.wrapping_mul(1099511628211);
}
v[(h % 64) as usize] += 1.0;
}
let n = v.iter().map(|x| x * x).sum::<f32>().sqrt().max(1e-9);
Ok(v.into_iter().map(|x| x / n).collect())
}
}
fn fact(ns: &str, s: &str, r: &str, o: &str) -> Fact {
let mut f = Fact::new(s, r, o).confidence(0.9);
f.common.namespace = Some(ns.to_string());
f
}
fn seed(m: &mut DejaDB) {
m.add(&fact("caller", "john", "prefers", "green tea in the morning")).unwrap();
m.add(&fact("caller", "جون", "يفضل", "الشاي الأخضر في الصباح")).unwrap();
let mut e = Event::new("约翰早上喜欢喝绿茶,还问了花生过敏的政策");
e.common.namespace = Some("caller".to_string());
e.session_id = Some("call-9".to_string());
m.add(&e).unwrap();
}
#[test]
fn structural_recall_is_script_agnostic() {
let d = TempDir::new().unwrap();
let mut m = DejaDB::open(d.path().join("m.db").to_str().unwrap()).unwrap();
seed(&mut m);
let got = m.recall("caller", "جون", None, 8).unwrap();
assert_eq!(got.len(), 1);
assert_eq!(got[0].get_str("object"), Some("الشاي الأخضر في الصباح"));
assert_eq!(m.verify().unwrap().hash_mismatches, 0);
}
#[test]
fn vector_leg_serves_each_language() {
let d = TempDir::new().unwrap();
let mut m = DejaDB::open(d.path().join("m.db").to_str().unwrap()).unwrap();
m.set_embedder(Box::new(TrigramEmbed));
seed(&mut m);
let hits = m.recall_hybrid("caller", None, None, Some("绿茶 花生过敏"), 3, None).unwrap();
assert!(!hits.is_empty());
assert!(hits[0].get_str("content").is_some_and(|c| c.contains("绿茶")), "{:?}", hits[0].fields);
let hits = m.recall_hybrid("caller", None, None, Some("الشاي الأخضر"), 3, None).unwrap();
assert!(!hits.is_empty());
assert_eq!(hits[0].get_str("subject"), Some("جون"));
let hits = m.recall_hybrid("caller", None, None, Some("green tea morning"), 3, None).unwrap();
assert!(!hits.is_empty());
assert_eq!(hits[0].get_str("subject"), Some("john"));
}
#[test]
fn forget_removes_embedding_row() {
let d = TempDir::new().unwrap();
let mut m = DejaDB::open(d.path().join("m.db").to_str().unwrap()).unwrap();
m.set_embedder(Box::new(TrigramEmbed));
let h = m.add(&fact("caller", "john", "prefers", "unique-zanzibar-token")).unwrap();
assert!(!m.search_vector("caller", "unique-zanzibar-token", 4).unwrap().is_empty());
m.forget(&h).unwrap();
let hits = m.recall_hybrid("caller", None, None, Some("unique-zanzibar-token"), 4, None).unwrap();
assert!(hits.is_empty(), "forgotten grain must leave the vector leg");
}
#[test]
fn nearest_semantic_ranks_paraphrase_and_needs_embedder() {
let d = TempDir::new().unwrap();
let mut m = DejaDB::open(d.path().join("m.db").to_str().unwrap()).unwrap();
m.set_embedder(Box::new(TrigramEmbed));
m.add(&fact("agent", "flaky", "lesson", "isolate the shared tempdir per test"))
.unwrap();
m.add(&fact("agent", "flaky", "lesson", "prefer table-driven cases for coverage"))
.unwrap();
let hits = m
.nearest_semantic(
"agent",
Some("flaky"),
Some("lesson"),
"isolate the tempdir for each test",
5,
)
.unwrap();
assert!(!hits.is_empty(), "advise mode returns near neighbours");
let top = m.get(&hits[0].0).unwrap();
assert_eq!(
top.get_str("object"),
Some("isolate the shared tempdir per test"),
"the paraphrase's nearest neighbour is the tempdir lesson"
);
if hits.len() > 1 {
assert!(hits[0].1 >= hits[1].1, "results are sorted by similarity desc");
}
let mut bare = DejaDB::open(d.path().join("bare.db").to_str().unwrap()).unwrap();
assert!(bare
.nearest_semantic("agent", None, None, "anything", 3)
.is_err());
}