refactor: modularize codebase and add 404 unit tests (#13)
CI / Format (push) Successful in 4s
CI / Clippy (push) Successful in 4m19s
CI / Detect Changes (push) Successful in 5s
CI / Tests (push) Successful in 5m15s
CI / Deploy Agent (push) Successful in 2s
CI / Deploy Dashboard (push) Successful in 2s
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Successful in 2s
CI / Security Audit (push) Successful in 1m44s

This commit was merged in pull request #13.
This commit is contained in:
2026-03-13 08:03:45 +00:00
parent acc5b86aa4
commit 3bb690e5bb
89 changed files with 11884 additions and 6046 deletions
+182
View File
@@ -172,3 +172,185 @@ impl GraphEngine {
ImpactAnalyzer::new(code_graph)
}
}
#[cfg(test)]
mod tests {
use super::*;
use compliance_core::models::graph::{CodeEdgeKind, CodeNode, CodeNodeKind};
fn make_node(qualified_name: &str) -> CodeNode {
CodeNode {
id: None,
repo_id: "test".to_string(),
graph_build_id: "build1".to_string(),
qualified_name: qualified_name.to_string(),
name: qualified_name
.split("::")
.last()
.unwrap_or(qualified_name)
.to_string(),
kind: CodeNodeKind::Function,
file_path: "src/main.rs".to_string(),
start_line: 1,
end_line: 10,
language: "rust".to_string(),
community_id: None,
is_entry_point: false,
graph_index: None,
}
}
fn build_test_node_map(names: &[&str]) -> HashMap<String, NodeIndex> {
let mut graph: DiGraph<String, String> = DiGraph::new();
let mut map = HashMap::new();
for name in names {
let idx = graph.add_node(name.to_string());
map.insert(name.to_string(), idx);
}
map
}
#[test]
fn test_resolve_edge_target_direct_match() {
let engine = GraphEngine::new(1000);
let node_map = build_test_node_map(&["src/main.rs::foo", "src/main.rs::bar"]);
let result = engine.resolve_edge_target("src/main.rs::foo", &node_map);
assert!(result.is_some());
assert_eq!(result.unwrap(), node_map["src/main.rs::foo"]);
}
#[test]
fn test_resolve_edge_target_short_name_match() {
let engine = GraphEngine::new(1000);
let node_map = build_test_node_map(&["src/main.rs::foo", "src/main.rs::bar"]);
let result = engine.resolve_edge_target("foo", &node_map);
assert!(result.is_some());
assert_eq!(result.unwrap(), node_map["src/main.rs::foo"]);
}
#[test]
fn test_resolve_edge_target_method_match() {
let engine = GraphEngine::new(1000);
let node_map = build_test_node_map(&["src/main.rs::MyStruct::do_thing"]);
let result = engine.resolve_edge_target("do_thing", &node_map);
assert!(result.is_some());
}
#[test]
fn test_resolve_edge_target_self_method() {
let engine = GraphEngine::new(1000);
let node_map = build_test_node_map(&["src/main.rs::MyStruct::process"]);
let result = engine.resolve_edge_target("self.process", &node_map);
assert!(result.is_some());
}
#[test]
fn test_resolve_edge_target_no_match() {
let engine = GraphEngine::new(1000);
let node_map = build_test_node_map(&["src/main.rs::foo"]);
let result = engine.resolve_edge_target("nonexistent", &node_map);
assert!(result.is_none());
}
#[test]
fn test_resolve_edge_target_empty_map() {
let engine = GraphEngine::new(1000);
let node_map = HashMap::new();
let result = engine.resolve_edge_target("anything", &node_map);
assert!(result.is_none());
}
#[test]
fn test_resolve_edge_target_dot_notation() {
let engine = GraphEngine::new(1000);
let node_map = build_test_node_map(&["src/app.js.handler"]);
let result = engine.resolve_edge_target("handler", &node_map);
assert!(result.is_some());
}
#[test]
fn test_build_petgraph_empty() {
let engine = GraphEngine::new(1000);
let output = ParseOutput::default();
let code_graph = engine.build_petgraph(output).unwrap();
assert_eq!(code_graph.nodes.len(), 0);
assert_eq!(code_graph.edges.len(), 0);
assert_eq!(code_graph.graph.node_count(), 0);
}
#[test]
fn test_build_petgraph_nodes_get_graph_index() {
let engine = GraphEngine::new(1000);
let mut output = ParseOutput::default();
output.nodes.push(make_node("src/main.rs::foo"));
output.nodes.push(make_node("src/main.rs::bar"));
let code_graph = engine.build_petgraph(output).unwrap();
assert_eq!(code_graph.nodes.len(), 2);
assert_eq!(code_graph.graph.node_count(), 2);
// All nodes should have a graph_index assigned
for node in &code_graph.nodes {
assert!(node.graph_index.is_some());
}
}
#[test]
fn test_build_petgraph_resolves_edges() {
let engine = GraphEngine::new(1000);
let mut output = ParseOutput::default();
output.nodes.push(make_node("src/main.rs::foo"));
output.nodes.push(make_node("src/main.rs::bar"));
output.edges.push(CodeEdge {
id: None,
repo_id: "test".to_string(),
graph_build_id: "build1".to_string(),
source: "src/main.rs::foo".to_string(),
target: "bar".to_string(), // short name, should resolve
kind: CodeEdgeKind::Calls,
file_path: "src/main.rs".to_string(),
line_number: Some(5),
});
let code_graph = engine.build_petgraph(output).unwrap();
assert_eq!(code_graph.edges.len(), 1);
assert_eq!(code_graph.graph.edge_count(), 1);
// The resolved edge target should be the full qualified name
assert_eq!(code_graph.edges[0].target, "src/main.rs::bar");
}
#[test]
fn test_build_petgraph_skips_unresolved_edges() {
let engine = GraphEngine::new(1000);
let mut output = ParseOutput::default();
output.nodes.push(make_node("src/main.rs::foo"));
output.edges.push(CodeEdge {
id: None,
repo_id: "test".to_string(),
graph_build_id: "build1".to_string(),
source: "src/main.rs::foo".to_string(),
target: "external_crate::something".to_string(),
kind: CodeEdgeKind::Calls,
file_path: "src/main.rs".to_string(),
line_number: Some(5),
});
let code_graph = engine.build_petgraph(output).unwrap();
assert_eq!(code_graph.edges.len(), 0);
assert_eq!(code_graph.graph.edge_count(), 0);
}
#[test]
fn test_code_graph_node_map_consistency() {
let engine = GraphEngine::new(1000);
let mut output = ParseOutput::default();
output.nodes.push(make_node("a::b"));
output.nodes.push(make_node("a::c"));
output.nodes.push(make_node("a::d"));
let code_graph = engine.build_petgraph(output).unwrap();
assert_eq!(code_graph.node_map.len(), 3);
assert!(code_graph.node_map.contains_key("a::b"));
assert!(code_graph.node_map.contains_key("a::c"));
assert!(code_graph.node_map.contains_key("a::d"));
}
}