#[path = "common/harness.rs"]
mod harness;
use harness::TestHarness;
use macrame::graph::EdgeAssertion;
use macrame::{BranchId, ConceptUpsert, Database, DbError, Divergence};
use std::sync::Arc;
use std::time::Duration;
const VF: &str = "2020-01-01T00:00:00.000000Z";
const VT: &str = "2021-01-01T00:00:00.000000Z";
const NOW: &str = "2030-01-01T00:00:00.000000Z";
const SENTINEL: &str = "9999-12-31T23:59:59.999999Z";
const STEP: Duration = Duration::from_secs(86_400);
async fn seed(h: &TestHarness) -> Database {
let db = h.db_with_fake_clock().await;
for id in ["a", "b", "c", "d"] {
db.upsert_concept(ConceptUpsert::new(id, "N").valid_from(VF))
.await
.unwrap();
}
for (source, target) in [("a", "b"), ("b", "c"), ("c", "d")] {
db.assert_edge(EdgeAssertion::new(source, target, "LEADSTO").valid_from(VF))
.await
.unwrap();
}
h.advance(STEP);
db
}
fn id(name: &str) -> BranchId {
BranchId::new(name).unwrap()
}
fn shown(rows: &[Divergence]) -> Vec<String> {
rows.iter()
.map(|d| {
format!(
"{}→{} on {} w{} {}",
d.source_id,
d.target_id,
d.branch_id,
d.weight,
if d.valid_to == SENTINEL {
"open"
} else {
"closed"
}
)
})
.collect()
}
async fn rows_on(db: &Database, branch: &str) -> i64 {
db.read_conn()
.query(
"SELECT COUNT(*) FROM links WHERE branch_id = ?1",
libsql::params![branch],
)
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap()
}
#[tokio::test]
async fn a_fork_that_wrote_nothing_diverges_in_nothing() {
let h = TestHarness::new();
let db = seed(&h).await;
let alt = db.fork(id("alt"), BranchId::main()).await.unwrap();
assert_eq!(
shown(&db.diff(&alt.id, &BranchId::main()).await.unwrap()),
[] as [String; 0]
);
assert_eq!(
shown(&db.diff(&BranchId::main(), &alt.id).await.unwrap()),
[] as [String; 0]
);
db.close().await.unwrap();
}
#[tokio::test]
async fn an_assertion_on_the_branch_is_what_the_branch_concluded() {
let h = TestHarness::new();
let db = seed(&h).await;
let alt = db.fork(id("alt"), BranchId::main()).await.unwrap();
h.advance(STEP);
db.assert_edge(
EdgeAssertion::new("a", "c", "LEADSTO")
.valid_from(VF)
.on_branch(alt.id.clone()),
)
.await
.unwrap();
assert_eq!(
shown(&db.diff(&alt.id, &BranchId::main()).await.unwrap()),
["a→c on alt w1 open"]
);
assert_eq!(
shown(&db.diff(&BranchId::main(), &alt.id).await.unwrap()),
[] as [String; 0],
"the trunk believes nothing the branch does not: the branch only added"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_reweight_is_a_divergence_on_both_sides_and_they_disagree() {
let h = TestHarness::new();
let db = seed(&h).await;
let alt = db.fork(id("alt"), BranchId::main()).await.unwrap();
h.advance(STEP);
db.assert_edge(
EdgeAssertion::new("a", "b", "LEADSTO")
.valid_from(VF)
.weight(0.25)
.on_branch(alt.id.clone()),
)
.await
.unwrap();
assert_eq!(
shown(&db.diff(&alt.id, &BranchId::main()).await.unwrap()),
["a→b on alt w0.25 open"]
);
assert_eq!(
shown(&db.diff(&BranchId::main(), &alt.id).await.unwrap()),
["a→b on main w1 open"],
"the same key from the other side, carrying the trunk's belief"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_shadow_retirement_is_a_divergence_no_instant_filtered_read_can_show() {
let h = TestHarness::new();
let db = seed(&h).await;
let alt = db.fork(id("alt"), BranchId::main()).await.unwrap();
h.advance(STEP);
db.retire_edge_on("b", "c", "LEADSTO", VF, VT, alt.id.clone())
.await
.unwrap();
assert_eq!(
shown(&db.diff(&alt.id, &BranchId::main()).await.unwrap()),
["b→c on alt w1 closed"]
);
assert_eq!(
shown(&db.diff(&BranchId::main(), &alt.id).await.unwrap()),
["b→c on main w1 open"],
"and the trunk still believes what the branch retired"
);
let live = macrame::temporal::query_as_of_edges_on(db.read_conn(), NOW, Some(alt.id.as_str()))
.await
.unwrap();
let mut pairs: Vec<_> = live.iter().map(|e| (e.0.as_str(), e.1.as_str())).collect();
pairs.sort_unstable();
assert_eq!(
pairs,
[("a", "b"), ("c", "d")],
"an instant-filtered read shows the retirement as a shorter list: \
`b → c` is simply not in it, and this reader is not anchored at a \
start node, so `c → d` stays. A diff built on it would report \
nothing at all from the branch's side"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn the_divergence_can_be_a_row_the_other_lineage_wrote() {
let h = TestHarness::new();
let db = seed(&h).await;
let alt = db.fork(id("alt"), BranchId::main()).await.unwrap();
h.advance(STEP);
db.assert_edge(
EdgeAssertion::new("b", "c", "LEADSTO")
.valid_from(VF)
.weight(0.25),
)
.await
.unwrap();
assert_eq!(rows_on(&db, "alt").await, 0, "the branch wrote nothing");
assert_eq!(
shown(&db.diff(&alt.id, &BranchId::main()).await.unwrap()),
["b→c on main w1 open"],
"a lineage that has written nothing still believes something the trunk \
does not, and the row carries the trunk's own id"
);
assert_eq!(
shown(&db.diff(&BranchId::main(), &alt.id).await.unwrap()),
["b→c on main w0.25 open"],
"the same key from the trunk's side, at the weight it corrected to"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn siblings_diverge_through_their_common_ancestor() {
let h = TestHarness::new();
let db = seed(&h).await;
let b1 = db.fork(id("b1"), BranchId::main()).await.unwrap();
let b2 = db.fork(id("b2"), BranchId::main()).await.unwrap();
h.advance(STEP);
db.retire_edge_on("c", "d", "LEADSTO", VF, VT, b2.id.clone())
.await
.unwrap();
assert_eq!(rows_on(&db, "b1").await, 0);
assert_eq!(
shown(&db.diff(&b1.id, &b2.id).await.unwrap()),
["c→d on main w1 open"]
);
assert_eq!(
shown(&db.diff(&b2.id, &b1.id).await.unwrap()),
["c→d on b2 w1 closed"]
);
db.close().await.unwrap();
}
#[tokio::test]
async fn re_asserting_what_was_already_believed_is_not_a_divergence() {
let h = TestHarness::new();
let db = seed(&h).await;
let alt = db.fork(id("alt"), BranchId::main()).await.unwrap();
h.advance(STEP);
db.assert_edge(
EdgeAssertion::new("a", "b", "LEADSTO")
.valid_from(VF)
.on_branch(alt.id.clone()),
)
.await
.unwrap();
assert_eq!(rows_on(&db, "alt").await, 1, "the write did land");
assert_eq!(
shown(&db.diff(&alt.id, &BranchId::main()).await.unwrap()),
[] as [String; 0],
"a row that restates what was already believed is not a conclusion"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn divergences_come_back_in_edge_key_order() {
let h = TestHarness::new();
let db = seed(&h).await;
let alt = db.fork(id("alt"), BranchId::main()).await.unwrap();
h.advance(STEP);
for (source, target) in [("c", "a"), ("a", "d"), ("b", "d")] {
db.assert_edge(
EdgeAssertion::new(source, target, "LEADSTO")
.valid_from(VF)
.on_branch(alt.id.clone()),
)
.await
.unwrap();
}
assert_eq!(
shown(&db.diff(&alt.id, &BranchId::main()).await.unwrap()),
[
"a→d on alt w1 open",
"b→d on alt w1 open",
"c→a on alt w1 open"
]
);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_lineage_does_not_diverge_from_itself() {
let h = TestHarness::new();
let db = seed(&h).await;
assert_eq!(
db.diff(&BranchId::main(), &BranchId::main())
.await
.unwrap()
.len(),
0,
"a ledger that never forked"
);
let alt = db.fork(id("alt"), BranchId::main()).await.unwrap();
h.advance(STEP);
db.assert_edge(
EdgeAssertion::new("a", "c", "LEADSTO")
.valid_from(VF)
.on_branch(alt.id.clone()),
)
.await
.unwrap();
for name in [BranchId::main(), alt.id.clone()] {
assert_eq!(
db.diff(&name, &name).await.unwrap().len(),
0,
"{name} diverged from itself"
);
}
db.close().await.unwrap();
}
#[tokio::test]
async fn an_unregistered_lineage_is_refused_by_name() {
let h = TestHarness::new();
let db = seed(&h).await;
db.fork(id("alt"), BranchId::main()).await.unwrap();
for (a, b, named) in [
("ghost", "main", "ghost"),
("main", "ghost", "ghost"),
("alt", "phantom", "phantom"),
("ghost", "phantom", "ghost"),
] {
let err = db
.diff(&id(a), &id(b))
.await
.expect_err("the diff answered for a lineage that does not exist");
assert!(
matches!(err, DbError::UnknownBranch(ref w) if w == named),
"diff({a}, {b}) named the wrong lineage: {err:?}"
);
}
db.close().await.unwrap();
}
#[tokio::test]
async fn the_view_diffs_from_its_own_lineage() {
let h = TestHarness::new();
let db = Arc::new(seed(&h).await);
let alt = db.fork(id("alt"), BranchId::main()).await.unwrap();
h.advance(STEP);
db.assert_edge(
EdgeAssertion::new("a", "c", "LEADSTO")
.valid_from(VF)
.on_branch(alt.id.clone()),
)
.await
.unwrap();
let view = db.view(alt.id.clone());
assert_eq!(
view.diff(&BranchId::main()).await.unwrap(),
db.diff(&alt.id, &BranchId::main()).await.unwrap()
);
assert_eq!(
shown(&view.diff(&BranchId::main()).await.unwrap()),
["a→c on alt w1 open"]
);
drop(view);
Arc::try_unwrap(db)
.unwrap_or_else(|_| panic!("the view outlived the assertion"))
.close()
.await
.unwrap();
}