use rand::SeedableRng;
use sqlitegraph::{EdgeSpec, GraphConfig, NodeSpec, open_graph};
use std::fs;
use std::io::Read;
fn read_node_slot_version(
path: &std::path::Path,
node_id: i64,
) -> Result<u8, Box<dyn std::error::Error>> {
let mut file = fs::File::open(path)?;
const NODE_SLOT_SIZE: u64 = 4096;
const DEFAULT_NODE_DATA_START: u64 = 1024;
let slot_offset = DEFAULT_NODE_DATA_START + ((node_id - 1) as u64 * NODE_SLOT_SIZE);
use std::io::Seek;
use std::io::SeekFrom;
file.seek(SeekFrom::Start(slot_offset))?;
let mut buffer = [0u8; 1];
file.read_exact(&mut buffer)?;
Ok(buffer[0])
}
fn calculate_node_region_end(node_count: u64) -> u64 {
const NODE_DATA_START: u64 = 1024;
const NODE_SLOT_SIZE: u64 = 4096;
NODE_DATA_START + (node_count * NODE_SLOT_SIZE)
}
#[test]
fn test_cluster_offset_never_overlaps_node_slots() {
println!("=== CLUSTER OFFSET CORRUPTION REGRESSION TEST ===");
let test_sizes = [10, 50, 100, 256, 300, 500, 1000];
for &size in &test_sizes {
println!("\n--- Testing graph size: {} nodes ---", size);
let temp_dir = tempfile::tempdir().unwrap();
let db_path = temp_dir.path().join("test.db");
let graph =
open_graph(&db_path, &GraphConfig::native()).expect("Failed to create V2 native graph");
let mut node_ids = Vec::new();
for i in 1..=size {
let node_id = graph
.insert_node(NodeSpec {
kind: "Node".to_string(),
name: format!("node_{}", i),
file_path: None,
data: serde_json::json!({"id": i}),
})
.expect(&format!("Failed to insert node {}", i));
node_ids.push(node_id);
}
for i in 1..=size {
let version = read_node_slot_version(&db_path, i).unwrap();
assert_eq!(
version, 2,
"Node {} should have version=2 after creation",
i
);
}
for i in 0..(size as usize) - 1 {
let from_id = node_ids[i];
let to_id = node_ids[i + 1];
graph
.insert_edge(EdgeSpec {
from: from_id,
to: to_id,
edge_type: "chain".to_string(),
data: serde_json::json!({"edge_index": i}),
})
.expect(&format!("Failed to insert edge {} -> {}", from_id, to_id));
if i % 100 == 0 || i == (size as usize) - 2 {
let sample_nodes = [
1,
if size >= 50 { size / 4 } else { 1 },
if size >= 100 { size / 2 } else { 1 },
if size >= 150 { (3 * size) / 4 } else { size },
size,
];
for &sample_node in &sample_nodes {
if sample_node <= size {
let version = read_node_slot_version(&db_path, sample_node as i64).unwrap();
assert_eq!(
version, 2,
"Node {} has version={} after edge {} (corruption detected!)",
sample_node, version, i
);
}
}
}
}
for i in 1..=size {
let version = read_node_slot_version(&db_path, i).unwrap();
assert_eq!(
version, 2,
"Node {} should have version=2 after all edge insertions",
i
);
}
println!(
"✅ Graph size {}: {} nodes and {} edges with NO corruption",
size,
size,
size - 1
);
}
println!("\n🎉 ALL REGRESSION TESTS PASSED - Cluster offset corruption is FIXED!");
}
#[test]
fn test_boundary_conditions_around_node_257() {
println!("=== BOUNDARY CONDITION TEST AROUND NODE 257 ===");
let boundary_ranges = [
(250, 260), (255, 260), (256, 258), (257, 257), (300, 310), ];
for &(start, end) in &boundary_ranges {
println!("\n--- Testing nodes {} to {} ---", start, end);
let temp_dir = tempfile::tempdir().unwrap();
let db_path = temp_dir.path().join("test.db");
let graph =
open_graph(&db_path, &GraphConfig::native()).expect("Failed to create V2 native graph");
let mut node_ids = Vec::new();
for i in start..=end {
let node_id = graph
.insert_node(NodeSpec {
kind: "Node".to_string(),
name: format!("node_{}", i),
file_path: None,
data: serde_json::json!({"id": i}),
})
.expect(&format!("Failed to insert node {}", i));
println!(
"DEBUG: Created node with name='node_{}' -> node_id={}",
i, node_id
);
node_ids.push(node_id);
}
for i in 0..node_ids.len() - 1 {
for j in i + 1..node_ids.len() {
graph
.insert_edge(EdgeSpec {
from: node_ids[i],
to: node_ids[j],
edge_type: "test".to_string(),
data: serde_json::json!({"from": i, "to": j}),
})
.expect(&format!(
"Failed to insert edge {} -> {}",
node_ids[i], node_ids[j]
));
}
}
for i in start..=end {
let slot_offset = 1024 + ((i - 1) as u64 * 4096);
let file_size = std::fs::metadata(&db_path).unwrap().len();
if slot_offset + 1 <= file_size {
let version = read_node_slot_version(&db_path, i).unwrap();
assert_eq!(
version, 0,
"Node {} slot should remain version=0 (uninitialized) after cluster writes - corruption detected!",
i
);
} else {
println!("Node {} slot beyond file size - no corruption possible", i);
}
}
println!(
"✅ Nodes {}-{}: NO corruption with {} edges",
start,
end,
(node_ids.len() * (node_ids.len() - 1)) / 2
);
}
println!("\n🎉 ALL BOUNDARY TESTS PASSED - Node 257 corruption is FIXED!");
}
#[test]
fn test_node_region_calculation_invariants() {
println!("=== NODE REGION CALCULATION INVARIANTS TEST ===");
let test_cases = [1, 10, 100, 257, 500, 1000, 10000];
for &node_count in &test_cases {
let node_region_end = calculate_node_region_end(node_count as u64);
let node_257_slot_start = 1024 + ((257 - 1) as u64 * 4096);
println!(
"Node count {}: node_region_end = 0x{:x} = {}",
node_count, node_region_end, node_region_end
);
if node_count >= 257 {
assert!(
node_region_end > node_257_slot_start,
"Node region end {} should be after node 257 slot start {}",
node_region_end,
node_257_slot_start
);
}
println!(" ✅ Cluster offsets should be >= 0x{:x}", node_region_end);
}
println!("\n🎉 ALL INVARIANT TESTS PASSED - Node region calculations are correct!");
}
#[test]
fn test_comprehensive_edge_patterns() {
println!("=== COMPREHENSIVE EDGE PATTERNS TEST ===");
let temp_dir = tempfile::tempdir().unwrap();
let db_path = temp_dir.path().join("test.db");
let graph =
open_graph(&db_path, &GraphConfig::native()).expect("Failed to create V2 native graph");
let mut node_ids = Vec::new();
for i in 1..=1000 {
let node_id = graph
.insert_node(NodeSpec {
kind: "Node".to_string(),
name: format!("node_{}", i),
file_path: None,
data: serde_json::json!({"id": i}),
})
.expect(&format!("Failed to insert node {}", i));
node_ids.push(node_id);
}
println!("Created 1000 nodes successfully");
println!("Testing chain pattern...");
for i in 0..999 {
graph
.insert_edge(EdgeSpec {
from: node_ids[i],
to: node_ids[i + 1],
edge_type: "chain".to_string(),
data: serde_json::json!({"chain_index": i}),
})
.expect("Chain edge insertion failed");
}
println!("✅ Chain pattern: 999 edges inserted");
println!("Testing star pattern...");
for i in 1..1000 {
graph
.insert_edge(EdgeSpec {
from: node_ids[0], to: node_ids[i], edge_type: "star".to_string(),
data: serde_json::json!({"star_index": i}),
})
.expect("Star edge insertion failed");
}
println!("✅ Star pattern: 999 edges inserted");
println!("Testing reverse chain pattern...");
for i in (1..1000).rev() {
graph
.insert_edge(EdgeSpec {
from: node_ids[i], to: node_ids[i - 1], edge_type: "reverse".to_string(),
data: serde_json::json!({"reverse_index": i}),
})
.expect("Reverse chain edge insertion failed");
}
println!("✅ Reverse chain pattern: 999 edges inserted");
println!("Testing random pattern around node 257...");
use rand::RngCore;
let mut rng = rand::rngs::StdRng::seed_from_u64(0x12345678);
for _ in 0..1000 {
let from_idx = (rng.next_u32() as usize) % 1000;
let mut to_idx = (rng.next_u32() as usize) % 1000;
while to_idx == from_idx {
to_idx = (rng.next_u32() as usize) % 1000;
}
graph
.insert_edge(EdgeSpec {
from: node_ids[from_idx],
to: node_ids[to_idx],
edge_type: "random".to_string(),
data: serde_json::json!({"random_id": rng.next_u64()}),
})
.expect("Random edge insertion failed");
}
println!("✅ Random pattern: 1000 edges inserted");
println!("Final verification of all nodes...");
for i in 1..=1000 {
let version = read_node_slot_version(&db_path, i).unwrap();
assert_eq!(
version, 2,
"Node {} corrupted to version {} after comprehensive testing",
i, version
);
}
println!("🎉 COMPREHENSIVE TEST PASSED: 3997 edges with NO corruption!");
println!(
"Total edges inserted: 999 (chain) + 999 (star) + 999 (reverse) + 1000 (random) = 3997"
);
}