#[path = "common/harness.rs"]
mod harness;
use std::time::Duration;
use harness::TestHarness;
use macrame::graph::EdgeAssertion;
use macrame::integrity::audit_current;
use macrame::{ConceptUpsert, Database, DbError};
const EPOCH: &str = "1970-01-01T00:00:00.000000Z";
const OPEN: &str = "9999-12-31T23:59:59.999999Z";
const HOUR: Duration = Duration::from_secs(3_600);
async fn seed(db: &Database, harness: &TestHarness) {
let ids: Vec<String> = (0..13).map(|i| format!("c{i:03}")).collect();
db.write_concepts(
ids.iter()
.map(|id| ConceptUpsert::new(id, "n").valid_from(EPOCH))
.collect(),
)
.await
.unwrap();
for generation in 0..4 {
let batch: Vec<_> = (0..12)
.map(|k| {
EdgeAssertion::new(&ids[k], &ids[k + 1], "LINKS")
.valid_from(EPOCH)
.valid_to(OPEN)
.weight(1.0 + generation as f64)
})
.collect();
db.bulk_import(batch).await.unwrap();
harness.advance(HOUR);
}
}
async fn observable(db: &Database) -> (Vec<(String, String, f64)>, i64, Option<i64>) {
let conn = db.read_conn();
let mut current = Vec::new();
let mut rows = conn
.query(
"SELECT source_id, target_id, weight FROM links_current \
ORDER BY source_id, target_id",
(),
)
.await
.unwrap();
while let Some(row) = rows.next().await.unwrap() {
current.push((
row.get(0).unwrap(),
row.get(1).unwrap(),
row.get(2).unwrap(),
));
}
let hot_links: i64 = conn
.query("SELECT COUNT(*) FROM links", ())
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap();
let horizon: Option<i64> = conn
.query("SELECT MIN(seq_id) FROM transaction_log", ())
.await
.unwrap()
.next()
.await
.unwrap()
.and_then(|r| r.get(0).ok());
(current, hot_links, horizon)
}
#[tokio::test]
async fn windowing_leaves_the_same_database_a_single_session_would() {
let one = {
let harness = TestHarness::new();
let db = harness.db_with_fake_clock().await;
seed(&db, &harness).await;
let cutoff = harness.clock.peek();
let report = db.archive(&cutoff).await.unwrap();
let state = observable(&db).await;
assert_eq!(audit_current(db.read_conn()).await.unwrap(), 0);
db.close().await.unwrap();
(report.links_archived, state)
};
let many = {
let harness = TestHarness::new();
let db = harness.db_with_fake_clock().await;
seed(&db, &harness).await;
let cutoff = harness.clock.peek();
let reports = db.archive_windowed(&cutoff, HOUR).await.unwrap();
assert!(
reports.len() > 1,
"the fixture spans four hours of transaction time but produced \
{} session(s) — the boundaries are not being derived from the data",
reports.len()
);
let state = observable(&db).await;
assert_eq!(
audit_current(db.read_conn()).await.unwrap(),
0,
"a windowed run induced drift in links_current"
);
db.close().await.unwrap();
(
reports.iter().map(|r| r.links_archived).sum::<usize>(),
state,
)
};
assert_eq!(
one.0, many.0,
"the two runs archived different numbers of links"
);
assert_eq!(one.1, many.1, "the two runs left different databases");
assert!(
one.0 > 0,
"the fixture archived nothing, so it proves nothing"
);
}
#[tokio::test]
async fn a_window_wider_than_the_history_collapses_to_one_session() {
let harness = TestHarness::new();
let db = harness.db_with_fake_clock().await;
seed(&db, &harness).await;
let cutoff = harness.clock.peek();
let reports = db
.archive_windowed(&cutoff, Duration::from_secs(86_400 * 365))
.await
.unwrap();
assert_eq!(reports.len(), 1);
db.close().await.unwrap();
}
#[tokio::test]
async fn an_empty_database_still_gets_one_session_and_a_horizon_row() {
let harness = TestHarness::new();
let db = harness.db_with_fake_clock().await;
let reports = db
.archive_windowed("2026-01-01T00:00:00.000000Z", HOUR)
.await
.unwrap();
assert_eq!(reports.len(), 1);
assert_eq!(reports[0].links_archived, 0);
assert!(
db.archive_path().exists(),
"no cold file, so no horizon row was written"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_zero_window_is_refused() {
let harness = TestHarness::new();
let db = harness.db_with_fake_clock().await;
seed(&db, &harness).await;
let cutoff = harness.clock.peek();
let err = db
.archive_windowed(&cutoff, Duration::ZERO)
.await
.expect_err("a zero-length window never advances");
assert!(matches!(err, DbError::ArchiveWindow { .. }), "got {err:?}");
db.close().await.unwrap();
}
#[tokio::test]
async fn a_window_needing_more_than_the_session_limit_is_refused() {
let harness = TestHarness::new();
let db = harness.db_with_fake_clock().await;
seed(&db, &harness).await;
let cutoff = harness.clock.peek();
let err = db
.archive_windowed(&cutoff, Duration::from_millis(1))
.await
.expect_err("14 million archive sessions is not what anyone meant");
match err {
DbError::ArchiveWindow { ref reason, .. } => {
assert!(
reason.contains(¯ame::connection::MAX_ARCHIVE_SESSIONS.to_string()),
"the refusal must quote the limit it is enforcing: {reason}"
);
}
other => panic!("got {other:?}"),
}
assert!(
!db.archive_path().exists(),
"a refused window still opened a cold file"
);
db.close().await.unwrap();
}