#[path = "common/harness.rs"]
mod harness;
use harness::TestHarness;
use macrame::graph::{EdgeAssertion, TraversalBuilder};
use macrame::{Branch, BranchId, ConceptUpsert, Database, DbError};
use std::time::Duration;
async fn raw(h: &TestHarness) -> libsql::Connection {
libsql::Builder::new_local(&h.db_path)
.build()
.await
.unwrap()
.connect()
.unwrap()
}
async fn count(db: &Database, sql: &str) -> i64 {
db.read_conn()
.query(sql, ())
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap()
}
async fn ledger_counts(db: &Database) -> [i64; 4] {
[
count(db, "SELECT COUNT(*) FROM links").await,
count(db, "SELECT COUNT(*) FROM links_current").await,
count(db, "SELECT COUNT(*) FROM concepts").await,
count(db, "SELECT COUNT(*) FROM transaction_log").await,
]
}
const VALID_FROM: &str = "2020-01-01T00:00:00.000000Z";
async fn nodes(db: &Database, ids: &[&str]) {
for id in ids {
db.upsert_concept(ConceptUpsert::new(*id, "N").valid_from(VALID_FROM))
.await
.unwrap();
}
}
async fn edge(db: &Database, source: &str, target: &str) {
db.assert_edge(EdgeAssertion::new(source, target, "LEADSTO").valid_from(VALID_FROM))
.await
.unwrap();
}
#[tokio::test]
async fn a_fresh_ledger_has_exactly_one_lineage_and_it_is_the_trunk() {
let h = TestHarness::new();
let db = h.db_with_fake_clock().await;
let all = db.branches().await.unwrap();
assert_eq!(all.len(), 1, "{all:?}");
let trunk = &all[0];
assert_eq!(trunk.id.as_str(), "main");
assert!(trunk.id.is_main());
assert_eq!(trunk.parent, None, "the trunk is nobody's child");
assert_eq!(
trunk.forked_at, None,
"and it was not cut from anything, which is the same fact"
);
assert!(!trunk.created_at.is_empty());
db.close().await.unwrap();
}
#[tokio::test]
async fn a_thousand_forks_write_a_thousand_rows_and_nothing_else() {
let h = TestHarness::new();
let db = h.db_with_fake_clock().await;
nodes(&db, &["a", "b", "c"]).await;
edge(&db, "a", "b").await;
edge(&db, "b", "c").await;
let before = ledger_counts(&db).await;
assert!(before[0] > 0, "the fixture must not be empty: {before:?}");
for i in 0..1_000 {
db.fork(BranchId::new(format!("alt/{i}")).unwrap(), BranchId::main())
.await
.unwrap();
}
assert_eq!(
ledger_counts(&db).await,
before,
"a fork touched a ledger table; the design's whole cost model is that it does not"
);
assert_eq!(count(&db, "SELECT COUNT(*) FROM branches").await, 1_001);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_fork_is_readable_the_moment_it_exists() {
let h = TestHarness::new();
let db = h.db_with_fake_clock().await;
nodes(&db, &["a", "b", "c", "d"]).await;
edge(&db, "a", "b").await;
edge(&db, "b", "c").await;
h.advance(Duration::from_secs(60));
let alt = db
.fork(BranchId::new("alt").unwrap(), BranchId::main())
.await
.unwrap();
h.advance(Duration::from_secs(60));
edge(&db, "c", "d").await;
let now = "2100-01-01T00:00:00.000000Z";
let trunk = TraversalBuilder::new("a")
.execute_ids(db.read_conn(), now)
.await
.unwrap();
let branch = TraversalBuilder::new("a")
.on_branch(alt.id.clone())
.execute_ids(db.read_conn(), now)
.await
.unwrap();
let sorted = |mut v: Vec<String>| {
v.sort();
v
};
assert_eq!(sorted(trunk), ["a", "b", "c", "d"]);
assert_eq!(
sorted(branch),
["a", "b", "c"],
"the branch absorbed a write its parent made after the fork point"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn the_returned_branch_is_the_row_that_was_written() {
let h = TestHarness::new();
let db = h.db_with_fake_clock().await;
let returned = db
.fork(BranchId::new("alt").unwrap(), BranchId::main())
.await
.unwrap();
let listed: Vec<Branch> = db.branches().await.unwrap();
let stored = listed.iter().find(|b| b.id.as_str() == "alt").unwrap();
assert_eq!(&returned, stored, "the handle and the row disagree");
assert_eq!(returned.parent.as_ref().unwrap().as_str(), "main");
assert_eq!(
returned.forked_at.as_deref(),
Some(returned.created_at.as_str()),
"this release forks from now, so the two columns hold one instant"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn the_listing_is_trunk_first_then_creation_order() {
let h = TestHarness::new();
let db = h.db_with_fake_clock().await;
for name in ["first", "second", "third"] {
h.advance(Duration::from_secs(1));
db.fork(BranchId::new(name).unwrap(), BranchId::main())
.await
.unwrap();
}
h.advance(Duration::from_secs(1));
db.fork(
BranchId::new("fourth").unwrap(),
BranchId::new("second").unwrap(),
)
.await
.unwrap();
let names: Vec<String> = db
.branches()
.await
.unwrap()
.into_iter()
.map(|b| b.id.to_string())
.collect();
assert_eq!(names, ["main", "first", "second", "third", "fourth"]);
db.close().await.unwrap();
}
#[tokio::test]
async fn forking_from_a_lineage_that_does_not_exist_names_it() {
let h = TestHarness::new();
let db = h.db_with_fake_clock().await;
let err = db
.fork(
BranchId::new("alt").unwrap(),
BranchId::new("ghost").unwrap(),
)
.await
.unwrap_err();
match err {
DbError::UnknownBranch(ref what) => assert_eq!(what, "ghost"),
other => panic!("a foreign-key violation reached the caller: {other}"),
}
assert_eq!(
count(&db, "SELECT COUNT(*) FROM branches").await,
1,
"the refused fork left a row behind"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_taken_name_is_refused_rather_than_quietly_returning_the_other_branch() {
let h = TestHarness::new();
let db = h.db_with_fake_clock().await;
db.fork(BranchId::new("alt").unwrap(), BranchId::main())
.await
.unwrap();
h.advance(Duration::from_secs(60));
db.fork(BranchId::new("other").unwrap(), BranchId::main())
.await
.unwrap();
let err = db
.fork(
BranchId::new("alt").unwrap(),
BranchId::new("other").unwrap(),
)
.await
.unwrap_err();
assert!(
matches!(err, DbError::BranchExists(ref w) if w == "alt"),
"{err}"
);
let alt = db.branches().await.unwrap();
let alt = alt.iter().find(|b| b.id.as_str() == "alt").unwrap();
assert_eq!(
alt.parent.as_ref().unwrap().as_str(),
"main",
"the original row was rewritten, which `branches` triggers should have refused"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn the_trunk_cannot_be_forked_into_existence_twice() {
let h = TestHarness::new();
let db = h.db_with_fake_clock().await;
let err = db
.fork(BranchId::main(), BranchId::main())
.await
.unwrap_err();
assert!(
matches!(err, DbError::BranchExists(ref w) if w == "main"),
"{err}"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_fork_point_before_its_parent_existed_is_refused() {
let h = TestHarness::new();
let db = h.db_with_fake_clock().await;
raw(&h)
.await
.execute(
"INSERT INTO branches (branch_id, parent_id, forked_at, created_at) \
VALUES ('ahead', 'main', ?1, ?1)",
libsql::params!["2999-01-01T00:00:00.000000Z"],
)
.await
.unwrap();
let err = db
.fork(
BranchId::new("behind").unwrap(),
BranchId::new("ahead").unwrap(),
)
.await
.unwrap_err();
match err {
DbError::ForkPrecedesParent {
ref branch,
ref parent,
ref parent_forked_at,
..
} => {
assert_eq!(branch, "behind");
assert_eq!(parent, "ahead");
assert_eq!(parent_forked_at, "2999-01-01T00:00:00.000000Z");
}
other => panic!("{other}"),
}
assert_eq!(
count(
&db,
"SELECT COUNT(*) FROM branches WHERE branch_id = 'behind'"
)
.await,
0
);
db.close().await.unwrap();
}
#[tokio::test]
async fn an_invalid_name_never_reaches_the_database() {
assert!(BranchId::new("trailing ").is_err());
assert!(BranchId::new("").is_err());
let h = TestHarness::new();
let db = h.db_with_fake_clock().await;
assert_eq!(count(&db, "SELECT COUNT(*) FROM branches").await, 1);
db.close().await.unwrap();
}