use fathomdb_engine::{
Engine, EngineError, InitialState, NodeRecord, PreparedWrite, ReadView, SourceId,
};
use fathomdb_schema::SQLITE_SUFFIX;
use std::path::{Path, PathBuf};
use tempfile::TempDir;
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:slice15b").expect("test source id"),
logical_id: Some(logical_id.to_string()),
state: InitialState::Active,
reason: None,
valid_from,
valid_until,
}
}
fn node(logical_id: &str, body: &str) -> PreparedWrite {
node_win(logical_id, body, None, None)
}
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 row_count(path: &Path, logical_id: &str) -> 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 COUNT(*) FROM canonical_nodes WHERE logical_id = ?1",
[logical_id],
|r| r.get(0),
)
.expect("count")
}
fn at(instant: i64) -> ReadView {
ReadView { valid_as_of: Some(instant), ..ReadView::default() }
}
fn ids(rows: &[NodeRecord]) -> Vec<String> {
let mut out: Vec<String> = rows.iter().map(|r| r.logical_id.clone()).collect();
out.sort();
out
}
#[test]
fn tc34_authored_window_round_trips_through_read_view() {
let dir = TempDir::new().unwrap();
let path = db_path(&dir, "tc34_round_trip");
seed(&path, &[node_win("WINDOWED", "bounded body", Some(1000), Some(2000))]);
assert_eq!(
window_of(&path, "WINDOWED"),
(Some(1000), Some(2000)),
"authored window must land verbatim in canonical_nodes"
);
let opened = Engine::open(path.clone()).unwrap();
let engine = &opened.engine;
assert!(
engine.read_get("WINDOWED", &at(1500)).unwrap().is_some(),
"node must be visible at an instant inside its window"
);
assert_eq!(ids(&engine.read_list("doc", &[], 100, &at(1500)).unwrap()), vec!["WINDOWED"]);
assert!(
engine.read_get("WINDOWED", &at(500)).unwrap().is_none(),
"node must be invisible before valid_from"
);
assert!(engine.read_list("doc", &[], 100, &at(500)).unwrap().is_empty());
assert!(
engine.read_get("WINDOWED", &at(2500)).unwrap().is_none(),
"node must be invisible at/after valid_until"
);
assert!(engine.read_list("doc", &[], 100, &at(2500)).unwrap().is_empty());
let relaxed = ReadView { include_out_of_window: true, ..at(2500) };
assert!(
engine.read_get("WINDOWED", &relaxed).unwrap().is_some(),
"include_out_of_window must still surface an out-of-window authored row"
);
}
#[test]
fn tc34_authored_window_is_half_open() {
let dir = TempDir::new().unwrap();
let path = db_path(&dir, "tc34_half_open");
seed(&path, &[node_win("HALFOPEN", "boundary body", Some(1000), Some(2000))]);
let opened = Engine::open(path).unwrap();
let engine = &opened.engine;
assert!(
engine.read_get("HALFOPEN", &at(999)).unwrap().is_none(),
"one second before valid_from is OUT"
);
assert!(
engine.read_get("HALFOPEN", &at(1000)).unwrap().is_some(),
"exactly valid_from is IN (lower bound inclusive)"
);
assert!(
engine.read_get("HALFOPEN", &at(1999)).unwrap().is_some(),
"one second before valid_until is IN"
);
assert!(
engine.read_get("HALFOPEN", &at(2000)).unwrap().is_none(),
"exactly valid_until is OUT (upper bound exclusive)"
);
}
#[test]
fn tc34_authored_window_supports_unbounded_sides() {
let dir = TempDir::new().unwrap();
let path = db_path(&dir, "tc34_unbounded");
seed(
&path,
&[
node_win("FROM_ONLY", "from only", Some(1000), None),
node_win("UNTIL_ONLY", "until only", None, Some(2000)),
],
);
assert_eq!(
window_of(&path, "FROM_ONLY"),
(Some(1000), None),
"omitted valid_until must land NULL, not a sentinel"
);
assert_eq!(
window_of(&path, "UNTIL_ONLY"),
(None, Some(2000)),
"omitted valid_from must land NULL, not a sentinel"
);
let opened = Engine::open(path).unwrap();
let engine = &opened.engine;
assert!(engine.read_get("FROM_ONLY", &at(999)).unwrap().is_none());
assert!(engine.read_get("FROM_ONLY", &at(1000)).unwrap().is_some());
assert!(engine.read_get("FROM_ONLY", &at(i64::MAX / 2)).unwrap().is_some());
assert!(engine.read_get("UNTIL_ONLY", &at(0)).unwrap().is_some());
assert!(engine.read_get("UNTIL_ONLY", &at(1999)).unwrap().is_some());
assert!(engine.read_get("UNTIL_ONLY", &at(2000)).unwrap().is_none());
}
#[test]
fn tc34_omitted_window_lands_null_null_and_stays_visible() {
let dir = TempDir::new().unwrap();
let path = db_path(&dir, "tc34_default");
seed(&path, &[node("PLAIN", "no window authored")]);
assert_eq!(
window_of(&path, "PLAIN"),
(None, None),
"a write omitting the window MUST land NULL/NULL — not 0, not now(), not a sentinel"
);
let opened = Engine::open(path).unwrap();
let engine = &opened.engine;
for instant in [0_i64, 1, 1000, 2_000_000_000, i64::MAX] {
assert!(
engine.read_get("PLAIN", &at(instant)).unwrap().is_some(),
"NULL/NULL row must be valid at instant {instant}"
);
}
assert!(
engine.read_get("PLAIN", &ReadView::default()).unwrap().is_some(),
"default-view visibility must be unchanged for a window-less write"
);
assert_eq!(
ids(&engine.read_list("doc", &[], 100, &ReadView::default()).unwrap()),
vec!["PLAIN"]
);
}
#[test]
fn tc34_unsatisfiable_window_is_a_typed_refusal() {
let dir = TempDir::new().unwrap();
let path = db_path(&dir, "tc34_invalid");
let opened = Engine::open(path.clone()).unwrap();
let engine = &opened.engine;
let inverted = engine.write(&[node_win("BAD", "inverted", Some(2000), Some(1000))]);
assert!(
matches!(inverted, Err(EngineError::WriteValidation)),
"an inverted window must be a typed WriteValidation refusal, got {inverted:?}"
);
let empty = engine.write(&[node_win("BAD", "empty", Some(1500), Some(1500))]);
assert!(
matches!(empty, Err(EngineError::WriteValidation)),
"an empty half-open window must be a typed refusal, got {empty:?}"
);
assert!(
!matches!(
engine.write(&[node_win("BAD", "inverted", Some(2000), Some(1000))]),
Err(EngineError::InvalidArgument { .. })
),
"validate_write must not raise InvalidArgument for any rejection (decision #18)"
);
engine.close().expect("close");
assert_eq!(row_count(&path, "BAD"), 0, "a refused write must not land a row");
}
#[test]
fn tc34_unsatisfiable_window_rejects_the_whole_batch() {
let dir = TempDir::new().unwrap();
let path = db_path(&dir, "tc34_batch");
let opened = Engine::open(path.clone()).unwrap();
let engine = &opened.engine;
let result = engine
.write(&[node("GOOD", "well formed"), node_win("BAD", "inverted", Some(2000), Some(1000))]);
assert!(matches!(result, Err(EngineError::WriteValidation)));
engine.close().expect("close");
assert_eq!(row_count(&path, "GOOD"), 0, "batch rejection must not commit the sibling row");
assert_eq!(row_count(&path, "BAD"), 0);
}
#[test]
fn tc34_single_bound_is_never_refused() {
let dir = TempDir::new().unwrap();
let path = db_path(&dir, "tc34_one_sided");
let opened = Engine::open(path).unwrap();
opened
.engine
.write(&[
node_win("A", "from only", Some(i64::MAX), None),
node_win("B", "until only", None, Some(i64::MIN)),
])
.expect("a one-sided window can never be unsatisfiable and must be accepted");
}
#[test]
fn tc34_crossed_boundary_since_works_on_an_authored_window() {
let dir = TempDir::new().unwrap();
let path = db_path(&dir, "tc34_crossing");
let opened = Engine::open(path).unwrap();
let engine = &opened.engine;
engine
.write(&[
node_win("OPENED", "became valid", Some(2000), None),
node_win("CLOSED", "became invalid", None, Some(2500)),
node_win("BOTH", "opened and closed", Some(1500), Some(2800)),
node("NEVER", "no window"),
node_win("OUTSIDE", "far future", Some(9000), Some(9500)),
])
.unwrap();
engine.drain(5_000).expect("drain");
let crossings = engine.crossed_boundary_since(1000, &at(3000)).unwrap();
let mut named: Vec<(String, Option<i64>, Option<i64>)> = crossings
.iter()
.map(|c| (c.node.logical_id.clone(), c.became_valid_at, c.became_invalid_at))
.collect();
named.sort();
assert_eq!(
named,
vec![
("BOTH".to_string(), Some(1500), Some(2800)),
("CLOSED".to_string(), None, Some(2500)),
("OPENED".to_string(), Some(2000), None),
],
"the hook must name WHICH boundary each SDK-authored window crossed"
);
}
#[test]
fn tc34_window_is_per_version_under_supersession() {
let dir = TempDir::new().unwrap();
let path = db_path(&dir, "tc34_supersede");
{
let opened = Engine::open(path.clone()).unwrap();
opened.engine.write(&[node_win("V", "first", Some(1000), Some(2000))]).unwrap();
opened.engine.write(&[node_win("V", "second", Some(3000), Some(4000))]).unwrap();
opened.engine.drain(5_000).expect("drain");
opened.engine.close().expect("close");
}
assert_eq!(window_of(&path, "V"), (Some(3000), Some(4000)));
let opened = Engine::open(path).unwrap();
let engine = &opened.engine;
assert!(engine.read_get("V", &at(1500)).unwrap().is_none());
let current = engine.read_get("V", &at(3500)).unwrap().expect("active version visible");
assert_eq!(current.body, "second");
}