use infigraph_core::graph::{GraphQuery, GraphStore};
use infigraph_core::model::{
FileExtraction, Relation, RelationKind, Span, Symbol, SymbolKind,
};
fn span(file: &str, start: u32, end: u32) -> Span {
Span { file: file.to_string(), start_line: start, start_col: 0, end_line: end, end_col: 0 }
}
fn sym(id: &str, name: &str, kind: SymbolKind, file: &str, start: u32, end: u32) -> Symbol {
Symbol {
id: id.to_string(),
name: name.to_string(),
kind,
span: span(file, start, end),
signature_hash: format!("h_{id}"),
parent: None,
language: "python".to_string(),
visibility: Some("public".to_string()),
docstring: None,
complexity: 1,
parameters: None,
return_type: None,
}
}
fn rel(src: &str, tgt: &str, kind: RelationKind) -> Relation {
Relation {
source_id: src.to_string(),
target_id: tgt.to_string(),
kind,
span: None,
receiver: None,
}
}
struct TestGraph {
_dir: tempfile::TempDir,
store: GraphStore,
}
fn setup_graph() -> TestGraph {
let dir = tempfile::TempDir::new().unwrap();
let store = GraphStore::open(&dir.path().join("graph")).unwrap();
let extractions = vec![
FileExtraction {
file: "src/api/handler.py".to_string(),
language: "python".to_string(),
content_hash: "a".to_string(),
symbols: vec![
sym("src/api/handler.py::handle_request", "handle_request", SymbolKind::Function, "src/api/handler.py", 1, 20),
sym("src/api/handler.py::validate_input", "validate_input", SymbolKind::Function, "src/api/handler.py", 22, 35),
],
relations: vec![
rel("src/api/handler.py::handle_request", "src/api/handler.py::validate_input", RelationKind::Calls),
rel("src/api/handler.py::handle_request", "src/service/user.py::get_user", RelationKind::Calls),
],
statements: vec![],
},
FileExtraction {
file: "src/service/user.py".to_string(),
language: "python".to_string(),
content_hash: "b".to_string(),
symbols: vec![
sym("src/service/user.py::get_user", "get_user", SymbolKind::Function, "src/service/user.py", 1, 15),
sym("src/service/user.py::save_user", "save_user", SymbolKind::Function, "src/service/user.py", 17, 30),
],
relations: vec![
rel("src/service/user.py::get_user", "src/service/user.py::save_user", RelationKind::Calls),
rel("src/service/user.py", "src/api/handler.py", RelationKind::Imports),
],
statements: vec![],
},
FileExtraction {
file: "src/models/base.py".to_string(),
language: "python".to_string(),
content_hash: "c".to_string(),
symbols: vec![
sym("src/models/base.py::BaseModel", "BaseModel", SymbolKind::Class, "src/models/base.py", 1, 20),
sym("src/models/base.py::UserModel", "UserModel", SymbolKind::Class, "src/models/base.py", 22, 40),
],
relations: vec![
rel("src/models/base.py::UserModel", "src/models/base.py::BaseModel", RelationKind::Inherits),
],
statements: vec![],
},
];
{
let conn = store.connection().unwrap();
store.upsert_all_bulk(&conn, &extractions).unwrap();
}
TestGraph { _dir: dir, store }
}
#[test]
fn test_export_cypher() {
let tg = setup_graph();
let conn = tg.store.connection().unwrap();
let q = GraphQuery::new(&conn);
let mut buf = Vec::new();
infigraph_core::export::export_cypher(&q, &mut buf).unwrap();
let output = String::from_utf8(buf).unwrap();
assert!(output.contains("CREATE"), "should have CREATE statements");
assert!(output.contains("handle_request"), "should contain symbol names");
assert!(output.contains("CALLS"), "should contain CALLS edges");
}
#[test]
fn test_export_graphml() {
let tg = setup_graph();
let conn = tg.store.connection().unwrap();
let q = GraphQuery::new(&conn);
let mut buf = Vec::new();
infigraph_core::export::export_graphml(&q, &mut buf).unwrap();
let output = String::from_utf8(buf).unwrap();
assert!(output.contains("<graphml"), "should be valid GraphML");
assert!(output.contains("handle_request"));
assert!(output.contains("<edge"));
}
#[test]
fn test_export_json() {
let tg = setup_graph();
let conn = tg.store.connection().unwrap();
let q = GraphQuery::new(&conn);
let mut buf = Vec::new();
infigraph_core::export::export_json(&q, &mut buf).unwrap();
let output = String::from_utf8(buf).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
assert!(parsed.get("nodes").is_some(), "JSON should have nodes key");
assert!(parsed.get("edges").is_some(), "JSON should have edges key");
}
#[test]
fn test_export_empty_graph() {
let dir = tempfile::TempDir::new().unwrap();
let store = GraphStore::open(&dir.path().join("graph")).unwrap();
let conn = store.connection().unwrap();
let q = GraphQuery::new(&conn);
let mut buf = Vec::new();
infigraph_core::export::export_json(&q, &mut buf).unwrap();
let output = String::from_utf8(buf).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
assert!(parsed["nodes"].as_array().unwrap().is_empty());
}
#[test]
fn test_sequence_diagram_basic() {
let tg = setup_graph();
let conn = tg.store.connection().unwrap();
let q = GraphQuery::new(&conn);
let mermaid = infigraph_core::sequence::generate_sequence_mermaid(
&q,
"src/api/handler.py::handle_request",
3,
).unwrap();
assert!(mermaid.contains("sequenceDiagram"), "should start with sequenceDiagram");
assert!(mermaid.contains("handle_request") || mermaid.contains("handler"),
"should reference entry symbol");
}
#[test]
fn test_sequence_diagram_no_calls() {
let tg = setup_graph();
let conn = tg.store.connection().unwrap();
let q = GraphQuery::new(&conn);
let mermaid = infigraph_core::sequence::generate_sequence_mermaid(
&q,
"src/api/handler.py::validate_input",
3,
).unwrap();
assert!(mermaid.contains("sequenceDiagram"));
assert!(mermaid.contains("no outgoing calls"), "leaf symbol should show no-calls note");
}
#[test]
fn test_sequence_diagram_depth_limit() {
let tg = setup_graph();
let conn = tg.store.connection().unwrap();
let q = GraphQuery::new(&conn);
let shallow = infigraph_core::sequence::generate_sequence_mermaid(
&q,
"src/api/handler.py::handle_request",
1,
).unwrap();
let deep = infigraph_core::sequence::generate_sequence_mermaid(
&q,
"src/api/handler.py::handle_request",
5,
).unwrap();
assert!(deep.len() >= shallow.len());
}
#[test]
fn test_detect_bridges_rust_ffi() {
let dir = tempfile::TempDir::new().unwrap();
let src_dir = dir.path().join("src");
std::fs::create_dir(&src_dir).unwrap();
std::fs::write(
src_dir.join("ffi.rs"),
r#"
extern "C" {
fn sqlite3_open(filename: *const i8, db: *mut *mut u8) -> i32;
}
#[no_mangle]
pub extern "C" fn my_exported_fn() {}
"#,
).unwrap();
let result = infigraph_core::bridges::detect_bridges(dir.path()).unwrap();
assert!(result.ffi_count() >= 1, "should detect FFI bridge: {:?}", result.bridges);
}
#[test]
fn test_detect_bridges_grpc() {
let dir = tempfile::TempDir::new().unwrap();
std::fs::write(
dir.path().join("service.proto"),
"syntax = \"proto3\";\nservice UserService {\n rpc GetUser (GetUserRequest) returns (User);\n}\n",
).unwrap();
let result = infigraph_core::bridges::detect_bridges(dir.path()).unwrap();
assert!(result.grpc_count() >= 1, "should detect gRPC service: {:?}", result.bridges);
}
#[test]
fn test_detect_bridges_jni() {
let dir = tempfile::TempDir::new().unwrap();
std::fs::write(
dir.path().join("Native.java"),
"public class Native {\n public native void process();\n System.loadLibrary(\"mylib\");\n}\n",
).unwrap();
let result = infigraph_core::bridges::detect_bridges(dir.path()).unwrap();
assert!(result.jni_count() >= 1, "should detect JNI bridge: {:?}", result.bridges);
}
#[test]
fn test_detect_bridges_pinvoke() {
let dir = tempfile::TempDir::new().unwrap();
std::fs::write(
dir.path().join("Interop.cs"),
"[DllImport(\"kernel32.dll\")]\nstatic extern bool CloseHandle(IntPtr handle);\n",
).unwrap();
let result = infigraph_core::bridges::detect_bridges(dir.path()).unwrap();
assert!(result.pinvoke_count() >= 1, "should detect P/Invoke: {:?}", result.bridges);
}
#[test]
fn test_detect_bridges_empty_dir() {
let dir = tempfile::TempDir::new().unwrap();
let result = infigraph_core::bridges::detect_bridges(dir.path()).unwrap();
assert!(result.bridges.is_empty());
}
#[test]
fn test_bridge_scan_result_by_kind() {
let dir = tempfile::TempDir::new().unwrap();
let src_dir = dir.path().join("src");
std::fs::create_dir(&src_dir).unwrap();
std::fs::write(
src_dir.join("lib.rs"),
"extern \"C\" { fn ext_func(); }\n",
).unwrap();
std::fs::write(
dir.path().join("service.proto"),
"syntax = \"proto3\";\nservice Svc { rpc Do (Req) returns (Res); }\n",
).unwrap();
let result = infigraph_core::bridges::detect_bridges(dir.path()).unwrap();
let ffi = result.by_kind(&infigraph_core::model::BridgeKind::Ffi);
let grpc = result.by_kind(&infigraph_core::model::BridgeKind::Grpc);
assert!(!ffi.is_empty());
assert!(!grpc.is_empty());
}
#[test]
fn test_diff_format() {
use infigraph_core::diff::{format_diff, ChangeKind, SymbolChange, SymbolDiff};
let diff = SymbolDiff {
old_ref: "main".to_string(),
new_ref: "feature".to_string(),
changes: vec![
SymbolChange {
name: "new_func".to_string(),
kind: "Function".to_string(),
file: "api.py".to_string(),
change: ChangeKind::Added,
caller_count: 0,
},
SymbolChange {
name: "old_func".to_string(),
kind: "Function".to_string(),
file: "api.py".to_string(),
change: ChangeKind::Removed,
caller_count: 2,
},
SymbolChange {
name: "changed_func".to_string(),
kind: "Function".to_string(),
file: "api.py".to_string(),
change: ChangeKind::Modified,
caller_count: 1,
},
],
};
assert_eq!(diff.added().count(), 1);
assert_eq!(diff.removed().count(), 1);
assert_eq!(diff.modified().count(), 1);
let formatted = format_diff(&diff);
assert!(formatted.contains("new_func"), "should contain added symbol");
assert!(formatted.contains("old_func"), "should contain removed symbol");
assert!(formatted.contains("changed_func"), "should contain modified symbol");
assert!(formatted.contains("main"), "should reference old ref");
assert!(formatted.contains("feature"), "should reference new ref");
}
#[test]
fn test_viz_generate_html() {
let tg = setup_graph();
let conn = tg.store.connection().unwrap();
let q = GraphQuery::new(&conn);
let output_path = tg._dir.path().join("graph.html");
let result_path = infigraph_core::viz::generate_html(&q, &output_path).unwrap();
assert!(!result_path.is_empty());
let html = std::fs::read_to_string(&output_path).unwrap();
assert!(html.contains("<html") || html.contains("<!DOCTYPE"), "should produce HTML");
assert!(html.contains("handle_request") || html.contains("node"), "should contain graph data");
}
#[test]
fn test_viz_generate_symbol_html() {
let tg = setup_graph();
let conn = tg.store.connection().unwrap();
let q = GraphQuery::new(&conn);
let output_path = tg._dir.path().join("symbol.html");
let result_path = infigraph_core::viz::generate_symbol_html(
&q,
"src/api/handler.py::handle_request",
2,
&output_path,
).unwrap();
assert!(!result_path.is_empty());
let html = std::fs::read_to_string(&output_path).unwrap();
assert!(html.contains("<html") || html.contains("<!DOCTYPE"));
assert!(html.contains("handle_request"));
}