use nodedb::bridge::envelope::{ErrorCode, PhysicalPlan};
use nodedb::bridge::physical_plan::GraphOp;
use nodedb::engine::graph::edge_store::Direction;
use crate::helpers::*;
#[test]
fn graph_traversal_bounded_under_adversarial_queries() {
let (mut core, mut tx, mut rx) = make_core();
for i in 0..200u32 {
send_ok(
&mut core,
&mut tx,
&mut rx,
PhysicalPlan::Graph(GraphOp::EdgePut {
src_id: "hub".into(),
label: "LINKS".into(),
dst_id: format!("n{i}"),
properties: vec![],
}),
);
}
for i in 0..50u32 {
send_ok(
&mut core,
&mut tx,
&mut rx,
PhysicalPlan::Graph(GraphOp::EdgePut {
src_id: format!("c{i}"),
label: "NEXT".into(),
dst_id: format!("c{}", i + 1),
properties: vec![],
}),
);
}
let hop1 = send_ok(
&mut core,
&mut tx,
&mut rx,
PhysicalPlan::Graph(GraphOp::Hop {
start_nodes: vec!["hub".into()],
edge_label: None,
direction: Direction::Out,
depth: 1,
options: Default::default(),
rls_filters: Vec::new(),
}),
);
let hop1_nodes: Vec<String> = serde_json::from_value(payload_value(&hop1)).unwrap();
assert!(
hop1_nodes.len() <= 201,
"depth=1 should cap at hub+neighbors: got {}",
hop1_nodes.len()
);
assert!(hop1_nodes.contains(&"hub".to_string()));
let hop0 = send_ok(
&mut core,
&mut tx,
&mut rx,
PhysicalPlan::Graph(GraphOp::Hop {
start_nodes: vec!["hub".into()],
edge_label: None,
direction: Direction::Out,
depth: 0,
options: Default::default(),
rls_filters: Vec::new(),
}),
);
let hop0_nodes: Vec<String> = serde_json::from_value(payload_value(&hop0)).unwrap();
assert_eq!(hop0_nodes.len(), 1, "depth=0 should return only start node");
let chain = send_ok(
&mut core,
&mut tx,
&mut rx,
PhysicalPlan::Graph(GraphOp::Hop {
start_nodes: vec!["c0".into()],
edge_label: Some("NEXT".into()),
direction: Direction::Out,
depth: 5,
options: Default::default(),
rls_filters: Vec::new(),
}),
);
let chain_nodes: Vec<String> = serde_json::from_value(payload_value(&chain)).unwrap();
assert!(
chain_nodes.len() <= 6,
"depth=5 from c0 should reach at most 6 nodes: got {}",
chain_nodes.len()
);
assert!(chain_nodes.contains(&"c0".to_string()));
assert!(chain_nodes.contains(&"c5".to_string()));
let path_short = send_raw(
&mut core,
&mut tx,
&mut rx,
PhysicalPlan::Graph(GraphOp::Path {
src: "c0".into(),
dst: "c50".into(),
edge_label: Some("NEXT".into()),
max_depth: 3,
options: Default::default(),
rls_filters: Vec::new(),
}),
);
assert_eq!(
path_short.error_code,
Some(ErrorCode::NotFound),
"50-hop path should not be found with max_depth=3"
);
let subgraph = send_ok(
&mut core,
&mut tx,
&mut rx,
PhysicalPlan::Graph(GraphOp::Subgraph {
start_nodes: vec!["c0".into()],
edge_label: Some("NEXT".into()),
depth: 3,
options: Default::default(),
rls_filters: Vec::new(),
}),
);
let edges: Vec<serde_json::Value> = serde_json::from_value(payload_value(&subgraph)).unwrap();
assert_eq!(
edges.len(),
3,
"depth=3 subgraph should have 3 edges: got {}",
edges.len()
);
}