#[test]
fn test_group_items_by_type() {
let items = vec![
AstItem::Function {
name: "f1".to_string(),
visibility: "pub".to_string(),
is_async: false,
line: 1,
},
AstItem::Struct {
name: "S1".to_string(),
visibility: "pub".to_string(),
fields_count: 2,
derives: vec![],
line: 2,
},
AstItem::Enum {
name: "E1".to_string(),
visibility: "pub".to_string(),
variants_count: 3,
line: 3,
},
AstItem::Trait {
name: "T1".to_string(),
visibility: "pub".to_string(),
line: 4,
},
AstItem::Impl {
type_name: "S1".to_string(),
trait_name: None,
line: 5,
},
AstItem::Module {
name: "m1".to_string(),
visibility: "pub".to_string(),
line: 6,
},
AstItem::Use {
path: "std::io".to_string(),
line: 7,
},
AstItem::Import {
module: "os".to_string(),
items: vec![],
alias: None,
line: 8,
},
];
let grouped = group_items_by_type(&items);
assert_eq!(grouped.functions.len(), 1);
assert_eq!(grouped.structs.len(), 1);
assert_eq!(grouped.enums.len(), 1);
assert_eq!(grouped.traits.len(), 1);
assert_eq!(grouped.impls.len(), 1);
assert_eq!(grouped.modules.len(), 1);
}
#[test]
fn test_format_item_groups() {
let items = vec![AstItem::Function {
name: "main".to_string(),
visibility: "pub".to_string(),
is_async: false,
line: 1,
}];
let grouped = group_items_by_type(&items);
let mut output = String::new();
format_item_groups(&mut output, &grouped);
assert!(output.contains("Functions"));
assert!(output.contains("main"));
}
#[test]
fn test_calculate_item_counts_empty() {
let files: Vec<FileContext> = vec![];
let mut summary = ProjectSummary {
total_files: 0,
total_functions: 0,
total_structs: 0,
total_enums: 0,
total_traits: 0,
total_impls: 0,
dependencies: vec![],
};
calculate_item_counts(&mut summary, &files);
assert_eq!(summary.total_functions, 0);
assert_eq!(summary.total_structs, 0);
}
#[test]
fn test_calculate_item_counts_with_items() {
let files = vec![FileContext {
path: "test.rs".to_string(),
language: "rust".to_string(),
items: vec![
AstItem::Function {
name: "f1".to_string(),
visibility: "pub".to_string(),
is_async: false,
line: 1,
},
AstItem::Function {
name: "f2".to_string(),
visibility: "pub".to_string(),
is_async: true,
line: 2,
},
AstItem::Struct {
name: "S1".to_string(),
visibility: "pub".to_string(),
fields_count: 2,
derives: vec![],
line: 3,
},
AstItem::Enum {
name: "E1".to_string(),
visibility: "pub".to_string(),
variants_count: 3,
line: 4,
},
AstItem::Trait {
name: "T1".to_string(),
visibility: "pub".to_string(),
line: 5,
},
AstItem::Impl {
type_name: "S1".to_string(),
trait_name: None,
line: 6,
},
],
complexity_metrics: None,
}];
let mut summary = ProjectSummary {
total_files: 1,
total_functions: 0,
total_structs: 0,
total_enums: 0,
total_traits: 0,
total_impls: 0,
dependencies: vec![],
};
calculate_item_counts(&mut summary, &files);
assert_eq!(summary.total_functions, 2);
assert_eq!(summary.total_structs, 1);
assert_eq!(summary.total_enums, 1);
assert_eq!(summary.total_traits, 1);
assert_eq!(summary.total_impls, 1);
}
#[test]
fn test_format_context_as_markdown_with_dependencies() {
let context = ProjectContext {
project_type: "rust".to_string(),
files: vec![],
graph: None,
summary: ProjectSummary {
total_files: 5,
total_functions: 20,
total_structs: 10,
total_enums: 5,
total_traits: 3,
total_impls: 15,
dependencies: vec!["serde".to_string(), "tokio".to_string()],
},
};
let markdown = format_context_as_markdown(&context);
assert!(markdown.contains("# Project Context"));
assert!(markdown.contains("Dependencies"));
assert!(markdown.contains("serde"));
assert!(markdown.contains("tokio"));
}
#[test]
fn test_format_context_as_markdown_no_dependencies() {
let context = ProjectContext {
project_type: "rust".to_string(),
files: vec![],
graph: None,
summary: ProjectSummary {
total_files: 1,
total_functions: 1,
total_structs: 0,
total_enums: 0,
total_traits: 0,
total_impls: 0,
dependencies: vec![],
},
};
let markdown = format_context_as_markdown(&context);
assert!(markdown.contains("# Project Context"));
}
#[tokio::test]
async fn test_analyze_rust_file_nonexistent() {
let result = analyze_rust_file(Path::new("/nonexistent/path/file.rs")).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_analyze_rust_file_invalid_syntax() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("invalid.rs");
fs::write(&file_path, "this is not valid rust {{{").unwrap();
let result = analyze_rust_file(&file_path).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_analyze_rust_file_empty() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("empty.rs");
fs::write(&file_path, "").unwrap();
let result = analyze_rust_file(&file_path).await;
assert!(result.is_ok());
let ctx = result.unwrap();
assert!(ctx.items.is_empty());
}
#[tokio::test]
async fn test_analyze_rust_file_with_all_item_types() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("all_items.rs");
let code = r#"
use std::io;
mod inner;
/// My struct.
pub struct MyStruct {
field: String,
}
#[derive(Debug)]
/// My enum.
pub enum MyEnum {
A,
B,
C,
}
/// Trait defining My trait behavior.
pub trait MyTrait {
fn method(&self);
}
impl MyStruct {
/// Create a new instance.
pub fn new() -> Self {
Self { field: String::new() }
}
}
impl MyTrait for MyStruct {
fn method(&self) {}
}
pub async fn async_func() {}
fn sync_func() {}
"#;
fs::write(&file_path, code).unwrap();
let result = analyze_rust_file(&file_path).await;
assert!(result.is_ok());
let ctx = result.unwrap();
assert!(!ctx.items.is_empty());
let has_use = ctx.items.iter().any(|i| matches!(i, AstItem::Use { .. }));
let has_mod = ctx
.items
.iter()
.any(|i| matches!(i, AstItem::Module { .. }));
let has_struct = ctx
.items
.iter()
.any(|i| matches!(i, AstItem::Struct { .. }));
let has_enum = ctx.items.iter().any(|i| matches!(i, AstItem::Enum { .. }));
let has_trait = ctx.items.iter().any(|i| matches!(i, AstItem::Trait { .. }));
let has_impl = ctx.items.iter().any(|i| matches!(i, AstItem::Impl { .. }));
let has_async_fn = ctx
.items
.iter()
.any(|i| matches!(i, AstItem::Function { is_async: true, .. }));
let has_sync_fn = ctx.items.iter().any(|i| {
matches!(
i,
AstItem::Function {
is_async: false,
..
}
)
});
assert!(has_use);
assert!(has_mod);
assert!(has_struct);
assert!(has_enum);
assert!(has_trait);
assert!(has_impl);
assert!(has_async_fn);
assert!(has_sync_fn);
}
#[tokio::test]
async fn test_analyze_project_empty_directory() {
let temp_dir = TempDir::new().unwrap();
let result = analyze_project(temp_dir.path(), "rust").await;
assert!(result.is_ok());
let ctx = result.unwrap();
assert!(ctx.files.is_empty());
assert_eq!(ctx.summary.total_files, 0);
}
#[tokio::test]
async fn test_analyze_project_with_rust_files() {
let temp_dir = TempDir::new().unwrap();
let src_dir = temp_dir.path().join("src");
fs::create_dir(&src_dir).unwrap();
fs::write(src_dir.join("lib.rs"), "pub fn hello() {}").unwrap();
fs::write(src_dir.join("main.rs"), "fn main() {}").unwrap();
let result = analyze_project(temp_dir.path(), "rust").await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_analyze_project_respects_gitignore() {
let temp_dir = TempDir::new().unwrap();
fs::write(temp_dir.path().join(".gitignore"), "target/").unwrap();
let target_dir = temp_dir.path().join("target");
fs::create_dir(&target_dir).unwrap();
fs::write(target_dir.join("ignored.rs"), "fn ignored() {}").unwrap();
let src_dir = temp_dir.path().join("src");
fs::create_dir(&src_dir).unwrap();
fs::write(src_dir.join("lib.rs"), "pub fn included() {}").unwrap();
let result = analyze_project(temp_dir.path(), "rust").await;
assert!(result.is_ok());
let ctx = result.unwrap();
let has_src_files = ctx.files.iter().any(|f| f.path.contains("src/"));
assert!(has_src_files || ctx.files.is_empty(), "Should have analyzed src files");
}