use core_api::{Direction, GraphDb, OpenOptions, Value};
fn store_from_fixture(name: &str, bytes: &[u8]) -> std::path::PathBuf {
let d = std::env::temp_dir().join(format!("graphdb-migrate-{}-{}", name, std::process::id()));
let _ = std::fs::remove_dir_all(&d);
std::fs::create_dir_all(&d).unwrap();
std::fs::write(d.join("snapshot.bin"), bytes).unwrap();
std::fs::write(d.join("wal.bin"), b"").unwrap();
d
}
#[test]
fn v5_store_auto_migrates_on_open_with_bak() {
let dir = store_from_fixture("v5", include_bytes!("fixtures/golden_v5.bin"));
{
let db = GraphDb::open(&dir).unwrap();
assert_eq!(db.node_count(), 2);
}
assert_eq!(
core_api::snapshot_version_at(&dir).unwrap(),
Some(core_storage::snapshot::VERSION),
"snapshot must be rewritten to current VERSION after migration"
);
let bak = std::fs::read(dir.join("snapshot.bin.bak")).unwrap();
assert_eq!(
u16::from_le_bytes([bak[4], bak[5]]),
5,
".bak must contain the original V5 bytes"
);
let db = GraphDb::open(&dir).unwrap();
assert_eq!(
db.get_prop("a", "v"),
Some(Value::Int(42)),
"property v=42 on node 'a' must survive migration"
);
assert!(
!dir.join("snapshot.bin.bak").exists(),
"second clean open must delete the .bak"
);
}
#[test]
fn auto_migrate_false_leaves_disk_untouched() {
let dir = store_from_fixture("v6-noauto", include_bytes!("fixtures/golden_v6.bin"));
let before = std::fs::read(dir.join("snapshot.bin")).unwrap();
let _db = GraphDb::open_with_options(
&dir,
OpenOptions {
auto_migrate: false,
..Default::default()
},
)
.unwrap();
assert_eq!(
std::fs::read(dir.join("snapshot.bin")).unwrap(),
before,
"snapshot.bin must be byte-identical after auto_migrate=false open"
);
assert!(
!dir.join("snapshot.bin.bak").exists(),
"no .bak should be created when auto_migrate=false"
);
}
#[test]
fn current_version_open_is_a_no_op_migration() {
let dir = std::env::temp_dir().join(format!(
"graphdb-migrate-current-noop-{}",
std::process::id()
));
let _ = std::fs::remove_dir_all(&dir);
{
let mut db = GraphDb::open(&dir).unwrap();
db.insert_node("N", "x", vec![]).unwrap();
db.snapshot().unwrap(); }
let _db = GraphDb::open(&dir).unwrap();
assert!(
!dir.join("snapshot.bin.bak").exists(),
"no .bak should appear when snapshot is already at current VERSION"
);
}
#[test]
fn wal_preserved_by_auto_migrate() {
let dir = store_from_fixture("v6-keepwal", include_bytes!("fixtures/golden_v6.bin"));
{
let mut db = GraphDb::open_with_options(
&dir,
OpenOptions {
auto_migrate: false,
..Default::default()
},
)
.unwrap();
db.insert_node("N", "extra", vec![]).unwrap();
}
let wal_count_before = core_api::wal_commit_count_at(&dir).unwrap();
assert!(
wal_count_before > 0,
"WAL must have at least one commit before migration"
);
let db = GraphDb::open(&dir).unwrap();
assert!(
db.has_node("extra"),
"WAL-inserted node 'extra' must survive migration"
);
let wal_count_after = core_api::wal_commit_count_at(&dir).unwrap();
assert!(
wal_count_after > 0,
"WAL commit count must remain > 0 after auto-migrate (keep_wal)"
);
}
#[test]
fn v7_store_auto_migrates_to_v8_with_bak() {
let dir = store_from_fixture("v7", include_bytes!("fixtures/golden_v7.bin"));
{
let db = GraphDb::open(&dir).unwrap();
assert_eq!(
db.node_count(),
2,
"V7 store must have 2 nodes after migrate"
);
assert_eq!(
db.edge_count(),
1,
"V7 store must have 1 edge after migrate"
);
}
assert_eq!(
core_api::snapshot_version_at(&dir).unwrap(),
Some(core_storage::snapshot::VERSION),
"snapshot must be rewritten to V8 after migration"
);
let bak = std::fs::read(dir.join("snapshot.bin.bak")).unwrap();
assert_eq!(
u16::from_le_bytes([bak[4], bak[5]]),
7,
".bak must contain the original V7 bytes"
);
let db = GraphDb::open(&dir).unwrap();
assert_eq!(
db.get_prop("a", "v"),
Some(Value::Int(42)),
"property v=42 on node 'a' must survive V7→V8 migration"
);
assert_eq!(
db.neighbors("a", "E", Direction::Out).unwrap(),
vec!["b"],
"edge a→b must survive V7→V8 migration"
);
assert!(
!dir.join("snapshot.bin.bak").exists(),
"second clean open at V8 must delete the .bak"
);
}