use fathomdb_engine::{Engine, InitialState, PreparedWrite, ReadView, SearchResult, SourceId};
use fathomdb_schema::SQLITE_SUFFIX;
use std::path::{Path, PathBuf};
use tempfile::TempDir;
const FAR_FUTURE: i64 = 4_000_000_000;
const FAR_PAST_UNTIL: i64 = 2_000;
fn node_win(
logical_id: &str,
body: &str,
valid_from: Option<i64>,
valid_until: Option<i64>,
) -> PreparedWrite {
PreparedWrite::Node {
kind: "doc".to_string(),
body: body.to_string(),
source_id: SourceId::new("test:s15b-fix2").expect("test source id"),
logical_id: Some(logical_id.to_string()),
state: InitialState::Active,
reason: None,
valid_from,
valid_until,
}
}
fn db_path(dir: &TempDir, name: &str) -> PathBuf {
dir.path().join(format!("{name}{SQLITE_SUFFIX}"))
}
fn seed(path: &Path, batch: &[PreparedWrite]) {
let opened = Engine::open(path.to_path_buf()).expect("open for seed");
opened.engine.write(batch).expect("seed write");
opened.engine.drain(5_000).expect("drain");
opened.engine.close().expect("close");
}
fn window_of(path: &Path, logical_id: &str) -> (Option<i64>, Option<i64>) {
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 valid_from, valid_until FROM canonical_nodes
WHERE logical_id = ?1 AND superseded_at IS NULL",
[logical_id],
|r| Ok((r.get::<_, Option<i64>>(0)?, r.get::<_, Option<i64>>(1)?)),
)
.expect("row present")
}
fn indexed_bodies(path: &Path) -> Vec<String> {
let conn = rusqlite::Connection::open_with_flags(
path,
rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY | rusqlite::OpenFlags::SQLITE_OPEN_URI,
)
.expect("open read-only");
let mut stmt =
conn.prepare("SELECT body FROM search_index ORDER BY write_cursor").expect("prepare");
let rows = stmt.query_map([], |r| r.get::<_, String>(0)).expect("query");
rows.flatten().collect()
}
fn bodies(result: &SearchResult) -> Vec<String> {
let mut out: Vec<String> = result.results.iter().map(|h| h.body.clone()).collect();
out.sort();
out
}
fn at(instant: i64) -> ReadView {
ReadView { valid_as_of: Some(instant), ..ReadView::default() }
}
fn unfiltered() -> ReadView {
ReadView { include_out_of_window: true, ..ReadView::default() }
}
#[test]
fn expired_window_node_is_not_returned_by_default_search() {
let dir = TempDir::new().unwrap();
let path = db_path(&dir, "expired_window");
seed(
&path,
&[
node_win("EXPIRED", "quarterly telemetry report", None, Some(FAR_PAST_UNTIL)),
node_win("ALWAYS", "quarterly telemetry summary", None, None),
],
);
assert_eq!(window_of(&path, "EXPIRED"), (None, Some(FAR_PAST_UNTIL)));
assert_eq!(window_of(&path, "ALWAYS"), (None, None));
let indexed = indexed_bodies(&path);
assert!(
indexed.iter().any(|b| b.contains("telemetry report")),
"expired node must be present in search_index (else the test is vacuous): {indexed:?}"
);
assert!(indexed.iter().any(|b| b.contains("telemetry summary")));
let opened = Engine::open(path.clone()).unwrap();
let engine = &opened.engine;
let hits = engine.search("telemetry").expect("search");
assert_eq!(
bodies(&hits),
vec!["quarterly telemetry summary".to_string()],
"a node whose valid_until is in the past must not leak through default search"
);
opened.engine.close().unwrap();
}
#[test]
fn future_window_node_is_not_returned_by_default_search() {
let dir = TempDir::new().unwrap();
let path = db_path(&dir, "future_window");
seed(
&path,
&[
node_win("PENDING", "embargoed launch memo", Some(FAR_FUTURE), None),
node_win("ALWAYS", "published launch note", None, None),
],
);
assert_eq!(window_of(&path, "PENDING"), (Some(FAR_FUTURE), None));
assert!(indexed_bodies(&path).iter().any(|b| b.contains("embargoed launch memo")));
let opened = Engine::open(path.clone()).unwrap();
let engine = &opened.engine;
assert_eq!(
bodies(&engine.search("launch").expect("search")),
vec!["published launch note".to_string()],
"a node whose valid_from is in the future must not leak through default search"
);
opened.engine.close().unwrap();
}
#[test]
fn covering_window_node_is_returned_by_default_search() {
let dir = TempDir::new().unwrap();
let path = db_path(&dir, "covering_window");
seed(&path, &[node_win("COVERING", "in force policy text", Some(1_000), Some(FAR_FUTURE))]);
assert_eq!(window_of(&path, "COVERING"), (Some(1_000), Some(FAR_FUTURE)));
let opened = Engine::open(path.clone()).unwrap();
assert_eq!(
bodies(&opened.engine.search("policy").expect("search")),
vec!["in force policy text".to_string()],
"a node valid at the current instant must still be returned"
);
opened.engine.close().unwrap();
}
#[test]
fn default_search_is_unchanged_on_a_corpus_with_no_authored_windows() {
let dir = TempDir::new().unwrap();
let path = db_path(&dir, "no_windows");
seed(
&path,
&[
node_win("A", "alpha retrieval corpus", None, None),
node_win("B", "beta retrieval corpus", None, None),
node_win("C", "gamma retrieval corpus", None, None),
],
);
for id in ["A", "B", "C"] {
assert_eq!(
window_of(&path, id),
(None, None),
"a write that omits the window must land NULL/NULL — the no-op premise"
);
}
let opened = Engine::open(path.clone()).unwrap();
let engine = &opened.engine;
let expected = vec![
"alpha retrieval corpus".to_string(),
"beta retrieval corpus".to_string(),
"gamma retrieval corpus".to_string(),
];
assert_eq!(bodies(&engine.search("retrieval").expect("search")), expected);
assert_eq!(bodies(&engine.search_view("retrieval", &at(1)).expect("search")), expected);
assert_eq!(
bodies(&engine.search_view("retrieval", &at(FAR_FUTURE)).expect("search")),
expected
);
assert_eq!(bodies(&engine.search_view("retrieval", &unfiltered()).expect("search")), expected);
opened.engine.close().unwrap();
}
#[test]
fn read_view_on_search_selects_by_instant_and_can_relax_validity() {
let dir = TempDir::new().unwrap();
let path = db_path(&dir, "search_view");
seed(
&path,
&[
node_win("EARLY", "epoch alpha record", Some(1_000), Some(2_000)),
node_win("LATE", "epoch beta record", Some(3_000), None),
],
);
let opened = Engine::open(path.clone()).unwrap();
let engine = &opened.engine;
assert_eq!(
bodies(&engine.search("epoch").expect("search")),
vec!["epoch beta record".to_string()]
);
assert_eq!(
bodies(&engine.search_view("epoch", &at(1_500)).expect("search")),
vec!["epoch alpha record".to_string()],
"valid_as_of must select the node valid at the bound instant"
);
assert!(
bodies(&engine.search_view("epoch", &at(2_000)).expect("search")).is_empty(),
"valid_until is EXCLUSIVE on the search path, as it is on the read verbs"
);
assert_eq!(
bodies(&engine.search_view("epoch", &at(3_000)).expect("search")),
vec!["epoch beta record".to_string()],
"valid_from is INCLUSIVE on the search path"
);
assert!(bodies(&engine.search_view("epoch", &at(2_500)).expect("search")).is_empty());
assert_eq!(
bodies(&engine.search_view("epoch", &unfiltered()).expect("search")),
vec!["epoch alpha record".to_string(), "epoch beta record".to_string()],
"include_out_of_window must return every node whatever its window"
);
opened.engine.close().unwrap();
}
#[test]
fn text_only_search_also_hides_out_of_window_nodes() {
let dir = TempDir::new().unwrap();
let path = db_path(&dir, "text_only_window");
seed(
&path,
&[
node_win("EXPIRED", "retired runbook entry", None, Some(FAR_PAST_UNTIL)),
node_win("ALWAYS", "current runbook entry", None, None),
],
);
let opened = Engine::open(path.clone()).unwrap();
let engine = &opened.engine;
assert_eq!(
bodies(&engine.search_text_only("runbook").expect("search_text_only")),
vec!["current runbook entry".to_string()],
"search_text_only must apply the same validity predicate as search"
);
assert_eq!(
bodies(&engine.search_text_only_view("runbook", &unfiltered()).expect("search")),
vec!["current runbook entry".to_string(), "retired runbook entry".to_string()]
);
opened.engine.close().unwrap();
}
#[test]
fn filtered_and_explained_search_hide_out_of_window_nodes() {
let dir = TempDir::new().unwrap();
let path = db_path(&dir, "entry_points_window");
seed(
&path,
&[
node_win("EXPIRED", "obsolete migration guide", None, Some(FAR_PAST_UNTIL)),
node_win("ALWAYS", "supported migration guide", None, None),
],
);
let opened = Engine::open(path.clone()).unwrap();
let engine = &opened.engine;
let expected = vec!["supported migration guide".to_string()];
assert_eq!(bodies(&engine.search_filtered("migration", None).expect("search")), expected);
assert_eq!(
bodies(&engine.search_reranked("migration", None, 0, false, 0.3, 0).expect("search")),
expected
);
assert_eq!(
bodies(&engine.search_explained("migration", None, 0, false, 0.3, 0).expect("search")),
expected
);
opened.engine.close().unwrap();
}
#[test]
fn search_refuses_a_view_that_relaxes_the_existence_axis() {
let dir = TempDir::new().unwrap();
let path = db_path(&dir, "existence_refusal");
seed(&path, &[node_win("A", "scope guard body", None, None)]);
let opened = Engine::open(path.clone()).unwrap();
let engine = &opened.engine;
for view in [
ReadView { include_superseded: true, ..ReadView::default() },
ReadView { include_inactive: true, ..ReadView::default() },
] {
let err = engine.search_view("scope", &view).expect_err("must refuse");
assert!(
matches!(err, fathomdb_engine::EngineError::InvalidArgument { .. }),
"existence flags on a search view must be a TYPED refusal, never a silent ignore: {err:?}"
);
}
assert_eq!(
bodies(&engine.search_view("scope", &unfiltered()).expect("search")),
vec!["scope guard body".to_string()]
);
opened.engine.close().unwrap();
}