#![cfg_attr(coverage_nightly, coverage(off))]
#[cfg(feature = "python-ast")]
use crate::services::ast_python;
#[cfg(feature = "typescript-ast")]
use crate::services::ast_typescript;
#[cfg(test)]
#[allow(unused_imports)]
use crate::services::context::AstItem;
#[cfg(any(feature = "python-ast", feature = "typescript-ast"))]
use std::path::Path;
#[cfg(all(test, feature = "python-ast"))]
mod ast_python_tests {
use super::*;
#[tokio::test]
#[ignore = "e2e test - requires binary build"]
async fn test_analyze_python_file_comprehensive() {
let fixture_path = Path::new("src/tests/fixtures/sample.py");
let result = ast_python::analyze_python_file(fixture_path).await;
assert!(
result.is_ok(),
"Failed to analyze Python file: {:?}",
result.err()
);
let context = result.unwrap();
assert_eq!(context.language, "python");
assert!(context.path.ends_with("sample.py"));
let functions: Vec<&AstItem> = context
.items
.iter()
.filter(|item| matches!(item, AstItem::Function { .. }))
.collect();
let classes: Vec<&AstItem> = context
.items
.iter()
.filter(|item| matches!(item, AstItem::Struct { .. }))
.collect();
let imports: Vec<&AstItem> = context
.items
.iter()
.filter(|item| matches!(item, AstItem::Import { .. }))
.collect();
assert!(
functions.len() >= 6,
"Expected at least 6 functions, found {}",
functions.len()
);
assert_eq!(
classes.len(),
2,
"Expected 2 classes, found {}",
classes.len()
);
assert!(
imports.len() >= 4,
"Expected at least 4 imports, found {}",
imports.len()
);
let function_names: Vec<String> = functions
.iter()
.filter_map(|item| {
if let AstItem::Function { name, .. } = item {
Some(name.clone())
} else {
None
}
})
.collect();
assert!(function_names.contains(&"process_data".to_string()));
assert!(function_names.contains(&"fetch_remote_data".to_string()));
assert!(function_names.contains(&"_private_helper".to_string()));
let async_functions: Vec<&&AstItem> = functions
.iter()
.filter(|item| {
if let AstItem::Function { is_async, .. } = item {
*is_async
} else {
false
}
})
.collect();
assert!(
async_functions.len() >= 3,
"Expected at least 3 async functions"
);
let private_functions: Vec<&&AstItem> = functions
.iter()
.filter(|item| {
if let AstItem::Function { visibility, .. } = item {
visibility == "private"
} else {
false
}
})
.collect();
assert!(
private_functions.len() >= 2,
"Expected at least 2 private functions"
);
let class_names: Vec<String> = classes
.iter()
.filter_map(|item| {
if let AstItem::Struct { name, .. } = item {
Some(name.clone())
} else {
None
}
})
.collect();
assert!(class_names.contains(&"User".to_string()));
assert!(class_names.contains(&"UserService".to_string()));
}
#[tokio::test]
#[ignore = "e2e test - requires binary build"]
async fn test_python_class_field_count() {
let fixture_path = Path::new("src/tests/fixtures/sample.py");
let result = ast_python::analyze_python_file(fixture_path).await;
assert!(result.is_ok());
let context = result.unwrap();
let user_service = context.items.iter().find(|item| {
if let AstItem::Struct { name, .. } = item {
name == "UserService"
} else {
false
}
});
assert!(user_service.is_some());
if let AstItem::Struct { fields_count, .. } = user_service.unwrap() {
assert!(
fields_count == fields_count,
"Field count is {fields_count}"
);
}
}
#[tokio::test]
#[ignore = "e2e test - requires binary build"]
async fn test_python_import_parsing() {
let fixture_path = Path::new("src/tests/fixtures/sample.py");
let result = ast_python::analyze_python_file(fixture_path).await;
assert!(result.is_ok());
let context = result.unwrap();
let imports: Vec<String> = context
.items
.iter()
.filter_map(|item| {
if let AstItem::Import { module, .. } = item {
Some(module.clone())
} else {
None
}
})
.collect();
assert!(imports.contains(&"os".to_string()));
assert!(imports.contains(&"sys".to_string()));
assert!(imports.iter().any(|p| p.contains("typing")));
assert!(imports.iter().any(|p| p.contains("dataclasses")));
}
#[tokio::test]
async fn test_python_file_not_found() {
let non_existent_path = Path::new("src/tests/fixtures/non_existent.py");
let result = ast_python::analyze_python_file(non_existent_path).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_python_invalid_syntax() {
use tokio::io::AsyncWriteExt;
let temp_dir = tempfile::tempdir().unwrap();
let invalid_file_path = temp_dir.path().join("invalid.py");
let mut file = tokio::fs::File::create(&invalid_file_path).await.unwrap();
file.write_all(b"def invalid_function(\n # Missing closing parenthesis and colon")
.await
.unwrap();
file.flush().await.unwrap();
let result = ast_python::analyze_python_file(&invalid_file_path).await;
assert!(result.is_err());
}
}
#[cfg(all(test, feature = "typescript-ast"))]
mod ast_typescript_tests {
use super::*;
use serial_test::serial;
#[tokio::test]
#[ignore] #[serial]
async fn test_analyze_typescript_file_comprehensive() {
let fixture_path = Path::new("src/tests/fixtures/sample.ts");
let result = ast_typescript::analyze_typescript_file(fixture_path).await;
assert!(
result.is_ok(),
"Failed to analyze TypeScript file: {:?}",
result.err()
);
let context = result.unwrap();
assert_eq!(context.language, "typescript");
assert!(context.path.ends_with("sample.ts"));
eprintln!("TypeScript items count: {}", context.items.len());
}
#[tokio::test]
#[ignore] #[serial]
async fn test_analyze_javascript_file() {
let fixture_path = Path::new("src/tests/fixtures/sample.js");
let result = ast_typescript::analyze_javascript_file(fixture_path).await;
assert!(
result.is_ok(),
"Failed to analyze JavaScript file: {:?}",
result.err()
);
let context = result.unwrap();
assert_eq!(context.language, "javascript");
assert!(context.path.ends_with("sample.js"));
eprintln!("JavaScript items count: {}", context.items.len());
}
#[tokio::test]
#[ignore] #[serial]
async fn test_typescript_class_field_count() {
let fixture_path = Path::new("src/tests/fixtures/sample.ts");
let result = ast_typescript::analyze_typescript_file(fixture_path).await;
assert!(result.is_ok());
let context = result.unwrap();
eprintln!("TypeScript items count: {}", context.items.len());
}
#[tokio::test]
#[ignore = "e2e test - requires binary build"]
async fn test_tsx_file_detection() {
use tokio::io::AsyncWriteExt;
let temp_dir = tempfile::tempdir().unwrap();
let tsx_file_path = temp_dir.path().join("component.tsx");
let mut file = tokio::fs::File::create(&tsx_file_path).await.unwrap();
file.write_all(b"export const Button = () => <button>Click me</button>;")
.await
.unwrap();
file.flush().await.unwrap();
let result = ast_typescript::analyze_typescript_file(&tsx_file_path).await;
assert!(result.is_ok());
let context = result.unwrap();
assert_eq!(context.language, "tsx");
}
#[tokio::test]
#[ignore = "e2e test - requires binary build"]
async fn test_jsx_file_detection() {
use tokio::io::AsyncWriteExt;
let temp_dir = tempfile::tempdir().unwrap();
let jsx_file_path = temp_dir.path().join("component.jsx");
let mut file = tokio::fs::File::create(&jsx_file_path).await.unwrap();
file.write_all(b"const Button = () => { return 'button'; };")
.await
.unwrap();
file.flush().await.unwrap();
let result = ast_typescript::analyze_javascript_file(&jsx_file_path).await;
assert!(
result.is_ok(),
"Failed to parse JSX file: {:?}",
result.err()
);
let context = result.unwrap();
assert_eq!(context.language, "jsx");
}
#[tokio::test]
async fn test_typescript_file_not_found() {
let non_existent_path = Path::new("src/tests/fixtures/non_existent.ts");
let result = ast_typescript::analyze_typescript_file(non_existent_path).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_typescript_invalid_syntax() {
use tokio::io::AsyncWriteExt;
let temp_dir = tempfile::tempdir().unwrap();
let invalid_file_path = temp_dir.path().join("invalid.ts");
let mut file = tokio::fs::File::create(&invalid_file_path).await.unwrap();
file.write_all(b"function invalid(] { // Invalid syntax")
.await
.unwrap();
file.flush().await.unwrap();
let result = ast_typescript::analyze_typescript_file(&invalid_file_path).await;
assert!(result.is_err());
}
}
#[cfg(all(test, feature = "python-ast", feature = "typescript-ast"))]
mod ast_integration_tests {
use super::*;
#[tokio::test]
#[ignore = "e2e test - requires binary build"]
async fn test_mixed_language_project_context() {
let py_path = Path::new("src/tests/fixtures/sample.py");
let ts_path = Path::new("src/tests/fixtures/sample.ts");
let js_path = Path::new("src/tests/fixtures/sample.js");
let py_result = ast_python::analyze_python_file(py_path).await;
let ts_result = ast_typescript::analyze_typescript_file(ts_path).await;
let js_result = ast_typescript::analyze_javascript_file(js_path).await;
assert!(py_result.is_ok());
assert!(ts_result.is_ok());
assert!(js_result.is_ok());
let py_context = py_result.unwrap();
let ts_context = ts_result.unwrap();
let js_context = js_result.unwrap();
assert_eq!(py_context.language, "python");
assert_eq!(ts_context.language, "typescript");
assert_eq!(js_context.language, "javascript");
let total_items = py_context.items.len() + ts_context.items.len() + js_context.items.len();
eprintln!("Python items: {}", py_context.items.len());
eprintln!("TypeScript items: {}", ts_context.items.len());
eprintln!("JavaScript items: {}", js_context.items.len());
eprintln!("Total items: {}", total_items);
assert!(
py_context.items.len() > 10,
"Expected more than 10 Python AST items, but got {}",
py_context.items.len()
);
}
}