#[tokio::test]
async fn test_traverse_depth_2() {
let store = setup_memory_store();
let a = Uuid::new_v4();
let b = Uuid::new_v4();
let c = Uuid::new_v4();
let d = Uuid::new_v4();
store
.upsert_edge(make_edge(a, b, EdgeRelation::Extends, 1.0))
.await
.unwrap();
store
.upsert_edge(make_edge(b, c, EdgeRelation::Extends, 2.0))
.await
.unwrap();
store
.upsert_edge(make_edge(c, d, EdgeRelation::Extends, 3.0))
.await
.unwrap();
let request = TraversalRequest {
roots: vec![a],
options: TraversalOptions::new(2).with_direction(Direction::Out),
include_roots: true,
include_properties: false,
execution_budget: Default::default(),
};
let paths = store.traverse(request).await.unwrap();
assert_eq!(paths.len(), 1);
let path = &paths[0];
let node_ids: Vec<Uuid> = path.nodes.iter().map(|n| n.node_id).collect();
assert!(node_ids.contains(&a));
assert!(node_ids.contains(&b));
assert!(node_ids.contains(&c));
assert!(!node_ids.contains(&d));
}
#[tokio::test]
async fn test_traverse_dedups_multipath_node() {
let store = setup_memory_store();
let a = Uuid::new_v4();
let b = Uuid::new_v4();
let c = Uuid::new_v4();
let d = Uuid::new_v4();
store
.upsert_edge(make_edge(a, b, EdgeRelation::Extends, 1.0))
.await
.unwrap();
store
.upsert_edge(make_edge(a, c, EdgeRelation::Extends, 1.0))
.await
.unwrap();
store
.upsert_edge(make_edge(b, d, EdgeRelation::Extends, 1.0))
.await
.unwrap();
store
.upsert_edge(make_edge(c, d, EdgeRelation::Extends, 1.0))
.await
.unwrap();
let request = TraversalRequest {
roots: vec![a],
options: TraversalOptions::new(3).with_direction(Direction::Out),
include_roots: false,
include_properties: false,
execution_budget: Default::default(),
};
let paths = store.traverse(request).await.unwrap();
assert_eq!(paths.len(), 1);
let nodes = &paths[0].nodes;
let d_count = nodes.iter().filter(|n| n.node_id == d).count();
assert_eq!(d_count, 1, "D must appear exactly once (dedup multi-path)");
assert_eq!(nodes.iter().filter(|n| n.node_id == b).count(), 1);
assert_eq!(nodes.iter().filter(|n| n.node_id == c).count(), 1);
}
#[tokio::test]
async fn test_traverse_preserves_first_path_metadata() {
let store = setup_memory_store();
let a = Uuid::new_v4();
let b = Uuid::new_v4();
let c = Uuid::new_v4();
let d = Uuid::new_v4();
store
.upsert_edge(make_edge(a, b, EdgeRelation::Extends, 1.0))
.await
.unwrap();
store
.upsert_edge(make_edge(a, c, EdgeRelation::Extends, 1.0))
.await
.unwrap();
store
.upsert_edge(make_edge(b, d, EdgeRelation::Extends, 1.0))
.await
.unwrap();
store
.upsert_edge(make_edge(c, d, EdgeRelation::Extends, 1.0))
.await
.unwrap();
let make_request = || TraversalRequest {
roots: vec![a],
options: TraversalOptions::new(3).with_direction(Direction::Out),
include_roots: false,
include_properties: false,
execution_budget: Default::default(),
};
let paths1 = store.traverse(make_request()).await.unwrap();
let paths2 = store.traverse(make_request()).await.unwrap();
let count1 = paths1[0].nodes.len();
let count2 = paths2[0].nodes.len();
assert_eq!(
count1, count2,
"traverse result count must be stable across calls"
);
let d_nodes: Vec<_> = paths1[0].nodes.iter().filter(|n| n.node_id == d).collect();
assert_eq!(d_nodes.len(), 1, "D deduped to one entry");
assert!(
d_nodes[0].via_edge.is_some(),
"kept entry must have a via_edge"
);
assert_eq!(d_nodes[0].depth, 2, "D lives at depth 2");
}
#[tokio::test]
async fn test_traverse_multi_root_independent_chains() {
let store = setup_memory_store();
let a = Uuid::new_v4();
let b = Uuid::new_v4();
let c = Uuid::new_v4();
let d = Uuid::new_v4();
let e = Uuid::new_v4();
let f = Uuid::new_v4();
for (src, tgt) in [(a, b), (b, c), (d, e), (e, f)] {
store
.upsert_edge(make_edge(src, tgt, EdgeRelation::Extends, 1.0))
.await
.unwrap();
}
let request = TraversalRequest {
roots: vec![a, d],
options: TraversalOptions::new(2).with_direction(Direction::Out),
include_roots: true,
include_properties: false,
execution_budget: Default::default(),
};
let paths = store.traverse(request).await.unwrap();
assert_eq!(paths.len(), 2, "one GraphPath per root");
let path_a = paths
.iter()
.find(|p| p.root_id == a)
.expect("path for root A");
let path_d = paths
.iter()
.find(|p| p.root_id == d)
.expect("path for root D");
let ids_a: HashSet<Uuid> = path_a.nodes.iter().map(|n| n.node_id).collect();
assert!(ids_a.contains(&a), "root A in its own path");
assert!(ids_a.contains(&b), "depth-1 B in A's path");
assert!(ids_a.contains(&c), "depth-2 C in A's path");
assert!(!ids_a.contains(&d), "root D must not appear in A's path");
let ids_d: HashSet<Uuid> = path_d.nodes.iter().map(|n| n.node_id).collect();
assert!(ids_d.contains(&d), "root D in its own path");
assert!(ids_d.contains(&e), "depth-1 E in D's path");
assert!(ids_d.contains(&f), "depth-2 F in D's path");
assert!(!ids_d.contains(&a), "root A must not appear in D's path");
}
#[tokio::test]
async fn test_traverse_multi_root_shared_neighbor_appears_in_both() {
let store = setup_memory_store();
let a = Uuid::new_v4();
let b = Uuid::new_v4();
let c = Uuid::new_v4();
for (src, tgt) in [(a, c), (b, c)] {
store
.upsert_edge(make_edge(src, tgt, EdgeRelation::Extends, 1.0))
.await
.unwrap();
}
let request = TraversalRequest {
roots: vec![a, b],
options: TraversalOptions::new(1).with_direction(Direction::Out),
include_roots: false,
include_properties: false,
execution_budget: Default::default(),
};
let paths = store.traverse(request).await.unwrap();
assert_eq!(paths.len(), 2, "one GraphPath per root");
for path in &paths {
let node_ids: HashSet<Uuid> = path.nodes.iter().map(|n| n.node_id).collect();
assert!(
node_ids.contains(&c),
"shared node C must appear in each root's path; root={:?}",
path.root_id
);
}
}