git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
387 lines
10 KiB
Rust
387 lines
10 KiB
Rust
//! Node CRUD operation tests
|
|
//!
|
|
//! Tests for creating, reading, updating, and deleting nodes in the graph database.
|
|
|
|
use ruvector_graph::{GraphDB, Label, Node, Properties, PropertyValue};
|
|
|
|
#[test]
|
|
fn test_create_node_basic() {
|
|
let db = GraphDB::new();
|
|
|
|
let mut properties = Properties::new();
|
|
properties.insert(
|
|
"name".to_string(),
|
|
PropertyValue::String("Alice".to_string()),
|
|
);
|
|
properties.insert("age".to_string(), PropertyValue::Integer(30));
|
|
|
|
let node = Node::new(
|
|
"node1".to_string(),
|
|
vec![Label {
|
|
name: "Person".to_string(),
|
|
}],
|
|
properties,
|
|
);
|
|
|
|
let node_id = db.create_node(node).unwrap();
|
|
assert_eq!(node_id, "node1");
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_node_existing() {
|
|
let db = GraphDB::new();
|
|
|
|
let mut properties = Properties::new();
|
|
properties.insert("name".to_string(), PropertyValue::String("Bob".to_string()));
|
|
|
|
let node = Node::new(
|
|
"node2".to_string(),
|
|
vec![Label {
|
|
name: "Person".to_string(),
|
|
}],
|
|
properties.clone(),
|
|
);
|
|
|
|
db.create_node(node).unwrap();
|
|
|
|
let retrieved = db.get_node("node2").unwrap();
|
|
assert_eq!(retrieved.id, "node2");
|
|
assert_eq!(
|
|
retrieved.properties.get("name"),
|
|
Some(&PropertyValue::String("Bob".to_string()))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_node_nonexistent() {
|
|
let db = GraphDB::new();
|
|
let result = db.get_node("nonexistent");
|
|
assert!(result.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_node_with_multiple_labels() {
|
|
let db = GraphDB::new();
|
|
|
|
let labels = vec![
|
|
Label {
|
|
name: "Person".to_string(),
|
|
},
|
|
Label {
|
|
name: "Employee".to_string(),
|
|
},
|
|
Label {
|
|
name: "Manager".to_string(),
|
|
},
|
|
];
|
|
|
|
let mut properties = Properties::new();
|
|
properties.insert(
|
|
"name".to_string(),
|
|
PropertyValue::String("Charlie".to_string()),
|
|
);
|
|
|
|
let node = Node::new("node3".to_string(), labels, properties);
|
|
db.create_node(node).unwrap();
|
|
|
|
let retrieved = db.get_node("node3").unwrap();
|
|
assert_eq!(retrieved.labels.len(), 3);
|
|
}
|
|
|
|
#[test]
|
|
fn test_node_with_complex_properties() {
|
|
let db = GraphDB::new();
|
|
|
|
let mut properties = Properties::new();
|
|
properties.insert(
|
|
"name".to_string(),
|
|
PropertyValue::String("David".to_string()),
|
|
);
|
|
properties.insert("age".to_string(), PropertyValue::Integer(35));
|
|
properties.insert("height".to_string(), PropertyValue::Float(1.82));
|
|
properties.insert("active".to_string(), PropertyValue::Boolean(true));
|
|
properties.insert(
|
|
"tags".to_string(),
|
|
PropertyValue::List(vec![
|
|
PropertyValue::String("developer".to_string()),
|
|
PropertyValue::String("team-lead".to_string()),
|
|
]),
|
|
);
|
|
|
|
let node = Node::new(
|
|
"node4".to_string(),
|
|
vec![Label {
|
|
name: "Person".to_string(),
|
|
}],
|
|
properties,
|
|
);
|
|
|
|
db.create_node(node).unwrap();
|
|
|
|
let retrieved = db.get_node("node4").unwrap();
|
|
assert_eq!(retrieved.properties.len(), 5);
|
|
assert!(matches!(
|
|
retrieved.properties.get("tags"),
|
|
Some(PropertyValue::List(_))
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn test_node_with_empty_properties() {
|
|
let db = GraphDB::new();
|
|
|
|
let node = Node::new(
|
|
"node5".to_string(),
|
|
vec![Label {
|
|
name: "EmptyNode".to_string(),
|
|
}],
|
|
Properties::new(),
|
|
);
|
|
|
|
db.create_node(node).unwrap();
|
|
|
|
let retrieved = db.get_node("node5").unwrap();
|
|
assert!(retrieved.properties.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_node_with_no_labels() {
|
|
let db = GraphDB::new();
|
|
|
|
let mut properties = Properties::new();
|
|
properties.insert(
|
|
"data".to_string(),
|
|
PropertyValue::String("test".to_string()),
|
|
);
|
|
|
|
let node = Node::new("node6".to_string(), vec![], properties);
|
|
|
|
db.create_node(node).unwrap();
|
|
|
|
let retrieved = db.get_node("node6").unwrap();
|
|
assert!(retrieved.labels.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_node_property_update() {
|
|
let db = GraphDB::new();
|
|
|
|
let mut properties = Properties::new();
|
|
properties.insert("counter".to_string(), PropertyValue::Integer(0));
|
|
|
|
let node = Node::new(
|
|
"node7".to_string(),
|
|
vec![Label {
|
|
name: "Counter".to_string(),
|
|
}],
|
|
properties,
|
|
);
|
|
|
|
db.create_node(node).unwrap();
|
|
|
|
// TODO: Implement update_node method
|
|
// For now, we'll recreate the node with updated properties
|
|
let mut updated_properties = Properties::new();
|
|
updated_properties.insert("counter".to_string(), PropertyValue::Integer(1));
|
|
|
|
let updated_node = Node::new(
|
|
"node7".to_string(),
|
|
vec![Label {
|
|
name: "Counter".to_string(),
|
|
}],
|
|
updated_properties,
|
|
);
|
|
|
|
db.create_node(updated_node).unwrap();
|
|
|
|
let retrieved = db.get_node("node7").unwrap();
|
|
assert_eq!(
|
|
retrieved.properties.get("counter"),
|
|
Some(&PropertyValue::Integer(1))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_create_1000_nodes() {
|
|
let db = GraphDB::new();
|
|
|
|
for i in 0..1000 {
|
|
let mut properties = Properties::new();
|
|
properties.insert("index".to_string(), PropertyValue::Integer(i));
|
|
|
|
let node = Node::new(
|
|
format!("node_{}", i),
|
|
vec![Label {
|
|
name: "TestNode".to_string(),
|
|
}],
|
|
properties,
|
|
);
|
|
|
|
db.create_node(node).unwrap();
|
|
}
|
|
|
|
// Verify all nodes were created
|
|
for i in 0..1000 {
|
|
let retrieved = db.get_node(&format!("node_{}", i));
|
|
assert!(retrieved.is_some());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_node_property_null_value() {
|
|
let db = GraphDB::new();
|
|
|
|
let mut properties = Properties::new();
|
|
properties.insert("nullable".to_string(), PropertyValue::Null);
|
|
|
|
let node = Node::new(
|
|
"node8".to_string(),
|
|
vec![Label {
|
|
name: "NullTest".to_string(),
|
|
}],
|
|
properties,
|
|
);
|
|
|
|
db.create_node(node).unwrap();
|
|
|
|
let retrieved = db.get_node("node8").unwrap();
|
|
assert_eq!(
|
|
retrieved.properties.get("nullable"),
|
|
Some(&PropertyValue::Null)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_node_nested_list_properties() {
|
|
let db = GraphDB::new();
|
|
|
|
let mut properties = Properties::new();
|
|
properties.insert(
|
|
"matrix".to_string(),
|
|
PropertyValue::List(vec![
|
|
PropertyValue::List(vec![PropertyValue::Integer(1), PropertyValue::Integer(2)]),
|
|
PropertyValue::List(vec![PropertyValue::Integer(3), PropertyValue::Integer(4)]),
|
|
]),
|
|
);
|
|
|
|
let node = Node::new(
|
|
"node9".to_string(),
|
|
vec![Label {
|
|
name: "Matrix".to_string(),
|
|
}],
|
|
properties,
|
|
);
|
|
|
|
db.create_node(node).unwrap();
|
|
|
|
let retrieved = db.get_node("node9").unwrap();
|
|
match retrieved.properties.get("matrix") {
|
|
Some(PropertyValue::List(outer)) => {
|
|
assert_eq!(outer.len(), 2);
|
|
match &outer[0] {
|
|
PropertyValue::List(inner) => assert_eq!(inner.len(), 2),
|
|
_ => panic!("Expected inner list"),
|
|
}
|
|
}
|
|
_ => panic!("Expected outer list"),
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Property-based tests using proptest
|
|
// ============================================================================
|
|
|
|
#[cfg(test)]
|
|
mod property_tests {
|
|
use super::*;
|
|
use proptest::prelude::*;
|
|
|
|
fn node_id_strategy() -> impl Strategy<Value = String> {
|
|
"[a-z][a-z0-9_]{0,20}".prop_map(|s| s.to_string())
|
|
}
|
|
|
|
fn label_strategy() -> impl Strategy<Value = Label> {
|
|
"[A-Z][a-zA-Z]{0,10}".prop_map(|name| Label { name })
|
|
}
|
|
|
|
fn property_value_strategy() -> impl Strategy<Value = PropertyValue> {
|
|
prop_oneof![
|
|
any::<String>().prop_map(PropertyValue::String),
|
|
any::<i64>().prop_map(PropertyValue::Integer),
|
|
any::<f64>()
|
|
.prop_filter("Must be finite", |x| x.is_finite())
|
|
.prop_map(PropertyValue::Float),
|
|
any::<bool>().prop_map(PropertyValue::Boolean),
|
|
Just(PropertyValue::Null),
|
|
]
|
|
}
|
|
|
|
proptest! {
|
|
#[test]
|
|
fn test_node_roundtrip(
|
|
id in node_id_strategy(),
|
|
labels in prop::collection::vec(label_strategy(), 0..5),
|
|
prop_count in 0..10usize
|
|
) {
|
|
let db = GraphDB::new();
|
|
|
|
let mut properties = Properties::new();
|
|
for i in 0..prop_count {
|
|
properties.insert(
|
|
format!("prop_{}", i),
|
|
PropertyValue::String(format!("value_{}", i))
|
|
);
|
|
}
|
|
|
|
let node = Node::new(id.clone(), labels.clone(), properties.clone());
|
|
db.create_node(node).unwrap();
|
|
|
|
let retrieved = db.get_node(&id).unwrap();
|
|
assert_eq!(retrieved.id, id);
|
|
assert_eq!(retrieved.labels.len(), labels.len());
|
|
assert_eq!(retrieved.properties.len(), properties.len());
|
|
}
|
|
|
|
#[test]
|
|
fn test_property_value_consistency(
|
|
value in property_value_strategy()
|
|
) {
|
|
let db = GraphDB::new();
|
|
|
|
let mut properties = Properties::new();
|
|
properties.insert("test_prop".to_string(), value.clone());
|
|
|
|
let node = Node::new(
|
|
"test_node".to_string(),
|
|
vec![],
|
|
properties
|
|
);
|
|
|
|
db.create_node(node).unwrap();
|
|
|
|
let retrieved = db.get_node("test_node").unwrap();
|
|
assert_eq!(retrieved.properties.get("test_prop"), Some(&value));
|
|
}
|
|
|
|
#[test]
|
|
fn test_many_nodes_no_collision(
|
|
ids in prop::collection::hash_set(node_id_strategy(), 10..100)
|
|
) {
|
|
let db = GraphDB::new();
|
|
|
|
for id in &ids {
|
|
let node = Node::new(
|
|
id.clone(),
|
|
vec![],
|
|
Properties::new()
|
|
);
|
|
db.create_node(node).unwrap();
|
|
}
|
|
|
|
for id in &ids {
|
|
assert!(db.get_node(id).is_some());
|
|
}
|
|
}
|
|
}
|
|
}
|