#![cfg(all(feature = "wal", feature = "grafeo-file", feature = "lpg"))]
use grafeo_common::types::EpochId;
use grafeo_engine::GrafeoDB;
#[test]
fn full_backup_and_restore_to_epoch() {
let dir = tempfile::tempdir().expect("create temp dir");
let db_path = dir.path().join("source.grafeo");
let backup_dir = dir.path().join("backups");
let restore_path = dir.path().join("restored.grafeo");
{
let db = GrafeoDB::open(&db_path).expect("open");
let session = db.session();
session
.execute("INSERT (:Person {name: 'Alix', batch: 1})")
.expect("insert");
session
.execute("INSERT (:Person {name: 'Gus', batch: 1})")
.expect("insert");
db.close().expect("close");
}
let db = GrafeoDB::open(&db_path).expect("reopen");
let segment = db.backup_full(&backup_dir).expect("full backup");
assert_eq!(segment.start_epoch, EpochId::new(0));
assert!(segment.size_bytes > 0);
let current_epoch = segment.end_epoch;
db.close().expect("close");
GrafeoDB::restore_to_epoch(&backup_dir, current_epoch, &restore_path)
.expect("restore to epoch");
let restored = GrafeoDB::open(&restore_path).expect("open restored");
assert_eq!(restored.node_count(), 2, "restored should have 2 nodes");
let session = restored.session();
let result = session
.execute("MATCH (n:Person) RETURN n.name ORDER BY n.name")
.unwrap();
assert_eq!(result.rows().len(), 2);
restored.close().expect("close");
}
#[test]
fn incremental_backup_captures_new_data() {
let dir = tempfile::tempdir().expect("create temp dir");
let db_path = dir.path().join("incr.grafeo");
let backup_dir = dir.path().join("backups");
let db = GrafeoDB::open(&db_path).expect("open");
let session = db.session();
session
.execute("INSERT (:Person {name: 'Alix'})")
.expect("insert");
let full = db.backup_full(&backup_dir).expect("full backup");
assert!(full.size_bytes > 0);
session
.execute("INSERT (:Person {name: 'Gus'})")
.expect("insert");
session
.execute("INSERT (:Person {name: 'Vincent'})")
.expect("insert");
db.wal().expect("WAL").rotate().expect("rotate");
session
.execute("INSERT (:Person {name: 'Jules'})")
.expect("insert");
let incr = db
.backup_incremental(&backup_dir)
.expect("incremental backup");
assert!(incr.size_bytes > 0);
assert!(incr.start_epoch > full.end_epoch);
db.close().expect("close");
let manifest = GrafeoDB::read_backup_manifest(&backup_dir)
.expect("read manifest")
.expect("manifest exists");
assert_eq!(manifest.segments.len(), 2);
}
#[test]
fn backup_cursor_updated_after_full_backup() {
let dir = tempfile::tempdir().expect("create temp dir");
let db_path = dir.path().join("cursor.grafeo");
let backup_dir = dir.path().join("backups");
let db = GrafeoDB::open(&db_path).expect("open");
let session = db.session();
session.execute("INSERT (:Test {val: 1})").expect("insert");
assert!(
db.backup_cursor().is_none(),
"no cursor before first backup"
);
db.backup_full(&backup_dir).expect("full backup");
let cursor = db
.backup_cursor()
.expect("cursor should exist after backup");
assert!(
cursor.backed_up_epoch.as_u64() > 0,
"epoch should be > 0 after session commit"
);
db.close().expect("close");
}
#[test]
fn backup_manifest_tracks_segments() {
let dir = tempfile::tempdir().expect("create temp dir");
let db_path = dir.path().join("meta.grafeo");
let backup_dir = dir.path().join("backups");
let db = GrafeoDB::open(&db_path).expect("open");
db.create_node(&["Test"]);
let segment = db.backup_full(&backup_dir).expect("full backup");
let manifest = GrafeoDB::read_backup_manifest(&backup_dir)
.unwrap()
.unwrap();
assert_eq!(manifest.segments.len(), 1);
assert_eq!(manifest.segments[0].filename, segment.filename);
assert_eq!(manifest.epoch_range().unwrap().1, segment.end_epoch);
db.close().expect("close");
}
#[test]
fn incremental_without_full_fails() {
let dir = tempfile::tempdir().expect("create temp dir");
let db_path = dir.path().join("nofull.grafeo");
let backup_dir = dir.path().join("backups");
let db = GrafeoDB::open(&db_path).expect("open");
db.create_node(&["Test"]);
let result = db.backup_incremental(&backup_dir);
assert!(result.is_err(), "incremental without full should fail");
db.close().expect("close");
}
#[test]
fn restore_nonexistent_backup_fails() {
let dir = tempfile::tempdir().expect("create temp dir");
let backup_dir = dir.path().join("empty_backups");
let restore_path = dir.path().join("restored.grafeo");
let result = GrafeoDB::restore_to_epoch(&backup_dir, EpochId::new(100), &restore_path);
assert!(result.is_err(), "restore from empty dir should fail");
}
#[test]
fn backup_full_on_read_only_database() {
let dir = tempfile::tempdir().expect("create temp dir");
let db_path = dir.path().join("readonly_backup.grafeo");
let backup_dir = dir.path().join("backups");
{
let db = GrafeoDB::open(&db_path).expect("open");
let session = db.session();
session
.execute("INSERT (:Person {name: 'Alix'})")
.expect("insert");
session
.execute("INSERT (:Person {name: 'Gus'})")
.expect("insert");
db.close().expect("close");
}
let db = GrafeoDB::open_read_only(&db_path).expect("open read-only");
let segment = db
.backup_full(&backup_dir)
.expect("backup_full on read-only should succeed");
assert_eq!(segment.start_epoch, EpochId::new(0));
assert!(segment.size_bytes > 0, "backup file should not be empty");
let restore_path = dir.path().join("restored.grafeo");
GrafeoDB::restore_to_epoch(&backup_dir, segment.end_epoch, &restore_path)
.expect("restore should succeed");
let restored = GrafeoDB::open(&restore_path).expect("open restored");
assert_eq!(restored.node_count(), 2, "restored should have 2 nodes");
restored.close().expect("close");
}
#[test]
fn backup_full_works_while_database_is_open() {
let dir = tempfile::tempdir().expect("create temp dir");
let db_path = dir.path().join("open_backup.grafeo");
let backup_dir = dir.path().join("backups");
let db = GrafeoDB::open(&db_path).expect("open");
let session = db.session();
session
.execute("INSERT (:Person {name: 'Alix'})")
.expect("insert");
session
.execute("INSERT (:Person {name: 'Gus'})")
.expect("insert");
let segment = db
.backup_full(&backup_dir)
.expect("backup_full on open database should work on all platforms");
assert_eq!(segment.start_epoch, EpochId::new(0));
assert!(segment.size_bytes > 0);
db.close().expect("close");
let restore_path = dir.path().join("restored.grafeo");
GrafeoDB::restore_to_epoch(&backup_dir, segment.end_epoch, &restore_path)
.expect("restore should succeed");
let restored = GrafeoDB::open(&restore_path).expect("open restored");
assert_eq!(restored.node_count(), 2, "restored should have 2 nodes");
restored.close().expect("close");
}
#[test]
fn backup_full_twice_produces_two_segments() {
let dir = tempfile::tempdir().expect("create temp dir");
let db_path = dir.path().join("double.grafeo");
let backup_dir = dir.path().join("backups");
let db = GrafeoDB::open(&db_path).expect("open");
let session = db.session();
session
.execute("INSERT (:Person {name: 'Alix'})")
.expect("insert");
let seg1 = db.backup_full(&backup_dir).expect("first backup");
assert_eq!(seg1.filename, "backup_full_0000.grafeo");
session
.execute("INSERT (:Person {name: 'Gus'})")
.expect("insert");
let seg2 = db.backup_full(&backup_dir).expect("second backup");
assert_eq!(seg2.filename, "backup_full_0001.grafeo");
assert!(seg2.end_epoch >= seg1.end_epoch);
let manifest = GrafeoDB::read_backup_manifest(&backup_dir)
.unwrap()
.unwrap();
assert_eq!(manifest.segments.len(), 2);
let restore_path = dir.path().join("restored.grafeo");
GrafeoDB::restore_to_epoch(&backup_dir, seg2.end_epoch, &restore_path)
.expect("restore should succeed");
let restored = GrafeoDB::open(&restore_path).expect("open restored");
assert_eq!(restored.node_count(), 2);
restored.close().expect("close");
db.close().expect("close");
}
#[test]
fn incremental_backup_works_without_manual_rotation() {
let dir = tempfile::tempdir().expect("create temp dir");
let db_path = dir.path().join("issue267.grafeo");
let backup_dir = dir.path().join("backups");
let db = GrafeoDB::open(&db_path).expect("open");
let session = db.session();
session
.execute("INSERT (:Person {name: 'Alix'})")
.expect("insert");
let full = db.backup_full(&backup_dir).expect("full backup");
session
.execute("INSERT (:Person {name: 'Gus'})")
.expect("insert");
session
.execute("INSERT (:Person {name: 'Vincent'})")
.expect("insert");
let incr = db
.backup_incremental(&backup_dir)
.expect("incremental backup should work without manual WAL rotation");
assert!(incr.size_bytes > 0);
assert!(incr.start_epoch > full.end_epoch);
db.close().expect("close");
}
#[test]
fn multiple_incremental_backups_in_sequence() {
let dir = tempfile::tempdir().expect("create temp dir");
let db_path = dir.path().join("multi_incr.grafeo");
let backup_dir = dir.path().join("backups");
let db = GrafeoDB::open(&db_path).expect("open");
let session = db.session();
session
.execute("INSERT (:Person {name: 'Alix'})")
.expect("insert");
db.backup_full(&backup_dir).expect("full backup");
session
.execute("INSERT (:Person {name: 'Gus'})")
.expect("insert");
let incr1 = db
.backup_incremental(&backup_dir)
.expect("first incremental");
session
.execute("INSERT (:Person {name: 'Vincent'})")
.expect("insert");
let incr2 = db
.backup_incremental(&backup_dir)
.expect("second incremental should also succeed");
assert!(incr2.start_epoch > incr1.start_epoch);
let manifest = GrafeoDB::read_backup_manifest(&backup_dir)
.unwrap()
.unwrap();
assert_eq!(
manifest.segments.len(),
3,
"should have 1 full + 2 incremental segments"
);
db.close().expect("close");
}