mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-05-25 06:36:37 +00:00
Pre-existing rustfmt drift across the workspace was blocking CI's `Rustfmt` check on PR #373 + PR #377. Running plain `cargo fmt` reformats 427 files; no semantic changes, no logic changes, no behavior changes — just what rustfmt already wanted. None of the touched files are in ruvector-rabitq, ruvector-rulake, or the new mirror-rulake workflow — those were already fmt-clean per the per-crate checks on commits5a4b0d782,5f32fd450,f5003bc7b. Drift is in cognitum-gate-kernel, mcp-brain, nervous-system, prime-radiant, ruqu-core, ruvector-attention, ruvector-mincut, ruvix/* and sub-crates, plus several examples. Verified post-fmt: cargo check -p ruvector-rabitq -p ruvector-rulake → clean cargo clippy -p ... -p ... --all-targets -- -D warnings → clean cargo test -p ... -p ... --release → 82/82 pass Intentionally does NOT touch clippy drift — many more warnings (missing docs, precision-loss casts, too-many-args, unsafe-safety- docs) spread across unrelated crates, each category a cross-cutting design decision that deserves its own review. With this commit Rustfmt CI goes green on PR #373 and PR #377. Clippy will still fail — that's honest pre-existing state for a separate dedicated PR. Co-Authored-By: claude-flow <ruv@ruv.net>
381 lines
10 KiB
Rust
381 lines
10 KiB
Rust
//! Neo4j compatibility tests
|
|
//!
|
|
//! Tests to verify that RuVector graph database is compatible with Neo4j
|
|
//! in terms of query syntax and result format.
|
|
|
|
use ruvector_graph::{Edge, GraphDB, Label, Node, Properties, PropertyValue};
|
|
|
|
fn setup_movie_graph() -> GraphDB {
|
|
let db = GraphDB::new();
|
|
|
|
// Actors
|
|
let mut keanu_props = Properties::new();
|
|
keanu_props.insert(
|
|
"name".to_string(),
|
|
PropertyValue::String("Keanu Reeves".to_string()),
|
|
);
|
|
keanu_props.insert("born".to_string(), PropertyValue::Integer(1964));
|
|
|
|
let mut carrie_props = Properties::new();
|
|
carrie_props.insert(
|
|
"name".to_string(),
|
|
PropertyValue::String("Carrie-Anne Moss".to_string()),
|
|
);
|
|
carrie_props.insert("born".to_string(), PropertyValue::Integer(1967));
|
|
|
|
let mut laurence_props = Properties::new();
|
|
laurence_props.insert(
|
|
"name".to_string(),
|
|
PropertyValue::String("Laurence Fishburne".to_string()),
|
|
);
|
|
laurence_props.insert("born".to_string(), PropertyValue::Integer(1961));
|
|
|
|
// Movies
|
|
let mut matrix_props = Properties::new();
|
|
matrix_props.insert(
|
|
"title".to_string(),
|
|
PropertyValue::String("The Matrix".to_string()),
|
|
);
|
|
matrix_props.insert("released".to_string(), PropertyValue::Integer(1999));
|
|
matrix_props.insert(
|
|
"tagline".to_string(),
|
|
PropertyValue::String("Welcome to the Real World".to_string()),
|
|
);
|
|
|
|
db.create_node(Node::new(
|
|
"keanu".to_string(),
|
|
vec![Label {
|
|
name: "Person".to_string(),
|
|
}],
|
|
keanu_props,
|
|
))
|
|
.unwrap();
|
|
|
|
db.create_node(Node::new(
|
|
"carrie".to_string(),
|
|
vec![Label {
|
|
name: "Person".to_string(),
|
|
}],
|
|
carrie_props,
|
|
))
|
|
.unwrap();
|
|
|
|
db.create_node(Node::new(
|
|
"laurence".to_string(),
|
|
vec![Label {
|
|
name: "Person".to_string(),
|
|
}],
|
|
laurence_props,
|
|
))
|
|
.unwrap();
|
|
|
|
db.create_node(Node::new(
|
|
"matrix".to_string(),
|
|
vec![Label {
|
|
name: "Movie".to_string(),
|
|
}],
|
|
matrix_props,
|
|
))
|
|
.unwrap();
|
|
|
|
// Relationships
|
|
let mut keanu_role = Properties::new();
|
|
keanu_role.insert(
|
|
"roles".to_string(),
|
|
PropertyValue::List(vec![PropertyValue::String("Neo".to_string())]),
|
|
);
|
|
|
|
let mut carrie_role = Properties::new();
|
|
carrie_role.insert(
|
|
"roles".to_string(),
|
|
PropertyValue::List(vec![PropertyValue::String("Trinity".to_string())]),
|
|
);
|
|
|
|
let mut laurence_role = Properties::new();
|
|
laurence_role.insert(
|
|
"roles".to_string(),
|
|
PropertyValue::List(vec![PropertyValue::String("Morpheus".to_string())]),
|
|
);
|
|
|
|
db.create_edge(Edge::new(
|
|
"e1".to_string(),
|
|
"keanu".to_string(),
|
|
"matrix".to_string(),
|
|
"ACTED_IN".to_string(),
|
|
keanu_role,
|
|
))
|
|
.unwrap();
|
|
|
|
db.create_edge(Edge::new(
|
|
"e2".to_string(),
|
|
"carrie".to_string(),
|
|
"matrix".to_string(),
|
|
"ACTED_IN".to_string(),
|
|
carrie_role,
|
|
))
|
|
.unwrap();
|
|
|
|
db.create_edge(Edge::new(
|
|
"e3".to_string(),
|
|
"laurence".to_string(),
|
|
"matrix".to_string(),
|
|
"ACTED_IN".to_string(),
|
|
laurence_role,
|
|
))
|
|
.unwrap();
|
|
|
|
db
|
|
}
|
|
|
|
// ============================================================================
|
|
// Neo4j Query Compatibility Tests
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_neo4j_match_all_nodes() {
|
|
let db = setup_movie_graph();
|
|
|
|
// Neo4j query: MATCH (n) RETURN n
|
|
// TODO: Implement query execution
|
|
// let results = db.execute("MATCH (n) RETURN n").unwrap();
|
|
// assert_eq!(results.len(), 4); // 3 people + 1 movie
|
|
|
|
// For now, verify graph setup
|
|
assert!(db.get_node("keanu").is_some());
|
|
assert!(db.get_node("matrix").is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn test_neo4j_match_with_label() {
|
|
let db = setup_movie_graph();
|
|
|
|
// Neo4j query: MATCH (p:Person) RETURN p
|
|
// TODO: Implement
|
|
// let results = db.execute("MATCH (p:Person) RETURN p").unwrap();
|
|
// assert_eq!(results.len(), 3);
|
|
|
|
// Verify label filtering would work
|
|
let keanu = db.get_node("keanu").unwrap();
|
|
assert_eq!(keanu.labels[0].name, "Person");
|
|
}
|
|
|
|
#[test]
|
|
fn test_neo4j_match_with_properties() {
|
|
let db = setup_movie_graph();
|
|
|
|
// Neo4j query: MATCH (m:Movie {title: 'The Matrix'}) RETURN m
|
|
// TODO: Implement
|
|
// let results = db.execute("MATCH (m:Movie {title: 'The Matrix'}) RETURN m").unwrap();
|
|
// assert_eq!(results.len(), 1);
|
|
|
|
let matrix = db.get_node("matrix").unwrap();
|
|
assert_eq!(
|
|
matrix.properties.get("title"),
|
|
Some(&PropertyValue::String("The Matrix".to_string()))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_neo4j_match_relationship() {
|
|
let db = setup_movie_graph();
|
|
|
|
// Neo4j query: MATCH (a:Person)-[r:ACTED_IN]->(m:Movie) RETURN a, r, m
|
|
// TODO: Implement
|
|
// let results = db.execute("MATCH (a:Person)-[r:ACTED_IN]->(m:Movie) RETURN a, r, m").unwrap();
|
|
// assert_eq!(results.len(), 3);
|
|
|
|
let edge = db.get_edge("e1").unwrap();
|
|
assert_eq!(edge.edge_type, "ACTED_IN");
|
|
}
|
|
|
|
#[test]
|
|
fn test_neo4j_where_clause() {
|
|
let db = setup_movie_graph();
|
|
|
|
// Neo4j query: MATCH (p:Person) WHERE p.born > 1965 RETURN p
|
|
// TODO: Implement
|
|
// let results = db.execute("MATCH (p:Person) WHERE p.born > 1965 RETURN p").unwrap();
|
|
// assert_eq!(results.len(), 1); // Only Carrie-Anne Moss
|
|
|
|
let carrie = db.get_node("carrie").unwrap();
|
|
if let Some(PropertyValue::Integer(born)) = carrie.properties.get("born") {
|
|
assert!(*born > 1965);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_neo4j_count_aggregation() {
|
|
let db = setup_movie_graph();
|
|
|
|
// Neo4j query: MATCH (p:Person) RETURN COUNT(p)
|
|
// TODO: Implement
|
|
// let results = db.execute("MATCH (p:Person) RETURN COUNT(p)").unwrap();
|
|
// assert_eq!(results[0]["count"], 3);
|
|
|
|
// Manually verify
|
|
assert!(db.get_node("keanu").is_some());
|
|
assert!(db.get_node("carrie").is_some());
|
|
assert!(db.get_node("laurence").is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn test_neo4j_collect_aggregation() {
|
|
let db = setup_movie_graph();
|
|
|
|
// Neo4j query: MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
|
|
// RETURN m.title, COLLECT(p.name) AS actors
|
|
// TODO: Implement
|
|
// let results = db.execute("...").unwrap();
|
|
|
|
// Verify relationships exist
|
|
assert!(db.get_edge("e1").is_some());
|
|
assert!(db.get_edge("e2").is_some());
|
|
assert!(db.get_edge("e3").is_some());
|
|
}
|
|
|
|
// ============================================================================
|
|
// Neo4j Data Type Compatibility
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_neo4j_string_property() {
|
|
let db = GraphDB::new();
|
|
|
|
let mut props = Properties::new();
|
|
props.insert(
|
|
"name".to_string(),
|
|
PropertyValue::String("Test".to_string()),
|
|
);
|
|
|
|
db.create_node(Node::new("n1".to_string(), vec![], props))
|
|
.unwrap();
|
|
|
|
let node = db.get_node("n1").unwrap();
|
|
assert!(matches!(
|
|
node.properties.get("name"),
|
|
Some(PropertyValue::String(_))
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn test_neo4j_integer_property() {
|
|
let db = GraphDB::new();
|
|
|
|
let mut props = Properties::new();
|
|
props.insert("count".to_string(), PropertyValue::Integer(42));
|
|
|
|
db.create_node(Node::new("n1".to_string(), vec![], props))
|
|
.unwrap();
|
|
|
|
let node = db.get_node("n1").unwrap();
|
|
assert_eq!(
|
|
node.properties.get("count"),
|
|
Some(&PropertyValue::Integer(42))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_neo4j_float_property() {
|
|
let db = GraphDB::new();
|
|
|
|
let mut props = Properties::new();
|
|
props.insert("score".to_string(), PropertyValue::Float(3.14));
|
|
|
|
db.create_node(Node::new("n1".to_string(), vec![], props))
|
|
.unwrap();
|
|
|
|
let node = db.get_node("n1").unwrap();
|
|
assert_eq!(
|
|
node.properties.get("score"),
|
|
Some(&PropertyValue::Float(3.14))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_neo4j_boolean_property() {
|
|
let db = GraphDB::new();
|
|
|
|
let mut props = Properties::new();
|
|
props.insert("active".to_string(), PropertyValue::Boolean(true));
|
|
|
|
db.create_node(Node::new("n1".to_string(), vec![], props))
|
|
.unwrap();
|
|
|
|
let node = db.get_node("n1").unwrap();
|
|
assert_eq!(
|
|
node.properties.get("active"),
|
|
Some(&PropertyValue::Boolean(true))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_neo4j_list_property() {
|
|
let db = GraphDB::new();
|
|
|
|
let mut props = Properties::new();
|
|
props.insert(
|
|
"tags".to_string(),
|
|
PropertyValue::List(vec![
|
|
PropertyValue::String("tag1".to_string()),
|
|
PropertyValue::String("tag2".to_string()),
|
|
]),
|
|
);
|
|
|
|
db.create_node(Node::new("n1".to_string(), vec![], props))
|
|
.unwrap();
|
|
|
|
let node = db.get_node("n1").unwrap();
|
|
assert!(matches!(
|
|
node.properties.get("tags"),
|
|
Some(PropertyValue::List(_))
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn test_neo4j_float_array_property() {
|
|
let db = GraphDB::new();
|
|
|
|
let vec = vec![0.1, 0.2, 0.3];
|
|
let mut props = Properties::new();
|
|
props.insert(
|
|
"_vector".to_string(),
|
|
PropertyValue::FloatArray(vec.clone()),
|
|
);
|
|
|
|
db.create_node(Node::new("n1".to_string(), vec![], props))
|
|
.unwrap();
|
|
|
|
let node = db.get_node("n1").unwrap();
|
|
let stored = node.properties.get("_vector");
|
|
assert!(matches!(stored, Some(PropertyValue::FloatArray(arr)) if arr.len() == 3));
|
|
let Some(PropertyValue::FloatArray(arr)) = stored else {
|
|
return;
|
|
};
|
|
assert_eq!(arr, &vec);
|
|
}
|
|
|
|
#[test]
|
|
fn test_float_array_constructor() {
|
|
use ruvector_graph::PropertyValue;
|
|
|
|
let vec = vec![1.0f32, 2.0, 3.0];
|
|
let pv = PropertyValue::float_array(vec.clone());
|
|
assert!(matches!(pv, PropertyValue::FloatArray(arr) if &arr == &vec));
|
|
}
|
|
|
|
// ============================================================================
|
|
// Known Differences from Neo4j
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_documented_differences() {
|
|
// Document any intentional differences from Neo4j behavior
|
|
// For example:
|
|
// - Different default values
|
|
// - Different error messages
|
|
// - Different performance characteristics
|
|
// - Missing features
|
|
|
|
// This test serves as documentation
|
|
assert!(true);
|
|
}
|