refactor: modularize codebase and add 404 unit tests (#13)
CI / Format (push) Successful in 4s
CI / Clippy (push) Successful in 4m19s
CI / Security Audit (push) Successful in 1m44s
CI / Tests (push) Successful in 5m15s
CI / Detect Changes (push) Successful in 5s
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 / Format (push) Successful in 4s
CI / Clippy (push) Successful in 4m19s
CI / Security Audit (push) Successful in 1m44s
CI / Tests (push) Successful in 5m15s
CI / Detect Changes (push) Successful in 5s
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
This commit was merged in pull request #13.
This commit is contained in:
@@ -184,3 +184,115 @@ impl Default for ParserRegistry {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn test_supports_rust_extension() {
|
||||
let registry = ParserRegistry::new();
|
||||
assert!(registry.supports_extension("rs"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_supports_python_extension() {
|
||||
let registry = ParserRegistry::new();
|
||||
assert!(registry.supports_extension("py"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_supports_javascript_extension() {
|
||||
let registry = ParserRegistry::new();
|
||||
assert!(registry.supports_extension("js"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_supports_typescript_extension() {
|
||||
let registry = ParserRegistry::new();
|
||||
assert!(registry.supports_extension("ts"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_does_not_support_unknown_extension() {
|
||||
let registry = ParserRegistry::new();
|
||||
assert!(!registry.supports_extension("go"));
|
||||
assert!(!registry.supports_extension("java"));
|
||||
assert!(!registry.supports_extension("cpp"));
|
||||
assert!(!registry.supports_extension(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_supported_extensions_includes_all() {
|
||||
let registry = ParserRegistry::new();
|
||||
let exts = registry.supported_extensions();
|
||||
assert!(exts.contains(&"rs"));
|
||||
assert!(exts.contains(&"py"));
|
||||
assert!(exts.contains(&"js"));
|
||||
assert!(exts.contains(&"ts"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_supported_extensions_count() {
|
||||
let registry = ParserRegistry::new();
|
||||
let exts = registry.supported_extensions();
|
||||
// At least 4 extensions (rs, py, js, ts); could be more if tsx, jsx etc.
|
||||
assert!(exts.len() >= 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_file_returns_none_for_unsupported() {
|
||||
let registry = ParserRegistry::new();
|
||||
let path = PathBuf::from("test.go");
|
||||
let result = registry.parse_file(&path, "package main", "repo1", "build1");
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_file_rust_source() {
|
||||
let registry = ParserRegistry::new();
|
||||
let path = PathBuf::from("src/main.rs");
|
||||
let source = "fn main() {\n println!(\"hello\");\n}\n";
|
||||
let result = registry.parse_file(&path, source, "repo1", "build1");
|
||||
assert!(result.is_ok());
|
||||
let output = result.unwrap();
|
||||
assert!(output.is_some());
|
||||
let output = output.unwrap();
|
||||
// Should have at least the file node and the main function node
|
||||
assert!(output.nodes.len() >= 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_file_python_source() {
|
||||
let registry = ParserRegistry::new();
|
||||
let path = PathBuf::from("app.py");
|
||||
let source = "def hello():\n print('hi')\n";
|
||||
let result = registry.parse_file(&path, source, "repo1", "build1");
|
||||
assert!(result.is_ok());
|
||||
let output = result.unwrap();
|
||||
assert!(output.is_some());
|
||||
let output = output.unwrap();
|
||||
assert!(!output.nodes.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_file_empty_source() {
|
||||
let registry = ParserRegistry::new();
|
||||
let path = PathBuf::from("empty.rs");
|
||||
let result = registry.parse_file(&path, "", "repo1", "build1");
|
||||
assert!(result.is_ok());
|
||||
let output = result.unwrap();
|
||||
assert!(output.is_some());
|
||||
// At minimum the file node
|
||||
let output = output.unwrap();
|
||||
assert!(!output.nodes.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_trait() {
|
||||
let registry = ParserRegistry::default();
|
||||
assert!(registry.supports_extension("rs"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,6 +363,214 @@ impl RustParser {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use compliance_core::traits::graph_builder::LanguageParser;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn parse_rust(source: &str) -> ParseOutput {
|
||||
let parser = RustParser::new();
|
||||
parser
|
||||
.parse_file(&PathBuf::from("test.rs"), source, "repo1", "build1")
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_use_path_simple() {
|
||||
let parser = RustParser::new();
|
||||
assert_eq!(
|
||||
parser.extract_use_path("use std::collections::HashMap;"),
|
||||
Some("std::collections::HashMap".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_use_path_nested() {
|
||||
let parser = RustParser::new();
|
||||
assert_eq!(
|
||||
parser.extract_use_path("use crate::models::graph::CodeNode;"),
|
||||
Some("crate::models::graph::CodeNode".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_use_path_no_prefix() {
|
||||
let parser = RustParser::new();
|
||||
assert_eq!(parser.extract_use_path("let x = 5;"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_use_path_empty() {
|
||||
let parser = RustParser::new();
|
||||
assert_eq!(parser.extract_use_path(""), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_function() {
|
||||
let output = parse_rust("fn hello() {\n let x = 1;\n}\n");
|
||||
let fn_nodes: Vec<_> = output
|
||||
.nodes
|
||||
.iter()
|
||||
.filter(|n| n.kind == CodeNodeKind::Function)
|
||||
.collect();
|
||||
assert_eq!(fn_nodes.len(), 1);
|
||||
assert_eq!(fn_nodes[0].name, "hello");
|
||||
assert!(fn_nodes[0].qualified_name.contains("hello"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_struct() {
|
||||
let output = parse_rust("struct Foo {\n x: i32,\n}\n");
|
||||
let struct_nodes: Vec<_> = output
|
||||
.nodes
|
||||
.iter()
|
||||
.filter(|n| n.kind == CodeNodeKind::Struct)
|
||||
.collect();
|
||||
assert_eq!(struct_nodes.len(), 1);
|
||||
assert_eq!(struct_nodes[0].name, "Foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_enum() {
|
||||
let output = parse_rust("enum Color {\n Red,\n Blue,\n}\n");
|
||||
let enum_nodes: Vec<_> = output
|
||||
.nodes
|
||||
.iter()
|
||||
.filter(|n| n.kind == CodeNodeKind::Enum)
|
||||
.collect();
|
||||
assert_eq!(enum_nodes.len(), 1);
|
||||
assert_eq!(enum_nodes[0].name, "Color");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_trait() {
|
||||
let output = parse_rust("trait Drawable {\n fn draw(&self);\n}\n");
|
||||
let trait_nodes: Vec<_> = output
|
||||
.nodes
|
||||
.iter()
|
||||
.filter(|n| n.kind == CodeNodeKind::Trait)
|
||||
.collect();
|
||||
assert_eq!(trait_nodes.len(), 1);
|
||||
assert_eq!(trait_nodes[0].name, "Drawable");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_file_node_always_created() {
|
||||
let output = parse_rust("");
|
||||
let file_nodes: Vec<_> = output
|
||||
.nodes
|
||||
.iter()
|
||||
.filter(|n| n.kind == CodeNodeKind::File)
|
||||
.collect();
|
||||
assert_eq!(file_nodes.len(), 1);
|
||||
assert_eq!(file_nodes[0].language, "rust");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_multiple_functions() {
|
||||
let source = "fn foo() {}\nfn bar() {}\nfn baz() {}\n";
|
||||
let output = parse_rust(source);
|
||||
let fn_nodes: Vec<_> = output
|
||||
.nodes
|
||||
.iter()
|
||||
.filter(|n| n.kind == CodeNodeKind::Function)
|
||||
.collect();
|
||||
assert_eq!(fn_nodes.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_main_is_entry_point() {
|
||||
let output = parse_rust("fn main() {\n println!(\"hi\");\n}\n");
|
||||
let main_node = output.nodes.iter().find(|n| n.name == "main").unwrap();
|
||||
assert!(main_node.is_entry_point);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_pub_fn_is_entry_point() {
|
||||
let output = parse_rust("pub fn handler() {}\n");
|
||||
let node = output.nodes.iter().find(|n| n.name == "handler").unwrap();
|
||||
assert!(node.is_entry_point);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_private_fn_is_not_entry_point() {
|
||||
let output = parse_rust("fn helper() {}\n");
|
||||
let node = output.nodes.iter().find(|n| n.name == "helper").unwrap();
|
||||
assert!(!node.is_entry_point);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_function_calls_create_edges() {
|
||||
let source = "fn caller() {\n callee();\n}\nfn callee() {}\n";
|
||||
let output = parse_rust(source);
|
||||
let call_edges: Vec<_> = output
|
||||
.edges
|
||||
.iter()
|
||||
.filter(|e| e.kind == CodeEdgeKind::Calls)
|
||||
.collect();
|
||||
assert!(!call_edges.is_empty());
|
||||
assert!(call_edges.iter().any(|e| e.target.contains("callee")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_use_declaration_creates_import_edge() {
|
||||
let source = "use std::collections::HashMap;\nfn foo() {}\n";
|
||||
let output = parse_rust(source);
|
||||
let import_edges: Vec<_> = output
|
||||
.edges
|
||||
.iter()
|
||||
.filter(|e| e.kind == CodeEdgeKind::Imports)
|
||||
.collect();
|
||||
assert!(!import_edges.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_impl_methods() {
|
||||
let source = "struct Foo {}\nimpl Foo {\n fn do_thing(&self) {}\n}\n";
|
||||
let output = parse_rust(source);
|
||||
let fn_nodes: Vec<_> = output
|
||||
.nodes
|
||||
.iter()
|
||||
.filter(|n| n.kind == CodeNodeKind::Function)
|
||||
.collect();
|
||||
assert_eq!(fn_nodes.len(), 1);
|
||||
assert_eq!(fn_nodes[0].name, "do_thing");
|
||||
// Method should be qualified under the impl type
|
||||
assert!(fn_nodes[0].qualified_name.contains("Foo"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_mod_item() {
|
||||
let source = "mod inner {\n fn nested() {}\n}\n";
|
||||
let output = parse_rust(source);
|
||||
let mod_nodes: Vec<_> = output
|
||||
.nodes
|
||||
.iter()
|
||||
.filter(|n| n.kind == CodeNodeKind::Module)
|
||||
.collect();
|
||||
assert_eq!(mod_nodes.len(), 1);
|
||||
assert_eq!(mod_nodes[0].name, "inner");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_line_numbers() {
|
||||
let source = "fn first() {}\n\n\nfn second() {}\n";
|
||||
let output = parse_rust(source);
|
||||
let first = output.nodes.iter().find(|n| n.name == "first").unwrap();
|
||||
let second = output.nodes.iter().find(|n| n.name == "second").unwrap();
|
||||
assert_eq!(first.start_line, 1);
|
||||
assert!(second.start_line > first.start_line);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_language_and_extensions() {
|
||||
let parser = RustParser::new();
|
||||
assert_eq!(parser.language(), "rust");
|
||||
assert_eq!(parser.extensions(), &["rs"]);
|
||||
}
|
||||
}
|
||||
|
||||
impl LanguageParser for RustParser {
|
||||
fn language(&self) -> &str {
|
||||
"rust"
|
||||
|
||||
Reference in New Issue
Block a user