use rust_relations_explorer::graph::{
FileNode, Item, ItemId, ItemType, KnowledgeGraph, Location, Relationship, RelationshipType,
Visibility,
};
use rust_relations_explorer::query::{
CentralityMetric, HubsQuery, ModuleCentralityQuery, Query, ShortestPathQuery,
};
use std::path::{Path, PathBuf};
use std::sync::Arc;
fn make_fn(path: &Path, name: &str) -> Item {
Item {
id: ItemId(format!("fn:{}:{}", name, path.display())),
item_type: ItemType::Function { is_async: false, is_const: false },
name: Arc::from(name),
visibility: Visibility::Public,
location: Location { file: path.to_path_buf(), line_start: 1, line_end: 1 },
attributes: vec![],
}
}
fn graph_chain() -> KnowledgeGraph {
let mut g = KnowledgeGraph::default();
let a = PathBuf::from("src/a.rs");
let b = PathBuf::from("src/b.rs");
let c = PathBuf::from("src/m1/c.rs");
let d = PathBuf::from("src/m2/d.rs");
let ia = make_fn(&a, "fa");
let ib = make_fn(&b, "fb");
let ic = make_fn(&c, "fc");
let id = make_fn(&d, "fd");
g.files.insert(
a.clone(),
FileNode {
path: a.clone(),
items: vec![ia.clone()],
imports: vec![],
metrics: Default::default(),
},
);
g.files.insert(
b.clone(),
FileNode {
path: b.clone(),
items: vec![ib.clone()],
imports: vec![],
metrics: Default::default(),
},
);
g.files.insert(
c.clone(),
FileNode {
path: c.clone(),
items: vec![ic.clone()],
imports: vec![],
metrics: Default::default(),
},
);
g.files.insert(
d.clone(),
FileNode {
path: d.clone(),
items: vec![id.clone()],
imports: vec![],
metrics: Default::default(),
},
);
g.relationships.push(Relationship {
from_item: ia.id.clone(),
to_item: ib.id.clone(),
relationship_type: RelationshipType::Calls { call_type: "test".into() },
strength: 1.0,
context: String::new(),
});
g.relationships.push(Relationship {
from_item: ib.id.clone(),
to_item: ic.id.clone(),
relationship_type: RelationshipType::Calls { call_type: "test".into() },
strength: 1.0,
context: String::new(),
});
g
}
#[test]
fn shortest_path_found_and_absent() {
let g = graph_chain();
let p = ShortestPathQuery::new("src/a.rs", "src/m1/c.rs").run(&g);
assert_eq!(p.first().unwrap().display().to_string(), "src/a.rs");
assert_eq!(p.last().unwrap().display().to_string(), "src/m1/c.rs");
let none = ShortestPathQuery::new("src/m1/c.rs", "src/a.rs").run(&g);
assert!(none.is_empty());
}
#[test]
fn hubs_query_metrics_and_sorting() {
let g = graph_chain();
let total = HubsQuery::new(CentralityMetric::Total, 10).run(&g);
assert!(total.iter().any(|(p, i, o)| p.ends_with("src/b.rs") && *i == 1 && *o == 1));
let ins = HubsQuery::new(CentralityMetric::In, 10).run(&g);
assert!(ins.iter().any(|(p, i, _)| p.ends_with("src/m1/c.rs") && *i == 1));
let outs = HubsQuery::new(CentralityMetric::Out, 10).run(&g);
assert!(outs.iter().any(|(p, _, o)| p.ends_with("src/a.rs") && *o == 1));
}
#[test]
fn module_centrality_inter_module_edges() {
let g = graph_chain();
let rows = ModuleCentralityQuery::new(CentralityMetric::Total, 10).run(&g);
assert!(rows.len() >= 2);
let mods: Vec<String> = rows.iter().map(|(p, _, _)| p.display().to_string()).collect();
assert!(mods.iter().any(|m| m == "src" || m.ends_with("/src")));
}