use std::path::Path;
use topodb::{Db, NodeId, Op, PropValue, Props, Scope, ScopeId, ScopeSet};
fn digest(path: &Path) -> (u64, Vec<u8>) {
let bytes = std::fs::read(path).expect("read db file");
(bytes.len() as u64, bytes)
}
fn seed(path: &Path) {
let db = Db::open(path).expect("open");
let scope = Scope::Id(ScopeId::new());
let mut props = Props::new();
props.insert("name".into(), PropValue::Str("alpha".into()));
props.insert("rank".into(), PropValue::Int(7));
db.submit_at(
vec![Op::CreateNode {
id: NodeId::new(),
scope,
label: "Memory".into(),
props,
}],
1,
)
.expect("submit");
}
#[test]
fn reopening_an_unchanged_database_does_not_modify_the_file() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("ro.redb");
seed(&path);
let before = digest(&path);
{
let _db = Db::open(&path).expect("reopen");
}
let after = digest(&path);
assert_eq!(
before.0, after.0,
"reopen changed the file length: {} -> {}",
before.0, after.0
);
assert!(
before.1 == after.1,
"reopen modified the file contents despite nothing needing to be written"
);
}
#[test]
fn repeated_reopens_are_all_no_ops() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("ro_repeat.redb");
seed(&path);
let baseline = digest(&path);
for i in 0..3 {
{
let _db = Db::open(&path).expect("reopen");
}
let now = digest(&path);
assert!(
baseline.1 == now.1,
"reopen #{i} modified the file contents"
);
}
}
#[test]
fn v5_fixture_migrates_on_first_open_then_takes_the_fast_path() {
use topodb::{IndexSpec, PropIndex, ScopeSet};
let src = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/v5.redb");
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("v5.redb");
std::fs::copy(&src, &path).expect("copy fixture");
let spec = IndexSpec {
equality: vec![PropIndex {
label: "Entity".into(),
prop: "name".into(),
}],
text: vec![PropIndex {
label: "Memory".into(),
prop: "content".into(),
}],
};
let before_migration = digest(&path);
{
let db = Db::open_with(&path, spec.clone()).expect("open v5 fixture");
let scopes = ScopeSet::of(&[topodb::ScopeId::from_u128(1)]);
let entities = db.nodes_by_label(&scopes, "Entity");
assert_eq!(
entities.len(),
1,
"LABEL_INDEX must be populated by the v5 -> v6 migration"
);
assert_eq!(db.nodes_by_label(&scopes, "Memory").len(), 1);
}
let after_migration = digest(&path);
assert!(
before_migration.1 != after_migration.1,
"a v5 file must be migrated on open, not skipped by the read-only fast path"
);
{
let _db = Db::open_with(&path, spec.clone()).expect("reopen migrated file");
}
let after_reopen = digest(&path);
assert!(
after_migration.1 == after_reopen.1,
"reopening the migrated file must take the read-only fast path"
);
let db = Db::open_with(&path, spec).expect("final open");
let scopes = ScopeSet::of(&[topodb::ScopeId::from_u128(1)]);
assert_eq!(db.nodes_by_label(&scopes, "Entity").len(), 1);
assert_eq!(
db.search_text(&scopes, "databases", 10)
.expect("text search")
.len(),
1,
"text index survives migration + fast-path reopen"
);
}
#[test]
fn a_reopened_database_still_reads_correctly() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("ro_reads.redb");
let id = NodeId::new();
let scope = Scope::Id(ScopeId::new());
{
let db = Db::open(&path).expect("open");
let mut props = Props::new();
props.insert("name".into(), PropValue::Str("beta".into()));
db.submit_at(
vec![Op::CreateNode {
id,
scope,
label: "Memory".into(),
props,
}],
1,
)
.expect("submit");
}
let db = Db::open(&path).expect("reopen");
let scope_id = match scope {
Scope::Id(v) => v,
Scope::Shared => unreachable!("test uses a scoped node"),
};
let scopes = ScopeSet::of(&[scope_id]);
let node = db.node(&scopes, id).expect("node survives reopen");
assert_eq!(node.label.as_str(), "Memory");
assert_eq!(
node.props.get("name"),
Some(&PropValue::Str("beta".into())),
"props readable after a reopen that took the read-only path"
);
let id2 = NodeId::new();
db.submit_at(
vec![Op::CreateNode {
id: id2,
scope,
label: "Memory".into(),
props: Props::new(),
}],
2,
)
.expect("write after read-only open");
assert!(
db.node(&scopes, id2).is_some(),
"write after reopen is visible"
);
}