#![allow(unused_variables)]
use std::collections::HashSet;
use interstellar::p;
use interstellar::storage::GraphStorage;
use interstellar::traversal::__;
use interstellar::value::{Value, VertexId};
use crate::common::graphs::{create_small_graph, create_social_graph, TestGraphBuilder};
#[test]
fn navigation_order_is_deterministic() {
let tg = create_small_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let run1 = g.v_ids([tg.alice]).out().to_list();
let run2 = g.v_ids([tg.alice]).out().to_list();
let run3 = g.v_ids([tg.alice]).out().to_list();
assert_eq!(run1.len(), run2.len());
assert_eq!(run2.len(), run3.len());
for i in 0..run1.len() {
assert_eq!(
run1[i], run2[i],
"Mismatch at index {} between run1 and run2",
i
);
assert_eq!(
run2[i], run3[i],
"Mismatch at index {} between run2 and run3",
i
);
}
}
#[test]
fn order_by_asc_produces_ascending_sequence() {
let tg = create_small_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let ages = g
.v()
.has_label("person")
.values("age")
.order()
.by_asc()
.build()
.to_list();
let age_vals: Vec<i64> = ages.iter().filter_map(|v| v.as_i64()).collect();
assert_eq!(age_vals.len(), 3, "Should have 3 person ages");
for i in 1..age_vals.len() {
assert!(
age_vals[i - 1] <= age_vals[i],
"Order violation: {} > {} at index {}",
age_vals[i - 1],
age_vals[i],
i
);
}
assert_eq!(age_vals, vec![25, 30, 35]);
}
#[test]
fn order_by_desc_produces_descending_sequence() {
let tg = create_small_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let ages = g
.v()
.has_label("person")
.values("age")
.order()
.by_desc()
.build()
.to_list();
let age_vals: Vec<i64> = ages.iter().filter_map(|v| v.as_i64()).collect();
assert_eq!(age_vals.len(), 3);
for i in 1..age_vals.len() {
assert!(
age_vals[i - 1] >= age_vals[i],
"Order violation: {} < {} at index {}",
age_vals[i - 1],
age_vals[i],
i
);
}
assert_eq!(age_vals, vec![35, 30, 25]);
}
#[test]
fn order_is_stable_for_equal_values() {
let graph = TestGraphBuilder::new()
.add_person("Alice", 30)
.add_person("Bob", 30) .add_person("Charlie", 30) .add_person("Diana", 25)
.build();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let names_run1: Vec<String> = g
.v()
.has_label("person")
.order()
.by_key_asc("age")
.build()
.values("name")
.to_list()
.iter()
.filter_map(|v| v.as_str().map(|s| s.to_string()))
.collect();
let names_run2: Vec<String> = g
.v()
.has_label("person")
.order()
.by_key_asc("age")
.build()
.values("name")
.to_list()
.iter()
.filter_map(|v| v.as_str().map(|s| s.to_string()))
.collect();
assert_eq!(names_run1, names_run2, "Sort should be stable across runs");
assert_eq!(names_run1[0], "Diana");
}
#[test]
fn limit_preserves_traversal_order() {
let tg = create_small_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let first_two = g
.v_ids([tg.alice])
.out_labels(&["knows"])
.limit(2)
.to_list();
let first_two_again = g
.v_ids([tg.alice])
.out_labels(&["knows"])
.limit(2)
.to_list();
assert_eq!(first_two, first_two_again);
}
#[test]
fn range_preserves_element_order() {
let graph = TestGraphBuilder::new()
.add_person("A", 1)
.add_person("B", 2)
.add_person("C", 3)
.add_person("D", 4)
.add_person("E", 5)
.build();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let all_ordered = g.v().values("age").order().by_asc().build().to_list();
let middle = g
.v()
.values("age")
.order()
.by_asc()
.build()
.range(1, 4) .to_list();
assert_eq!(middle.len(), 3);
assert_eq!(middle[0], all_ordered[1]);
assert_eq!(middle[1], all_ordered[2]);
assert_eq!(middle[2], all_ordered[3]);
}
#[test]
fn dedup_keeps_first_occurrence() {
let tg = create_small_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let with_duplicates = g
.v_ids([tg.alice])
.repeat(__.out_labels(&["knows"]))
.times(4) .emit()
.to_list();
let alice_count = with_duplicates
.iter()
.filter(|v| v.as_vertex_id() == Some(tg.alice))
.count();
let deduped = g
.v_ids([tg.alice])
.repeat(__.out_labels(&["knows"]))
.times(4)
.emit()
.dedup()
.to_list();
let ids: Vec<VertexId> = deduped.iter().filter_map(|v| v.as_vertex_id()).collect();
let unique_ids: HashSet<VertexId> = ids.iter().copied().collect();
assert_eq!(
ids.len(),
unique_ids.len(),
"Dedup should remove all duplicates"
);
}
#[test]
fn dedup_by_key_uses_property_for_uniqueness() {
let graph = TestGraphBuilder::new()
.add_person_with_status("Alice", 30, "active")
.add_person_with_status("Bob", 25, "active") .add_person_with_status("Charlie", 35, "inactive")
.add_person_with_status("Diana", 28, "inactive") .build();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let deduped = g.v().has_label("person").dedup_by_key("status").to_list();
assert_eq!(deduped.len(), 2, "Should have one vertex per unique status");
let statuses: Vec<String> = deduped
.iter()
.filter_map(|v| {
if let Some(vid) = v.as_vertex_id() {
snapshot.get_vertex(vid).and_then(|vertex| {
vertex
.properties
.get("status")
.and_then(|s: &Value| s.as_str())
.map(|s: &str| s.to_string())
})
} else {
None
}
})
.collect();
let unique_statuses: HashSet<_> = statuses.iter().collect();
assert_eq!(statuses.len(), unique_statuses.len());
}
#[test]
fn dedup_on_empty_traversal_returns_empty() {
let tg = create_small_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let result = g.v().has_value("nonexistent", "value").dedup().to_list();
assert!(result.is_empty());
}
#[test]
fn dedup_single_element_returns_element() {
let tg = create_small_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let result = g.v_ids([tg.alice]).dedup().to_list();
assert_eq!(result.len(), 1);
assert_eq!(result[0].as_vertex_id(), Some(tg.alice));
}
#[test]
fn multiple_dedup_calls_are_idempotent() {
let tg = create_small_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let once = g
.v_ids([tg.alice])
.repeat(__.out_labels(&["knows"]))
.times(4)
.emit()
.dedup()
.to_list();
let twice = g
.v_ids([tg.alice])
.repeat(__.out_labels(&["knows"]))
.times(4)
.emit()
.dedup()
.dedup()
.to_list();
assert_eq!(once.len(), twice.len());
for (a, b) in once.iter().zip(twice.iter()) {
assert_eq!(a, b);
}
}
#[test]
fn dedup_works_on_property_values() {
let tg = create_social_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let all_cities = g.v().has_label("person").values("city").to_list();
let unique_cities = g.v().has_label("person").values("city").dedup().to_list();
assert!(
unique_cities.len() <= all_cities.len(),
"Dedup should not increase count"
);
let city_set: HashSet<_> = unique_cities.iter().collect();
assert_eq!(
unique_cities.len(),
city_set.len(),
"All values should be unique"
);
}
#[test]
fn group_preserves_all_elements() {
let tg = create_social_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let total_people = g.v().has_label("person").count();
let grouped = g
.v()
.has_label("person")
.group()
.by_key("city")
.by_value()
.build()
.to_list();
let mut total_in_groups = 0;
for result in &grouped {
if let Value::Map(map) = result {
for value in map.values() {
if let Value::List(list) = value {
total_in_groups += list.len();
}
}
}
}
assert_eq!(
total_in_groups as u64, total_people,
"Group should preserve all elements"
);
}
#[test]
fn group_count_produces_correct_counts() {
let tg = create_social_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let counts = g
.v()
.has_label("person")
.group_count()
.by_key("city")
.build()
.to_list();
assert_eq!(counts.len(), 1);
if let Value::Map(map) = &counts[0] {
assert!(map.contains_key("NYC"));
assert!(map.contains_key("SF"));
assert!(map.contains_key("LA"));
let nyc_count = map.get("NYC").and_then(|v| v.as_i64()).unwrap_or(0);
assert_eq!(nyc_count, 2, "NYC should have 2 people");
let sf_count = map.get("SF").and_then(|v| v.as_i64()).unwrap_or(0);
assert_eq!(sf_count, 2, "SF should have 2 people");
let la_count = map.get("LA").and_then(|v| v.as_i64()).unwrap_or(0);
assert_eq!(la_count, 1, "LA should have 1 person");
} else {
panic!("Expected Map result from group_count");
}
}
#[test]
fn fold_reduces_all_elements() {
let tg = create_small_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let count = g.v().has_label("person").fold(0usize, |acc, _| acc + 1);
assert_eq!(count, 3, "Fold should process all 3 people");
}
#[test]
fn sum_produces_correct_total() {
let tg = create_small_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let total = g.v().has_label("person").values("age").sum();
assert_eq!(total, Value::Int(90));
}
#[test]
fn min_max_return_correct_extremes() {
let tg = create_small_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let min_age = g.v().has_label("person").values("age").min();
let max_age = g.v().has_label("person").values("age").max();
assert_eq!(min_age, Some(Value::Int(25)));
assert_eq!(max_age, Some(Value::Int(35)));
}
#[test]
fn group_by_label_creates_correct_groups() {
let tg = create_small_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let grouped = g.v().group().by_label().by_value().build().to_list();
assert_eq!(grouped.len(), 1);
if let Value::Map(map) = &grouped[0] {
assert!(map.contains_key("person"), "Should have 'person' group");
assert!(map.contains_key("software"), "Should have 'software' group");
if let Some(Value::List(people)) = map.get("person") {
assert_eq!(people.len(), 3, "Should have 3 people");
}
if let Some(Value::List(software)) = map.get("software") {
assert_eq!(software.len(), 1, "Should have 1 software");
}
} else {
panic!("Expected Map result from group");
}
}
#[test]
fn path_contains_all_visited_elements() {
let tg = create_small_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let paths = g
.v_ids([tg.alice])
.out_labels(&["knows"]) .out_labels(&["knows"]) .with_path()
.path()
.to_list();
assert!(!paths.is_empty(), "Should have at least one path");
for p in &paths {
if let Value::List(path_elements) = p {
assert_eq!(
path_elements.len(),
3,
"Path should contain start + 2 navigation steps"
);
assert_eq!(
path_elements[0].as_vertex_id(),
Some(tg.alice),
"Path should start with Alice"
);
}
}
}
#[test]
fn as_labels_are_retrievable_via_select() {
let tg = create_small_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let results = g
.v_ids([tg.alice])
.as_("start")
.out_labels(&["knows"])
.as_("friend")
.select(&["start", "friend"])
.to_list();
assert!(!results.is_empty());
for result in &results {
if let Value::Map(map) = result {
assert!(map.contains_key("start"));
assert!(map.contains_key("friend"));
let start = map.get("start");
if let Some(v) = start {
assert_eq!(v.as_vertex_id(), Some(tg.alice));
}
} else {
panic!("Expected Map from select");
}
}
}
#[test]
fn path_tracks_through_repeat() {
let tg = create_small_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let paths = g
.v_ids([tg.alice])
.with_path()
.repeat(__.out_labels(&["knows"]))
.times(2)
.path()
.to_list();
for p in &paths {
if let Value::List(path_elements) = p {
assert!(
path_elements.len() >= 3,
"Path should have start + repeat iterations"
);
}
}
}
#[test]
fn path_is_independent_per_traverser() {
let tg = create_social_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let paths = g.v().has_label("person").with_path().out().path().to_list();
let start_vertices: HashSet<_> = paths
.iter()
.filter_map(|p| {
if let Value::List(elements) = p {
elements.first().and_then(|v| v.as_vertex_id())
} else {
None
}
})
.collect();
assert!(!start_vertices.is_empty());
}
#[test]
fn simple_path_excludes_cycles() {
let tg = create_small_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let paths = g
.v_ids([tg.alice])
.with_path()
.repeat(__.out_labels(&["knows"]).simple_path())
.times(5) .emit()
.path()
.to_list();
for p in &paths {
if let Value::List(elements) = p {
let ids: Vec<_> = elements.iter().filter_map(|v| v.as_vertex_id()).collect();
let unique: HashSet<_> = ids.iter().copied().collect();
assert_eq!(
ids.len(),
unique.len(),
"simple_path should prevent vertex revisits: {:?}",
ids
);
}
}
}
#[test]
fn select_one_returns_single_value() {
let tg = create_small_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let results = g
.v_ids([tg.alice])
.as_("person")
.out_labels(&["knows"])
.select_one("person")
.to_list();
for result in &results {
assert!(
result.as_vertex_id().is_some(),
"select_one should return vertex directly, got: {:?}",
result
);
assert_eq!(result.as_vertex_id(), Some(tg.alice));
}
}
#[test]
fn order_then_limit_produces_top_n() {
let tg = create_social_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let oldest_two = g
.v()
.has_label("person")
.order()
.by_key_desc("age")
.build()
.limit(2)
.values("name")
.to_list();
assert_eq!(oldest_two.len(), 2);
let names: Vec<_> = oldest_two
.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect();
assert!(names.contains(&"Charlie".to_string()));
}
#[test]
fn dedup_before_group_count_counts_unique() {
let tg = create_social_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let counts = g
.v()
.has_label("person")
.out_labels(&["knows"])
.out_labels(&["knows"])
.dedup()
.group_count()
.by_key("city")
.build()
.to_list();
if !counts.is_empty() {
if let Value::Map(map) = &counts[0] {
let total: i64 = map.values().filter_map(|v| v.as_i64()).sum();
let unique_fof = g
.v()
.has_label("person")
.out_labels(&["knows"])
.out_labels(&["knows"])
.dedup()
.count();
assert_eq!(total as u64, unique_fof);
}
}
}
#[test]
fn path_survives_filter_steps() {
let tg = create_small_graph();
let snapshot = tg.graph.snapshot();
let g = snapshot.gremlin();
let paths = g
.v_ids([tg.alice])
.with_path()
.out_labels(&["knows"])
.has_where("age", p::gt(20)) .path()
.to_list();
for p in &paths {
if let Value::List(elements) = p {
assert!(
elements.len() >= 2,
"Path should have start + at least one navigation"
);
assert_eq!(
elements[0].as_vertex_id(),
Some(tg.alice),
"Path should start with source vertex"
);
}
}
}