#[path = "common/harness.rs"]
mod harness;
use harness::TestHarness;
use macrame::graph::{EdgeAssertion, TraversalBuilder, WalkOutcome};
use macrame::{BranchId, ConceptUpsert, Database, ReadPlan};
const TS: &str = "2026-01-01T00:00:00.000000Z";
const NOW: &str = "2026-06-01T00:00:00.000000Z";
fn harness() -> TestHarness {
TestHarness::starting_at(macrame::util::parse_iso8601_utc(TS).unwrap())
}
async fn near_far(h: &TestHarness) -> Database {
let db = h.db_with_fake_clock().await;
let mut ids = vec!["m0".to_string()];
for z in 1..=3 {
ids.push(format!("z{z}"));
for a in 1..=3 {
ids.push(format!("a{z}{a}"));
}
}
for id in &ids {
db.upsert_concept(ConceptUpsert::new(id, "N").valid_from(TS))
.await
.unwrap();
}
for z in 1..=3 {
db.assert_edge(EdgeAssertion::new("m0", format!("z{z}"), "KNOWS").valid_from(TS))
.await
.unwrap();
for a in 1..=3 {
db.assert_edge(
EdgeAssertion::new(format!("z{z}"), format!("a{z}{a}"), "KNOWS").valid_from(TS),
)
.await
.unwrap();
}
}
db
}
#[tokio::test]
async fn a_limited_walk_keeps_the_nodes_nearest_the_start() {
let h = harness();
let db = near_far(&h).await;
let (ids, outcome) = TraversalBuilder::new("m0")
.max_depth(3)
.limit(4)
.execute_ids_explained(db.read_conn(), NOW)
.await
.unwrap();
assert_eq!(
ids,
vec!["m0", "z1", "z2", "z3"],
"a limit on the sorted projection would have answered with the `a`s"
);
assert_eq!(outcome, WalkOutcome::LimitReached);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_limit_the_walk_never_reaches_reports_a_complete_answer() {
let h = harness();
let db = near_far(&h).await;
let unlimited = TraversalBuilder::new("m0")
.max_depth(3)
.execute_ids(db.read_conn(), NOW)
.await
.unwrap();
let (ids, outcome) = TraversalBuilder::new("m0")
.max_depth(3)
.limit(1_000)
.execute_ids_explained(db.read_conn(), NOW)
.await
.unwrap();
assert_eq!(ids, unlimited, "a slack ceiling must not change the answer");
assert_eq!(outcome, WalkOutcome::Complete);
assert!(!outcome.hit_limit());
db.close().await.unwrap();
}
#[tokio::test]
async fn an_unlimited_walk_emits_the_statement_it_always_did() {
let h = harness();
let db = near_far(&h).await;
let plain = TraversalBuilder::new("m0").max_depth(3);
let sql = plain.build_sql();
assert!(
!sql.contains("LIMIT") && !sql.contains("SELECT COUNT(*) FROM walk"),
"an unlimited traversal must not carry a ceiling or its reporting: {sql}"
);
assert!(
sql.contains("SELECT DISTINCT w.node_id\nFROM walk w JOIN concepts c"),
"the unlimited projection must be the one every plan pin asserts: {sql}"
);
let (_, outcome) = plain
.execute_ids_explained(db.read_conn(), NOW)
.await
.unwrap();
assert_eq!(outcome, WalkOutcome::Complete);
db.close().await.unwrap();
}
#[tokio::test]
async fn the_ceiling_sits_inside_the_recursive_cte() {
let sql = TraversalBuilder::new("m0").limit(4).build_sql();
let cte_end = sql.find("\n)").expect("the walk CTE must close");
let limit_at = sql
.find("LIMIT ?")
.expect("a limited walk must carry a LIMIT");
assert!(
limit_at < cte_end,
"the LIMIT must sit inside the recursion, not after it: {sql}"
);
assert!(
!sql.contains("LIMIT 4"),
"the ceiling is bound, not spliced: {sql}"
);
}
#[tokio::test]
async fn every_optional_slot_can_be_occupied_at_once() {
let h = harness();
let db = near_far(&h).await;
let alt = BranchId::new("alt").unwrap();
db.fork(alt.clone(), BranchId::main()).await.unwrap();
let recorded = db.clock().now();
let (ids, outcome) = TraversalBuilder::new("m0")
.max_depth(3)
.on_branch(alt.as_str())
.as_of_recorded(&recorded)
.edge_types(vec!["KNOWS".to_string(), "CITES".to_string()])
.limit(4)
.execute_ids_explained(db.read_conn(), NOW)
.await
.unwrap();
assert_eq!(ids, vec!["m0", "z1", "z2", "z3"]);
assert_eq!(outcome, WalkOutcome::LimitReached);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_walk_whose_every_concept_is_retired_still_reports_the_ceiling() {
let h = harness();
let db = near_far(&h).await;
for z in 1..=3 {
for a in 1..=3 {
db.upsert_concept(
ConceptUpsert::new(format!("a{z}{a}"), "N")
.valid_from(TS)
.retired(true),
)
.await
.unwrap();
}
db.upsert_concept(
ConceptUpsert::new(format!("z{z}"), "N")
.valid_from(TS)
.retired(true),
)
.await
.unwrap();
}
db.upsert_concept(ConceptUpsert::new("m0", "N").valid_from(TS).retired(true))
.await
.unwrap();
let (ids, outcome) = TraversalBuilder::new("m0")
.max_depth(3)
.limit(4)
.execute_ids_explained(db.read_conn(), NOW)
.await
.unwrap();
assert!(ids.is_empty(), "every concept is retired: {ids:?}");
assert_eq!(
outcome,
WalkOutcome::LimitReached,
"the walk spent its whole budget before the projection dropped the rows"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_node_reached_twice_spends_the_ceiling_twice() {
let h = harness();
let db = h.db_with_fake_clock().await;
for id in ["p", "q", "r"] {
db.upsert_concept(ConceptUpsert::new(id, "N").valid_from(TS))
.await
.unwrap();
}
for (s, t) in [("p", "q"), ("q", "r"), ("p", "r")] {
db.assert_edge(EdgeAssertion::new(s, t, "KNOWS").valid_from(TS))
.await
.unwrap();
}
let (ids, outcome) = TraversalBuilder::new("p")
.max_depth(2)
.limit(4)
.execute_ids_explained(db.read_conn(), NOW)
.await
.unwrap();
assert_eq!(ids, vec!["p", "q", "r"]);
assert_eq!(
outcome,
WalkOutcome::LimitReached,
"four walk rows at a ceiling of four is a cut walk, and the three ids it answered with cannot say so"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_ceiling_survives_a_traversal_builder_in_both_directions() {
let plan = ReadPlan::new()
.on(BranchId::new("alt").unwrap())
.valid_at(TS)
.recorded_at(NOW)
.limit(7);
let b = TraversalBuilder::new("m0").plan(plan.clone());
assert_eq!(b.limit, Some(7));
assert_eq!(b.read_plan().unwrap(), plan);
let cleared = b.plan(ReadPlan::new());
assert_eq!(cleared.limit, None, "an empty plan clears the ceiling too");
assert_eq!(cleared.read_plan().unwrap(), ReadPlan::new());
}
#[tokio::test]
async fn a_plan_ceiling_bounds_the_whole_ledger_read() {
let h = harness();
let db = near_far(&h).await;
let all = db.edges(ReadPlan::new().valid_at(NOW)).await.unwrap();
assert_eq!(all.len(), 12, "3 hub edges and 9 leaf edges");
let some = db
.edges(ReadPlan::new().valid_at(NOW).limit(5))
.await
.unwrap();
assert_eq!(some.len(), 5);
assert!(
some.iter().all(|e| all.contains(e)),
"a limited read must be a subset of the unlimited one"
);
db.close().await.unwrap();
}