//! Cypher query execution correctness tests //! //! Tests to verify that Cypher queries execute correctly and return expected results. use ruvector_graph::{Edge, GraphDB, Label, Node, Properties, PropertyValue}; fn setup_test_graph() -> GraphDB { let db = GraphDB::new(); // Create people let mut alice_props = Properties::new(); alice_props.insert( "name".to_string(), PropertyValue::String("Alice".to_string()), ); alice_props.insert("age".to_string(), PropertyValue::Integer(30)); let mut bob_props = Properties::new(); bob_props.insert("name".to_string(), PropertyValue::String("Bob".to_string())); bob_props.insert("age".to_string(), PropertyValue::Integer(35)); let mut charlie_props = Properties::new(); charlie_props.insert( "name".to_string(), PropertyValue::String("Charlie".to_string()), ); charlie_props.insert("age".to_string(), PropertyValue::Integer(28)); db.create_node(Node::new( "alice".to_string(), vec![Label { name: "Person".to_string(), }], alice_props, )) .unwrap(); db.create_node(Node::new( "bob".to_string(), vec![Label { name: "Person".to_string(), }], bob_props, )) .unwrap(); db.create_node(Node::new( "charlie".to_string(), vec![Label { name: "Person".to_string(), }], charlie_props, )) .unwrap(); // Create relationships db.create_edge(Edge::new( "e1".to_string(), "alice".to_string(), "bob".to_string(), "KNOWS".to_string(), Properties::new(), )) .unwrap(); db.create_edge(Edge::new( "e2".to_string(), "bob".to_string(), "charlie".to_string(), "KNOWS".to_string(), Properties::new(), )) .unwrap(); db } #[test] fn test_execute_simple_match_all_nodes() { let db = setup_test_graph(); // TODO: Implement query execution // let results = db.execute("MATCH (n) RETURN n").unwrap(); // assert_eq!(results.len(), 3); // For now, just verify the graph was set up correctly assert!(db.get_node("alice").is_some()); assert!(db.get_node("bob").is_some()); assert!(db.get_node("charlie").is_some()); } #[test] fn test_execute_match_with_label_filter() { let db = setup_test_graph(); // TODO: Implement // let results = db.execute("MATCH (n:Person) RETURN n").unwrap(); // assert_eq!(results.len(), 3); assert!(db.get_node("alice").is_some()); } #[test] fn test_execute_match_with_property_filter() { let db = setup_test_graph(); // TODO: Implement // let results = db.execute("MATCH (n:Person {name: 'Alice'}) RETURN n").unwrap(); // assert_eq!(results.len(), 1); let alice = db.get_node("alice").unwrap(); assert_eq!( alice.properties.get("name"), Some(&PropertyValue::String("Alice".to_string())) ); } #[test] fn test_execute_match_with_where_clause() { let db = setup_test_graph(); // TODO: Implement // let results = db.execute("MATCH (n:Person) WHERE n.age > 30 RETURN n").unwrap(); // Should return Bob (35) // assert_eq!(results.len(), 1); let bob = db.get_node("bob").unwrap(); if let Some(PropertyValue::Integer(age)) = bob.properties.get("age") { assert!(*age > 30); } } #[test] fn test_execute_match_relationship() { let db = setup_test_graph(); // TODO: Implement // let results = db.execute("MATCH (a)-[r:KNOWS]->(b) RETURN a, r, b").unwrap(); // Should return 2 relationships assert!(db.get_edge("e1").is_some()); assert!(db.get_edge("e2").is_some()); } #[test] fn test_execute_create_node() { let db = GraphDB::new(); // TODO: Implement // db.execute("CREATE (n:Person {name: 'David', age: 40})").unwrap(); // For now, create manually let mut props = Properties::new(); props.insert( "name".to_string(), PropertyValue::String("David".to_string()), ); props.insert("age".to_string(), PropertyValue::Integer(40)); db.create_node(Node::new( "david".to_string(), vec![Label { name: "Person".to_string(), }], props, )) .unwrap(); let david = db.get_node("david").unwrap(); assert_eq!( david.properties.get("name"), Some(&PropertyValue::String("David".to_string())) ); } #[test] fn test_execute_count_aggregation() { let db = setup_test_graph(); // TODO: Implement // let results = db.execute("MATCH (n:Person) RETURN COUNT(n) AS count").unwrap(); // assert_eq!(results[0]["count"], 3); // Manual verification assert!(db.get_node("alice").is_some()); assert!(db.get_node("bob").is_some()); assert!(db.get_node("charlie").is_some()); } #[test] fn test_execute_sum_aggregation() { let db = setup_test_graph(); // TODO: Implement // let results = db.execute("MATCH (n:Person) RETURN SUM(n.age) AS total_age").unwrap(); // assert_eq!(results[0]["total_age"], 93); // 30 + 35 + 28 // Manual verification let ages: Vec = ["alice", "bob", "charlie"] .iter() .filter_map(|id| { db.get_node(*id).and_then(|n| { if let Some(PropertyValue::Integer(age)) = n.properties.get("age") { Some(*age) } else { None } }) }) .collect(); assert_eq!(ages.iter().sum::(), 93); } #[test] fn test_execute_avg_aggregation() { let db = setup_test_graph(); // TODO: Implement // let results = db.execute("MATCH (n:Person) RETURN AVG(n.age) AS avg_age").unwrap(); // assert_eq!(results[0]["avg_age"], 31.0); // (30 + 35 + 28) / 3 let ages: Vec = ["alice", "bob", "charlie"] .iter() .filter_map(|id| { db.get_node(*id).and_then(|n| { if let Some(PropertyValue::Integer(age)) = n.properties.get("age") { Some(*age) } else { None } }) }) .collect(); let avg = ages.iter().sum::() as f64 / ages.len() as f64; assert!((avg - 31.0).abs() < 0.1); } #[test] fn test_execute_order_by() { let db = setup_test_graph(); // TODO: Implement // let results = db.execute("MATCH (n:Person) RETURN n ORDER BY n.age ASC").unwrap(); // First should be Charlie (28), last should be Bob (35) let mut ages: Vec = ["alice", "bob", "charlie"] .iter() .filter_map(|id| { db.get_node(*id).and_then(|n| { if let Some(PropertyValue::Integer(age)) = n.properties.get("age") { Some(*age) } else { None } }) }) .collect(); ages.sort(); assert_eq!(ages, vec![28, 30, 35]); } #[test] fn test_execute_limit() { let db = setup_test_graph(); // TODO: Implement // let results = db.execute("MATCH (n:Person) RETURN n LIMIT 2").unwrap(); // assert_eq!(results.len(), 2); assert!(db.get_node("alice").is_some()); } #[test] fn test_execute_path_query() { let db = setup_test_graph(); // TODO: Implement // let results = db.execute("MATCH p = (a:Person)-[:KNOWS*1..2]->(b:Person) RETURN p").unwrap(); // Should find paths: Alice->Bob, Bob->Charlie, Alice->Bob->Charlie let e1 = db.get_edge("e1").unwrap(); let e2 = db.get_edge("e2").unwrap(); assert_eq!(e1.from, "alice"); assert_eq!(e1.to, "bob"); assert_eq!(e2.from, "bob"); assert_eq!(e2.to, "charlie"); } // ============================================================================ // Complex Query Execution Tests // ============================================================================ #[test] fn test_execute_multi_hop_traversal() { let db = setup_test_graph(); // TODO: Implement // Find all people connected to Alice within 2 hops // let results = db.execute(" // MATCH (alice:Person {name: 'Alice'})-[:KNOWS*1..2]->(connected) // RETURN DISTINCT connected.name // ").unwrap(); // Should find Bob (1 hop) and Charlie (2 hops) assert!(db.get_node("bob").is_some()); assert!(db.get_node("charlie").is_some()); } #[test] fn test_execute_pattern_matching() { let db = setup_test_graph(); // TODO: Implement // let results = db.execute(" // MATCH (a:Person)-[:KNOWS]->(b:Person)-[:KNOWS]->(c:Person) // RETURN a.name, c.name // ").unwrap(); // Should find Alice knows Charlie through Bob assert!(db.get_edge("e1").is_some()); assert!(db.get_edge("e2").is_some()); } #[test] fn test_execute_collect_aggregation() { let db = setup_test_graph(); // TODO: Implement // let results = db.execute(" // MATCH (p:Person)-[:KNOWS]->(friend) // RETURN p.name, COLLECT(friend.name) AS friends // ").unwrap(); // Alice: [Bob], Bob: [Charlie], Charlie: [] assert!(db.get_edge("e1").is_some()); } #[test] fn test_execute_optional_match() { let db = setup_test_graph(); // TODO: Implement // let results = db.execute(" // MATCH (p:Person) // OPTIONAL MATCH (p)-[:KNOWS]->(friend) // RETURN p.name, friend.name // ").unwrap(); // Should return all people, some with null friends assert!(db.get_node("charlie").is_some()); } // ============================================================================ // Result Verification Tests // ============================================================================ #[test] fn test_query_result_schema() { // TODO: Implement // Verify that query results have correct schema // let db = setup_test_graph(); // let results = db.execute("MATCH (n:Person) RETURN n.name AS name, n.age AS age").unwrap(); // assert!(results.has_column("name")); // assert!(results.has_column("age")); } #[test] fn test_query_result_ordering() { // TODO: Implement // Verify that ORDER BY is correctly applied } #[test] fn test_query_result_pagination() { // TODO: Implement // Verify SKIP and LIMIT work correctly together } // ============================================================================ // Error Handling Tests // ============================================================================ #[test] fn test_execute_invalid_property_access() { // TODO: Implement // let db = setup_test_graph(); // let result = db.execute("MATCH (n:Person) WHERE n.nonexistent > 5 RETURN n"); // Should handle gracefully (return no results or error depending on semantics) } #[test] fn test_execute_type_mismatch() { // TODO: Implement // let db = setup_test_graph(); // let result = db.execute("MATCH (n:Person) WHERE n.name > 5 RETURN n"); // Should handle type mismatch error }