use sqlitegraph::backend::native::constants::HEADER_SIZE;
use sqlitegraph::backend::native::persistent_header::PERSISTENT_HEADER_SIZE;
use sqlitegraph::backend::native::types::NativeBackendError;
use sqlitegraph::backend::native::{GraphFile, decode_persistent_header};
use std::fs::OpenOptions;
use std::io::Write;
use tempfile::tempdir;
#[test]
fn test_header_decode_exact_80_bytes_boundary() {
let dir = tempdir().unwrap();
let file_path = dir.path().join("test_boundary_80.db");
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(&file_path)
.expect("Failed to create test file");
let mut header_data = vec![0u8; PERSISTENT_HEADER_SIZE];
header_data[0..8].copy_from_slice(b"SQLTGRPH");
header_data[8] = 0;
header_data[9] = 0;
header_data[10] = 0;
header_data[11] = 2;
file.write_all(&header_data)
.expect("Failed to write header data");
file.sync_all().expect("Failed to sync file");
drop(file);
let result = std::panic::catch_unwind(|| GraphFile::open(&file_path));
assert!(
result.is_ok(),
"GraphFile::open should not panic with exactly 80 bytes"
);
match result.unwrap() {
Ok(graph_file) => {
assert_eq!(&graph_file.persistent_header().magic, b"SQLTGRPH");
}
Err(e) => {
match e {
NativeBackendError::FileTooSmall { .. } => {
}
NativeBackendError::InvalidHeader { .. } => {
}
other => {
panic!("Unexpected error type: {:?}", other);
}
}
}
}
}
#[test]
fn test_decode_persistent_header_direct_boundary() {
let mut header_data = vec![0u8; PERSISTENT_HEADER_SIZE];
header_data[0..8].copy_from_slice(b"SQLTGRPH");
header_data[8..12].copy_from_slice(&2u32.to_be_bytes());
header_data[12..16].copy_from_slice(&0u32.to_be_bytes()); header_data[16..24].copy_from_slice(&0u64.to_be_bytes()); header_data[24..32].copy_from_slice(&0u64.to_be_bytes()); header_data[32..36].copy_from_slice(&2u32.to_be_bytes()); header_data[36..44].copy_from_slice(&80u64.to_be_bytes()); header_data[44..52].copy_from_slice(&80u64.to_be_bytes()); header_data[52..56].copy_from_slice(&0u32.to_be_bytes()); header_data[56..64].copy_from_slice(&80u64.to_be_bytes()); header_data[64..72].copy_from_slice(&80u64.to_be_bytes()); header_data[72..80].copy_from_slice(&80u64.to_be_bytes());
let result = std::panic::catch_unwind(|| decode_persistent_header(&header_data));
assert!(
result.is_ok(),
"decode_persistent_header should not panic with exactly 80 bytes"
);
match result.unwrap() {
Ok(header) => {
assert_eq!(&header.magic, b"SQLTGRPH");
assert_eq!(header.version, 2);
assert_eq!(header.schema_version, 2);
assert_eq!(header.node_data_offset, 80);
assert_eq!(header.edge_data_offset, 80);
assert_eq!(header.outgoing_cluster_offset, 80);
assert_eq!(header.incoming_cluster_offset, 80);
assert_eq!(header.free_space_offset, 80);
}
Err(e) => {
match e {
NativeBackendError::FileTooSmall { .. } => {
panic!("Should not get FileTooSmall error with exactly 80 bytes");
}
other => {
println!("Got error (acceptable): {:?}", other);
}
}
}
}
}
#[test]
fn test_header_decode_79_bytes_should_fail_gracefully() {
let header_data = vec![0u8; PERSISTENT_HEADER_SIZE - 1];
let result = std::panic::catch_unwind(|| decode_persistent_header(&header_data));
assert!(result.is_ok(), "Should not panic, just return error");
match result.unwrap() {
Ok(_) => {
panic!("Should not successfully decode header with only 79 bytes");
}
Err(e) => match e {
NativeBackendError::FileTooSmall { size, min_size } => {
assert_eq!(size, 79);
assert_eq!(min_size, 80);
}
other => {
panic!("Expected FileTooSmall error, got: {:?}", other);
}
},
}
}
#[test]
fn test_header_decode_81_bytes_should_work() {
let mut header_data = vec![0u8; PERSISTENT_HEADER_SIZE + 1];
header_data[0..8].copy_from_slice(b"SQLTGRPH");
header_data[8..12].copy_from_slice(&1u32.to_be_bytes());
let result = std::panic::catch_unwind(|| decode_persistent_header(&header_data));
assert!(result.is_ok(), "Should not panic with 81 bytes");
match result.unwrap() {
Ok(header) => {
assert_eq!(&header.magic, b"SQLTGRPH");
assert_eq!(header.version, 2);
}
Err(e) => {
panic!(
"Should successfully decode header with 81 bytes, got error: {:?}",
e
);
}
}
}
#[test]
fn test_graphfile_reopen_with_exact_header_size() {
let dir = tempdir().unwrap();
let file_path = dir.path().join("test_reopen.db");
{
let mut graph_file = GraphFile::create(&file_path).expect("Failed to create graph file");
graph_file.write_header().expect("Failed to write header");
assert!(graph_file.file_size().unwrap() >= HEADER_SIZE);
}
let result = std::panic::catch_unwind(|| GraphFile::open(&file_path));
assert!(result.is_ok(), "GraphFile reopen should not panic");
match result.unwrap() {
Ok(mut graph_file) => {
graph_file
.read_header()
.expect("Should be able to read header");
assert_eq!(&graph_file.persistent_header().magic, b"SQLTGRPH");
}
Err(e) => {
panic!("Should be able to reopen graph file: {:?}", e);
}
}
}