use std::collections::HashMap;
use interstellar::storage::Graph;
use interstellar::traversal::__;
use interstellar::value::{Value, VertexId};
#[allow(dead_code)]
struct TestGraph {
graph: Graph,
alice: VertexId,
bob: VertexId,
charlie: VertexId,
graphdb: VertexId,
redis: VertexId,
}
fn create_test_graph() -> TestGraph {
let graph = Graph::new();
let alice = graph.add_vertex("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Alice".to_string()));
props.insert("age".to_string(), Value::Int(30));
props.insert("status".to_string(), Value::String("active".to_string()));
props.insert("priority".to_string(), Value::Int(1));
props
});
let bob = graph.add_vertex("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Bob".to_string()));
props.insert("age".to_string(), Value::Int(25));
props.insert("status".to_string(), Value::String("inactive".to_string()));
props.insert("priority".to_string(), Value::Int(2));
props
});
let charlie = graph.add_vertex("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Charlie".to_string()));
props.insert("age".to_string(), Value::Int(35));
props.insert("status".to_string(), Value::String("active".to_string()));
props.insert("priority".to_string(), Value::Int(1));
props
});
let graphdb = graph.add_vertex("software", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("GraphDB".to_string()));
props.insert("version".to_string(), Value::Float(2.0));
props.insert("priority".to_string(), Value::Int(3));
props
});
let redis = graph.add_vertex("software", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Redis".to_string()));
props.insert("version".to_string(), Value::Float(7.0));
props.insert("priority".to_string(), Value::Int(2));
props
});
graph
.add_edge(alice, bob, "knows", {
let mut props = HashMap::new();
props.insert("since".to_string(), Value::Int(2020));
props
})
.unwrap();
graph
.add_edge(bob, charlie, "knows", {
let mut props = HashMap::new();
props.insert("since".to_string(), Value::Int(2021));
props
})
.unwrap();
graph
.add_edge(alice, graphdb, "created", {
let mut props = HashMap::new();
props.insert("year".to_string(), Value::Int(2019));
props
})
.unwrap();
graph
.add_edge(bob, redis, "created", {
let mut props = HashMap::new();
props.insert("year".to_string(), Value::Int(2020));
props
})
.unwrap();
TestGraph {
graph,
alice,
bob,
charlie,
graphdb,
redis,
}
}
#[test]
fn test_branch_routes_by_label() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let results = g
.v()
.branch(__.label())
.option("person", __.out_labels(&["knows"]))
.option("software", __.in_labels(&["created"]))
.values("name")
.to_list();
assert!(results.contains(&Value::String("Bob".to_string())));
assert!(results.contains(&Value::String("Charlie".to_string())));
assert!(results.contains(&Value::String("Alice".to_string())));
assert_eq!(results.len(), 4); }
#[test]
fn test_branch_with_identity_option() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let results = g
.v()
.branch(__.label())
.option("person", __.identity())
.option("software", __.identity())
.values("name")
.to_list();
assert_eq!(results.len(), 5);
assert!(results.contains(&Value::String("Alice".to_string())));
assert!(results.contains(&Value::String("Bob".to_string())));
assert!(results.contains(&Value::String("Charlie".to_string())));
assert!(results.contains(&Value::String("GraphDB".to_string())));
assert!(results.contains(&Value::String("Redis".to_string())));
}
#[test]
fn test_branch_with_property_value_key() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let results = g
.v()
.has_label("person")
.branch(__.values("status"))
.option("active", __.out_labels(&["knows"]))
.option("inactive", __.identity())
.values("name")
.to_list();
assert!(results.contains(&Value::String("Bob".to_string())));
let bob_count = results
.iter()
.filter(|v| **v == Value::String("Bob".to_string()))
.count();
assert_eq!(bob_count, 2);
}
#[test]
fn test_branch_with_none_default() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let results = g
.v()
.branch(__.label())
.option("person", __.values("name"))
.option_none(__.constant(Value::String("other".to_string())))
.to_list();
assert!(results.contains(&Value::String("Alice".to_string())));
assert!(results.contains(&Value::String("Bob".to_string())));
assert!(results.contains(&Value::String("Charlie".to_string())));
let other_count = results
.iter()
.filter(|v| **v == Value::String("other".to_string()))
.count();
assert_eq!(other_count, 2);
}
#[test]
fn test_branch_none_branch_used_when_no_match() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let results = g
.v()
.branch(__.label())
.option("person", __.values("age"))
.option_none(__.values("version"))
.to_list();
assert!(results.contains(&Value::Int(30)));
assert!(results.contains(&Value::Int(25)));
assert!(results.contains(&Value::Int(35)));
assert!(results.contains(&Value::Float(2.0)));
assert!(results.contains(&Value::Float(7.0)));
}
#[test]
fn test_branch_filters_unmatched_without_none() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let count = g
.v()
.branch(__.label())
.option("person", __.identity())
.count();
assert_eq!(count, 3);
}
#[test]
fn test_branch_filters_when_branch_traversal_empty() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let results = g
.v()
.branch(__.values("status")) .option("active", __.values("name"))
.option("inactive", __.values("name"))
.to_list();
assert!(results.contains(&Value::String("Alice".to_string())));
assert!(results.contains(&Value::String("Bob".to_string())));
assert!(results.contains(&Value::String("Charlie".to_string())));
assert_eq!(results.len(), 3);
}
#[test]
fn test_branch_with_integer_key() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let results = g
.v()
.branch(__.values("priority"))
.option(1i64, __.values("name"))
.option(2i64, __.constant(Value::String("priority-2".to_string())))
.option(3i64, __.constant(Value::String("priority-3".to_string())))
.to_list();
assert!(results.contains(&Value::String("Alice".to_string())));
assert!(results.contains(&Value::String("Charlie".to_string())));
let p2_count = results
.iter()
.filter(|v| **v == Value::String("priority-2".to_string()))
.count();
assert_eq!(p2_count, 2);
assert!(results.contains(&Value::String("priority-3".to_string())));
}
#[test]
fn test_branch_with_i32_key() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let results = g
.v()
.branch(__.values("priority"))
.option(1i32, __.values("name"))
.option_none(__.constant(Value::String("other".to_string())))
.to_list();
assert!(results.contains(&Value::String("Alice".to_string())));
assert!(results.contains(&Value::String("Charlie".to_string())));
let other_count = results
.iter()
.filter(|v| **v == Value::String("other".to_string()))
.count();
assert_eq!(other_count, 3); }
#[test]
fn test_branch_with_boolean_key() {
let graph = Graph::new();
let _v1 = graph.add_vertex("item", {
let mut props = HashMap::new();
props.insert("active".to_string(), Value::Bool(true));
props.insert("name".to_string(), Value::String("Item1".to_string()));
props
});
let _v2 = graph.add_vertex("item", {
let mut props = HashMap::new();
props.insert("active".to_string(), Value::Bool(false));
props.insert("name".to_string(), Value::String("Item2".to_string()));
props
});
let _v3 = graph.add_vertex("item", {
let mut props = HashMap::new();
props.insert("active".to_string(), Value::Bool(true));
props.insert("name".to_string(), Value::String("Item3".to_string()));
props
});
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let results = g
.v()
.branch(__.values("active"))
.option(true, __.values("name"))
.option(false, __.constant(Value::String("inactive".to_string())))
.to_list();
assert!(results.contains(&Value::String("Item1".to_string())));
assert!(results.contains(&Value::String("Item3".to_string())));
assert!(results.contains(&Value::String("inactive".to_string())));
assert_eq!(results.len(), 3);
}
#[test]
fn test_choose_by_equivalent_to_branch() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let branch_results = g
.v()
.branch(__.label())
.option("person", __.out())
.to_list();
let choose_results = g
.v()
.choose_by(__.label())
.option("person", __.out())
.to_list();
assert_eq!(branch_results.len(), choose_results.len());
for result in &branch_results {
assert!(choose_results.contains(result));
}
}
#[test]
fn test_choose_by_with_option_none() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let results = g
.v()
.choose_by(__.values("status"))
.option("active", __.constant(Value::String("ACTIVE".to_string())))
.option(
"inactive",
__.constant(Value::String("INACTIVE".to_string())),
)
.option_none(__.constant(Value::String("NO_STATUS".to_string())))
.to_list();
let active_count = results
.iter()
.filter(|v| **v == Value::String("ACTIVE".to_string()))
.count();
assert_eq!(active_count, 2);
assert!(results.contains(&Value::String("INACTIVE".to_string())));
let no_status_count = results
.iter()
.filter(|v| **v == Value::String("NO_STATUS".to_string()))
.count();
assert_eq!(no_status_count, 2);
}
#[test]
fn test_branch_preserves_path() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let results = g
.v_ids([test.alice])
.as_("start")
.branch(__.label())
.option("person", __.out_labels(&["knows"]).as_("end"))
.path()
.to_list();
assert_eq!(results.len(), 1);
if let Value::List(path) = &results[0] {
assert_eq!(path.len(), 2); } else {
panic!("Expected path to be a list");
}
}
#[test]
fn test_branch_path_with_multiple_inputs() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let results = g
.v()
.has_label("person")
.as_("person")
.branch(__.values("status"))
.option("active", __.out_labels(&["knows"]).as_("friend"))
.option("inactive", __.identity().as_("self"))
.path()
.to_list();
for result in &results {
if let Value::List(path) = result {
assert!(!path.is_empty());
}
}
}
#[test]
fn test_branch_builder_continuation_out() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let results = g
.v()
.has_label("person")
.branch(__.values("status"))
.option("active", __.identity())
.option("inactive", __.identity())
.out() .values("name")
.to_list();
assert!(!results.is_empty());
}
#[test]
fn test_branch_builder_continuation_has_label() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let count = g
.v()
.branch(__.label())
.option("person", __.out())
.option("software", __.in_())
.has_label("person") .count();
assert!(count > 0);
}
#[test]
fn test_branch_builder_terminal_count() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let count = g
.v()
.branch(__.label())
.option("person", __.identity())
.count();
assert_eq!(count, 3);
}
#[test]
fn test_branch_builder_terminal_next() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let result = g
.v()
.has_label("person")
.branch(__.label())
.option("person", __.values("name"))
.next();
assert!(result.is_some());
if let Some(Value::String(name)) = result {
assert!(name == "Alice" || name == "Bob" || name == "Charlie");
}
}
#[test]
fn test_branch_builder_terminal_has_next() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let has_results = g
.v()
.has_label("person")
.branch(__.label())
.option("person", __.identity())
.has_next();
assert!(has_results);
let no_results = g
.v()
.has_label("nonexistent")
.branch(__.label())
.option("nonexistent", __.identity())
.has_next();
assert!(!no_results);
}
#[test]
fn test_branch_with_empty_input() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let results = g
.v()
.has_label("nonexistent")
.branch(__.label())
.option("nonexistent", __.identity())
.to_list();
assert!(results.is_empty());
}
#[test]
fn test_branch_option_produces_multiple_results() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let results = g
.v_ids([test.alice])
.branch(__.label())
.option("person", __.out()) .to_list();
assert_eq!(results.len(), 2);
}
#[test]
fn test_branch_multiple_options_same_key_overwrites() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let results = g
.v()
.has_label("person")
.branch(__.label())
.option("person", __.constant(Value::String("first".to_string())))
.option("person", __.constant(Value::String("second".to_string()))) .to_list();
for result in &results {
assert_eq!(*result, Value::String("second".to_string()));
}
}
#[test]
fn test_branch_chained_with_filter() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let results = g
.v()
.branch(__.label())
.option("person", __.out_labels(&["knows"]))
.option("software", __.in_labels(&["created"]))
.has_label("person") .values("name")
.dedup()
.to_list();
for result in &results {
if let Value::String(name) = result {
assert!(
name == "Alice" || name == "Bob" || name == "Charlie",
"Unexpected name: {}",
name
);
}
}
}
#[test]
fn test_branch_with_transform_in_option() {
let test = create_test_graph();
let snapshot = test.graph.snapshot();
let g = snapshot.gremlin();
let results = g
.v()
.has_label("person")
.branch(__.values("status"))
.option("active", __.values("age"))
.option("inactive", __.values("age"))
.to_list();
assert_eq!(results.len(), 3);
assert!(results.contains(&Value::Int(30)));
assert!(results.contains(&Value::Int(25)));
assert!(results.contains(&Value::Int(35)));
}