use sqlitegraph::backend::native::{
adjacency::Direction,
graph_file::GraphFile,
node_store::NodeStore,
types::{NativeBackendError, NativeResult},
};
use std::path::PathBuf;
use tempfile::TempDir;
fn create_test_graph() -> NativeResult<(TempDir, GraphFile)> {
let temp_dir = TempDir::new()?;
let db_path = temp_dir.path().join("test.db");
let mut graph_file = GraphFile::create(&db_path)?;
let mut node_store = NodeStore::new(&mut graph_file);
let node1 = sqlitegraph::backend::native::v2::node_record_v2::NodeRecordV2::new(
1,
"Node".to_string(),
"node1".to_string(),
serde_json::json!({"id": 1}),
);
node_store.write_node_v2(&node1)?;
let node2 = sqlitegraph::backend::native::v2::node_record_v2::NodeRecordV2::new(
2,
"Node".to_string(),
"node2".to_string(),
serde_json::json!({"id": 2}),
);
node_store.write_node_v2(&node2)?;
let node3 = sqlitegraph::backend::native::v2::node_record_v2::NodeRecordV2::new(
3,
"Node".to_string(),
"node3".to_string(),
serde_json::json!({"id": 3}),
);
node_store.write_node_v2(&node3)?;
Ok((temp_dir, graph_file))
}
#[test]
fn test_delete_isolated_node() {
let (_temp_dir, mut graph_file) = create_test_graph().unwrap();
let mut node_store = NodeStore::new(&mut graph_file);
let node4 = sqlitegraph::backend::native::v2::node_record_v2::NodeRecordV2::new(
4,
"Node".to_string(),
"isolated".to_string(),
serde_json::json!({}),
);
node_store.write_node_v2(&node4).unwrap();
let node_count_before = {
drop(node_store);
graph_file.header().node_count
};
let mut node_store = NodeStore::new(&mut graph_file);
let result = node_store.delete_node(4);
assert!(result.is_ok(), "Should be able to delete isolated node");
let result = node_store.read_node_v2(4);
assert!(result.is_err(), "Deleted node should not be readable");
drop(node_store);
let node_count_after = graph_file.header().node_count;
assert_eq!(
node_count_after, node_count_before,
"node_count should NOT be decremented - it represents max allocated slot, not active nodes"
);
}
#[test]
fn test_delete_node_with_edges() {
let (_temp_dir, mut graph_file) = create_test_graph().unwrap();
let mut node_store = NodeStore::new(&mut graph_file);
let _node1 = node_store.read_node_v2(1).unwrap();
let _node2 = node_store.read_node_v2(2).unwrap();
let _node3 = node_store.read_node_v2(3).unwrap();
let result = node_store.delete_node(2);
assert!(result.is_ok(), "Should be able to delete node with edges");
let result = node_store.read_node_v2(2);
assert!(result.is_err(), "Deleted node should not be readable");
let result = node_store.read_node_v2(1);
assert!(result.is_ok(), "Node 1 should still exist");
let result = node_store.read_node_v2(3);
assert!(result.is_ok(), "Node 3 should still exist");
}
#[test]
fn test_deletion_updates_header() {
let (_temp_dir, mut graph_file) = create_test_graph().unwrap();
let mut node_store = NodeStore::new(&mut graph_file);
let node4 = sqlitegraph::backend::native::v2::node_record_v2::NodeRecordV2::new(
4,
"Node".to_string(),
"node4".to_string(),
serde_json::json!({}),
);
node_store.write_node_v2(&node4).unwrap();
let node5 = sqlitegraph::backend::native::v2::node_record_v2::NodeRecordV2::new(
5,
"Node".to_string(),
"node5".to_string(),
serde_json::json!({}),
);
node_store.write_node_v2(&node5).unwrap();
let node_count_before = {
drop(node_store);
graph_file.header().node_count
};
assert_eq!(node_count_before, 5, "Should have 5 node slots initially");
let mut node_store = NodeStore::new(&mut graph_file);
let _ = node_store.delete_node(4).unwrap();
let mut node_store = NodeStore::new(&mut graph_file);
let _ = node_store.delete_node(5).unwrap();
let node_count_after = graph_file.header().node_count;
assert_eq!(
node_count_after, 5,
"node_count should remain 5 (max allocated slot) - deletion doesn't change slot addresses"
);
let mut node_store = NodeStore::new(&mut graph_file);
assert!(
node_store.read_node_v2(4).is_err(),
"Deleted node 4 should be unreadable"
);
assert!(
node_store.read_node_v2(5).is_err(),
"Deleted node 5 should be unreadable"
);
assert!(
node_store.read_node_v2(1).is_ok(),
"Node 1 should still exist"
);
assert!(
node_store.read_node_v2(3).is_ok(),
"Node 3 should still exist"
);
}
#[test]
fn test_slot_reuse_after_deletion() {
let (_temp_dir, mut graph_file) = create_test_graph().unwrap();
let mut node_store = NodeStore::new(&mut graph_file);
let node4 = sqlitegraph::backend::native::v2::node_record_v2::NodeRecordV2::new(
4,
"Node".to_string(),
"node4".to_string(),
serde_json::json!({}),
);
node_store.write_node_v2(&node4).unwrap();
let result = node_store.delete_node(4);
assert!(result.is_ok(), "Should be able to delete node 4");
let node5 = sqlitegraph::backend::native::v2::node_record_v2::NodeRecordV2::new(
5,
"Node".to_string(),
"node5".to_string(),
serde_json::json!({}),
);
let result = node_store.write_node_v2(&node5);
assert!(result.is_ok(), "Should be able to create new node after deletion");
}
#[test]
fn test_delete_nonexistent_node() {
let (_temp_dir, mut graph_file) = create_test_graph().unwrap();
let mut node_store = NodeStore::new(&mut graph_file);
let result = node_store.delete_node(999);
assert!(result.is_err(), "Should fail to delete non-existent node");
match result {
Err(NativeBackendError::InvalidNodeId { id, .. }) => {
assert_eq!(id, 999, "Error should reference the invalid node ID");
}
_ => panic!("Expected InvalidNodeId error"),
}
}
#[test]
fn test_delete_node_clears_cluster_metadata() {
let (_temp_dir, mut graph_file) = create_test_graph().unwrap();
let mut node_store = NodeStore::new(&mut graph_file);
let node_before = node_store.read_node_v2(1).unwrap();
let _original_outgoing_count = node_before.outgoing_edge_count;
let _original_incoming_count = node_before.incoming_edge_count;
let result = node_store.delete_node(1);
assert!(result.is_ok(), "Should be able to delete node 1");
let result = node_store.read_node_v2(1);
match result {
Ok(node_after) => {
assert_eq!(
node_after.outgoing_edge_count, 0,
"Outgoing edge count should be 0 after deletion"
);
assert_eq!(
node_after.incoming_edge_count, 0,
"Incoming edge count should be 0 after deletion"
);
assert_eq!(
node_after.outgoing_cluster_offset, 0,
"Outgoing cluster offset should be 0 after deletion"
);
assert_eq!(
node_after.incoming_cluster_offset, 0,
"Incoming cluster offset should be 0 after deletion"
);
}
Err(_) => {
}
}
}
#[test]
fn test_multiple_decrements_in_same_slot() {
let (_temp_dir, mut graph_file) = create_test_graph().unwrap();
let mut node_store = NodeStore::new(&mut graph_file);
let result1 = node_store.delete_node(1);
assert!(result1.is_ok(), "First deletion should succeed");
let result2 = node_store.delete_node(1);
assert!(result2.is_err(), "Second deletion of same node should fail");
}