use std::path::Path;
use std::sync::Arc;
use fathomdb_embedder_api::{Embedder, EmbedderError, EmbedderIdentity, Vector};
use fathomdb_engine::{
Engine, InitialState, LifecycleState, PreparedWrite, ReadView, TraversalDirection,
};
use fathomdb_schema::SQLITE_SUFFIX;
use tempfile::TempDir;
#[derive(Clone, Debug)]
struct ConstantEmbedder;
impl Embedder for ConstantEmbedder {
fn identity(&self) -> EmbedderIdentity {
EmbedderIdentity::new("const", "rev-a", 8)
}
fn embed(&self, _text: &str) -> Result<Vector, EmbedderError> {
let mut v = vec![0.0_f32; 8];
v[0] = 1.0;
Ok(v)
}
}
fn active_node(kind: &str, body: &str, logical_id: &str) -> PreparedWrite {
PreparedWrite::Node {
kind: kind.to_string(),
body: body.to_string(),
source_id: fathomdb_engine::SourceId::new("test:fixture").expect("test source id"),
logical_id: Some(logical_id.to_string()),
state: InitialState::Active,
reason: None,
valid_from: None,
valid_until: None,
}
}
fn pending_node(kind: &str, body: &str, logical_id: &str, reason: &str) -> PreparedWrite {
PreparedWrite::Node {
kind: kind.to_string(),
body: body.to_string(),
source_id: fathomdb_engine::SourceId::new("test:fixture").expect("test source id"),
logical_id: Some(logical_id.to_string()),
state: InitialState::Pending,
reason: Some(reason.to_string()),
valid_from: None,
valid_until: None,
}
}
fn open(name: &str) -> (TempDir, fathomdb_engine::OpenedEngine) {
let dir = TempDir::new().unwrap();
let path = dir.path().join(format!("{name}{SQLITE_SUFFIX}"));
let opened = Engine::open(path).unwrap();
(dir, opened)
}
fn read_state_reason(path: &Path, logical_id: &str) -> (String, Option<String>) {
let conn = rusqlite::Connection::open_with_flags(
path,
rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY | rusqlite::OpenFlags::SQLITE_OPEN_URI,
)
.expect("open read-only");
conn.query_row(
"SELECT state, reason FROM canonical_nodes \
WHERE logical_id = ?1 AND superseded_at IS NULL",
[logical_id],
|r| Ok((r.get::<_, String>(0)?, r.get::<_, Option<String>>(1)?)),
)
.expect("row present")
}
#[test]
fn r_ex_1_state_and_reason_round_trip() {
let (dir, opened) = open("rex1_roundtrip");
let path = dir.path().join(format!("rex1_roundtrip{SQLITE_SUFFIX}"));
let engine = &opened.engine;
engine.write(&[active_node("doc", "active body", "act1")]).expect("write active");
engine
.write(&[pending_node("doc", "pending body", "pen1", "awaiting-review")])
.expect("write pending");
assert_eq!(read_state_reason(&path, "act1"), ("active".to_string(), None));
assert_eq!(
read_state_reason(&path, "pen1"),
("pending".to_string(), Some("awaiting-review".to_string()))
);
}
#[test]
fn r_ex_1_deleted_and_purged_not_creatable() {
assert_eq!(InitialState::from_create_str("active"), Some(InitialState::Active));
assert_eq!(InitialState::from_create_str("pending"), Some(InitialState::Pending));
assert_eq!(InitialState::from_create_str("deleted"), None);
assert_eq!(InitialState::from_create_str("purged"), None);
assert_eq!(InitialState::from_create_str("bogus"), None);
assert_eq!(InitialState::Active.as_str(), "active");
assert_eq!(InitialState::Pending.as_str(), "pending");
assert_eq!(LifecycleState::Deleted.as_str(), "deleted");
assert_eq!(LifecycleState::Purged.as_str(), "purged");
assert_eq!(LifecycleState::from_str_opt("deleted"), Some(LifecycleState::Deleted));
assert_eq!(LifecycleState::from_str_opt("nope"), None);
assert_eq!(InitialState::Pending.to_lifecycle_state(), LifecycleState::Pending);
assert_eq!(InitialState::Active.to_lifecycle_state(), LifecycleState::Active);
}
#[test]
fn r_ex_2_pending_absent_from_default_search_and_read() {
let (_dir, opened) = open("rex2_exclusion");
let engine = &opened.engine;
engine
.write(&[active_node("doc", "zephyrunique active payload", "act1")])
.expect("write active");
engine
.write(&[pending_node("doc", "zephyrunique pending payload", "pen1", "quarantine")])
.expect("write pending");
let hits = engine.search("zephyrunique").expect("search");
let bodies: Vec<&str> = hits.results.iter().map(|h| h.body.as_str()).collect();
assert!(
bodies.iter().any(|b| b.contains("active payload")),
"the active node must be returned by default search, got: {bodies:?}"
);
assert!(
!bodies.iter().any(|b| b.contains("pending payload")),
"the pending node must be EXCLUDED from default search, got: {bodies:?}"
);
assert!(engine.read_get("act1", &ReadView::default()).expect("read_get active").is_some());
assert!(
engine.read_get("pen1", &ReadView::default()).expect("read_get pending").is_none(),
"read.get must not surface a pending node"
);
let listed: Vec<String> = engine
.read_list("doc", &[], 100, &ReadView::default())
.expect("read_list")
.into_iter()
.map(|n| n.logical_id)
.collect();
assert!(listed.contains(&"act1".to_string()), "read.list must include the active node");
assert!(
!listed.contains(&"pen1".to_string()),
"read.list must exclude the pending node, got: {listed:?}"
);
}
#[test]
fn r_ex_2_graph_traversal_excludes_pending_neighbor() {
let (_d1, o1) = open("rex2_graph_pending");
let e1 = &o1.engine;
e1.write(&[active_node("doc", "root node", "root")]).unwrap();
e1.write(&[pending_node("doc", "neighbor node", "nbr", "quarantine")]).unwrap();
e1.write(&[PreparedWrite::Edge {
kind: "link".to_string(),
from: "root".to_string(),
to: "nbr".to_string(),
source_id: fathomdb_engine::SourceId::new("test:fixture").expect("test source id"),
logical_id: Some("e1".to_string()),
body: None,
t_valid: None,
t_invalid: None,
confidence: None,
extractor_model_id: None,
temporal_fallback: None,
}])
.unwrap();
let neighbors: Vec<String> = e1
.graph_neighbors("root", 1, TraversalDirection::Outgoing, &ReadView::default())
.unwrap()
.into_iter()
.map(|n| n.logical_id)
.collect();
assert!(
!neighbors.contains(&"nbr".to_string()),
"a pending neighbor must be excluded from graph traversal, got: {neighbors:?}"
);
let (_d2, o2) = open("rex2_graph_active");
let e2 = &o2.engine;
e2.write(&[active_node("doc", "root node", "root")]).unwrap();
e2.write(&[active_node("doc", "neighbor node", "nbr")]).unwrap();
e2.write(&[PreparedWrite::Edge {
kind: "link".to_string(),
from: "root".to_string(),
to: "nbr".to_string(),
source_id: fathomdb_engine::SourceId::new("test:fixture").expect("test source id"),
logical_id: Some("e1".to_string()),
body: None,
t_valid: None,
t_invalid: None,
confidence: None,
extractor_model_id: None,
temporal_fallback: None,
}])
.unwrap();
let neighbors2: Vec<String> = e2
.graph_neighbors("root", 1, TraversalDirection::Outgoing, &ReadView::default())
.unwrap()
.into_iter()
.map(|n| n.logical_id)
.collect();
assert!(
neighbors2.contains(&"nbr".to_string()),
"an active neighbor must be surfaced by graph traversal, got: {neighbors2:?}"
);
}
#[test]
fn r_ex_2_vector_search_excludes_superseded_node_version() {
let dir = TempDir::new().unwrap();
let path = dir.path().join(format!("rex2_vector_supersede{SQLITE_SUFFIX}"));
let opened =
Engine::open_with_embedder_for_test(&path, Arc::new(ConstantEmbedder)).expect("open");
let engine = &opened.engine;
engine.configure_vector_kind_for_test("doc").expect("configure vector kind");
engine
.write(&[PreparedWrite::Node {
kind: "doc".to_string(),
body: "quokka original stale body".to_string(),
source_id: fathomdb_engine::SourceId::new("test:fixture").expect("test source id"),
logical_id: Some("L".to_string()),
state: InitialState::Active,
reason: None,
valid_from: None,
valid_until: None,
}])
.expect("write v1");
engine.drain(10_000).expect("drain v1");
engine
.write(&[PreparedWrite::Node {
kind: "doc".to_string(),
body: "quokka revised fresh body".to_string(),
source_id: fathomdb_engine::SourceId::new("test:fixture").expect("test source id"),
logical_id: Some("L".to_string()),
state: InitialState::Active,
reason: None,
valid_from: None,
valid_until: None,
}])
.expect("write v2");
engine.drain(10_000).expect("drain v2");
let hits = engine.search("quokka").expect("search");
let bodies: Vec<&str> = hits.results.iter().map(|h| h.body.as_str()).collect();
assert!(
bodies.iter().any(|b| b.contains("revised fresh body")),
"the current node version must be recalled via vector search, got: {bodies:?}"
);
assert!(
!bodies.iter().any(|b| b.contains("original stale body")),
"a superseded node version must be EXCLUDED from vector search \
(vector hydration must guard `superseded_at IS NULL`), got: {bodies:?}"
);
opened.engine.close().unwrap();
}
#[test]
fn r_ex_2_no_op_on_all_active_corpus() {
let (_dir, opened) = open("rex2_noop");
let engine = &opened.engine;
for i in 0..5 {
engine
.write(&[active_node("doc", &format!("commonterm doc number {i}"), &format!("id{i}"))])
.unwrap();
}
let hits = engine.search("commonterm").expect("search");
assert_eq!(
hits.results.iter().filter(|h| h.body.contains("commonterm")).count(),
5,
"all five active nodes must be returned (state='active' is a no-op on an all-active corpus)"
);
}