rename: archdoc → wtismycode (WTIsMyCode)
This commit is contained in:
100
wtismycode-core/tests/caching.rs
Normal file
100
wtismycode-core/tests/caching.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
//! Caching tests for WTIsMyCode
|
||||
//!
|
||||
//! These tests verify that the caching functionality works correctly.
|
||||
|
||||
use std::path::Path;
|
||||
use std::fs;
|
||||
use tempfile::TempDir;
|
||||
use wtismycode_core::{Config, python_analyzer::PythonAnalyzer};
|
||||
|
||||
#[test]
|
||||
fn test_cache_store_and_retrieve() {
|
||||
let config = Config::default();
|
||||
let analyzer = PythonAnalyzer::new(config);
|
||||
|
||||
// Create a temporary Python file
|
||||
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let temp_file = temp_dir.path().join("test.py");
|
||||
let python_code = r#"
|
||||
def hello():
|
||||
return "Hello, World!"
|
||||
|
||||
class Calculator:
|
||||
def add(self, a, b):
|
||||
return a + b
|
||||
"#;
|
||||
fs::write(&temp_file, python_code).expect("Failed to write test file");
|
||||
|
||||
// Parse the module for the first time
|
||||
let parsed_module1 = analyzer.parse_module(&temp_file)
|
||||
.expect("Failed to parse module first time");
|
||||
|
||||
// Parse the module again - should come from cache
|
||||
let parsed_module2 = analyzer.parse_module(&temp_file)
|
||||
.expect("Failed to parse module second time");
|
||||
|
||||
// Both parses should return the same data
|
||||
assert_eq!(parsed_module1.path, parsed_module2.path);
|
||||
assert_eq!(parsed_module1.module_path, parsed_module2.module_path);
|
||||
assert_eq!(parsed_module1.imports.len(), parsed_module2.imports.len());
|
||||
assert_eq!(parsed_module1.symbols.len(), parsed_module2.symbols.len());
|
||||
assert_eq!(parsed_module1.calls.len(), parsed_module2.calls.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cache_invalidation_on_file_change() {
|
||||
let config = Config::default();
|
||||
let analyzer = PythonAnalyzer::new(config);
|
||||
|
||||
// Create a temporary Python file
|
||||
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let temp_file = temp_dir.path().join("test.py");
|
||||
let python_code1 = r#"
|
||||
def hello():
|
||||
return "Hello, World!"
|
||||
"#;
|
||||
fs::write(&temp_file, python_code1).expect("Failed to write test file");
|
||||
|
||||
// Parse the module for the first time
|
||||
let parsed_module1 = analyzer.parse_module(&temp_file)
|
||||
.expect("Failed to parse module first time");
|
||||
|
||||
// Modify the file
|
||||
let python_code2 = r#"
|
||||
def hello():
|
||||
return "Hello, World!"
|
||||
|
||||
def goodbye():
|
||||
return "Goodbye, World!"
|
||||
"#;
|
||||
fs::write(&temp_file, python_code2).expect("Failed to write test file");
|
||||
|
||||
// Parse the module again - should NOT come from cache due to file change
|
||||
let parsed_module2 = analyzer.parse_module(&temp_file)
|
||||
.expect("Failed to parse module second time");
|
||||
|
||||
// The second parse should have more symbols
|
||||
assert!(parsed_module2.symbols.len() >= parsed_module1.symbols.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cache_disabled() {
|
||||
let mut config = Config::default();
|
||||
config.caching.enabled = false;
|
||||
let analyzer = PythonAnalyzer::new(config);
|
||||
|
||||
// Create a temporary Python file
|
||||
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let temp_file = temp_dir.path().join("test.py");
|
||||
let python_code = r#"
|
||||
def hello():
|
||||
return "Hello, World!"
|
||||
"#;
|
||||
fs::write(&temp_file, python_code).expect("Failed to write test file");
|
||||
|
||||
// Parse the module - should work even with caching disabled
|
||||
let parsed_module = analyzer.parse_module(&temp_file)
|
||||
.expect("Failed to parse module with caching disabled");
|
||||
|
||||
assert_eq!(parsed_module.symbols.len(), 1);
|
||||
}
|
||||
131
wtismycode-core/tests/enhanced_analysis.rs
Normal file
131
wtismycode-core/tests/enhanced_analysis.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
//! Enhanced analysis tests for WTIsMyCode
|
||||
//!
|
||||
//! These tests verify that the enhanced analysis functionality works correctly
|
||||
//! with complex code that includes integrations, calls, and docstrings.
|
||||
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use wtismycode_core::{Config, scanner::FileScanner, python_analyzer::PythonAnalyzer};
|
||||
|
||||
#[test]
|
||||
fn test_enhanced_analysis_with_integrations() {
|
||||
// Print current directory for debugging
|
||||
let current_dir = std::env::current_dir().unwrap();
|
||||
println!("Current directory: {:?}", current_dir);
|
||||
|
||||
// Try different paths for the config file
|
||||
let possible_paths = [
|
||||
"tests/golden/test_project/wtismycode.toml",
|
||||
"../tests/golden/test_project/wtismycode.toml",
|
||||
];
|
||||
|
||||
let config_path = possible_paths.iter().find(|&path| {
|
||||
Path::new(path).exists()
|
||||
}).expect("Could not find config file in any expected location");
|
||||
|
||||
println!("Using config path: {:?}", config_path);
|
||||
|
||||
let config = Config::load_from_file(Path::new(config_path)).expect("Failed to load config");
|
||||
|
||||
// Initialize scanner with the correct root path
|
||||
let project_root = Path::new("tests/golden/test_project");
|
||||
let scanner = FileScanner::new(config.clone());
|
||||
|
||||
// Scan for Python files
|
||||
let python_files = scanner.scan_python_files(project_root)
|
||||
.expect("Failed to scan Python files");
|
||||
|
||||
println!("Found Python files: {:?}", python_files);
|
||||
|
||||
// Should find both example.py and advanced_example.py
|
||||
assert_eq!(python_files.len(), 2);
|
||||
|
||||
// Initialize Python analyzer
|
||||
let analyzer = PythonAnalyzer::new(config.clone());
|
||||
|
||||
// Parse each Python file
|
||||
let mut parsed_modules = Vec::new();
|
||||
for file_path in python_files {
|
||||
println!("Parsing file: {:?}", file_path);
|
||||
match analyzer.parse_module(&file_path) {
|
||||
Ok(module) => {
|
||||
println!("Successfully parsed module: {:?}", module.module_path);
|
||||
println!("Imports: {:?}", module.imports);
|
||||
println!("Symbols: {:?}", module.symbols.len());
|
||||
println!("Calls: {:?}", module.calls.len());
|
||||
parsed_modules.push(module);
|
||||
},
|
||||
Err(e) => {
|
||||
panic!("Failed to parse {}: {}", file_path.display(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Parsed {} modules", parsed_modules.len());
|
||||
|
||||
// Resolve symbols and build project model
|
||||
let project_model = analyzer.resolve_symbols(&parsed_modules)
|
||||
.expect("Failed to resolve symbols");
|
||||
|
||||
println!("Project model modules: {}", project_model.modules.len());
|
||||
println!("Project model files: {}", project_model.files.len());
|
||||
println!("Project model symbols: {}", project_model.symbols.len());
|
||||
|
||||
// Add assertions to verify the project model
|
||||
assert!(!project_model.modules.is_empty());
|
||||
assert!(!project_model.files.is_empty());
|
||||
assert!(!project_model.symbols.is_empty());
|
||||
|
||||
// Check that we have the right number of modules (2 files = 2 modules)
|
||||
assert_eq!(project_model.modules.len(), 2);
|
||||
|
||||
// Check that we have the right number of files
|
||||
assert_eq!(project_model.files.len(), 2);
|
||||
|
||||
// Check that we have the right number of symbols
|
||||
// The actual number might be less due to deduplication or other factors
|
||||
// but should be at least the sum of symbols from both files minus duplicates
|
||||
assert!(project_model.symbols.len() >= 10);
|
||||
|
||||
// Check specific details about the advanced example module
|
||||
let mut found_advanced_module = false;
|
||||
for (_, module) in project_model.modules.iter() {
|
||||
if module.path.contains("advanced_example.py") {
|
||||
found_advanced_module = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert!(found_advanced_module);
|
||||
|
||||
// Check that we found the UserService class with DB integration
|
||||
let user_service_symbol = project_model.symbols.values().find(|s| s.id.ends_with("::UserService"));
|
||||
assert!(user_service_symbol.is_some());
|
||||
assert_eq!(user_service_symbol.unwrap().kind, wtismycode_core::model::SymbolKind::Class);
|
||||
|
||||
// Check that we found the NotificationService class with queue integration
|
||||
let notification_service_symbol = project_model.symbols.values().find(|s| s.id.ends_with("::NotificationService"));
|
||||
assert!(notification_service_symbol.is_some());
|
||||
assert_eq!(notification_service_symbol.unwrap().kind, wtismycode_core::model::SymbolKind::Class);
|
||||
|
||||
// Check that we found the fetch_external_user_data function with HTTP integration
|
||||
let fetch_external_user_data_symbol = project_model.symbols.values().find(|s| s.id.ends_with("::fetch_external_user_data"));
|
||||
assert!(fetch_external_user_data_symbol.is_some());
|
||||
assert_eq!(fetch_external_user_data_symbol.unwrap().kind, wtismycode_core::model::SymbolKind::Function);
|
||||
|
||||
// Check file imports
|
||||
let mut found_advanced_file = false;
|
||||
for (_, file_doc) in project_model.files.iter() {
|
||||
if file_doc.path.contains("advanced_example.py") {
|
||||
found_advanced_file = true;
|
||||
assert!(!file_doc.imports.is_empty());
|
||||
// Should have imports for requests, sqlite3, redis, typing
|
||||
let import_names: Vec<&String> = file_doc.imports.iter().collect();
|
||||
assert!(import_names.contains(&&"requests".to_string()));
|
||||
assert!(import_names.contains(&&"sqlite3".to_string()));
|
||||
assert!(import_names.contains(&&"redis".to_string()));
|
||||
assert!(import_names.contains(&&"typing.List".to_string()) || import_names.contains(&&"typing".to_string()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert!(found_advanced_file);
|
||||
}
|
||||
83
wtismycode-core/tests/error_handling.rs
Normal file
83
wtismycode-core/tests/error_handling.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
//! Error handling tests for WTIsMyCode
|
||||
//!
|
||||
//! These tests verify that WTIsMyCode properly handles various error conditions
|
||||
//! and edge cases.
|
||||
|
||||
use std::path::Path;
|
||||
use std::fs;
|
||||
use tempfile::TempDir;
|
||||
use wtismycode_core::{Config, scanner::FileScanner, python_analyzer::PythonAnalyzer};
|
||||
|
||||
#[test]
|
||||
fn test_scanner_nonexistent_directory() {
|
||||
let config = Config::default();
|
||||
let scanner = FileScanner::new(config);
|
||||
|
||||
// Try to scan a nonexistent directory
|
||||
let result = scanner.scan_python_files(Path::new("/nonexistent/directory"));
|
||||
assert!(result.is_err());
|
||||
|
||||
// Check that we get an IO error
|
||||
match result.unwrap_err() {
|
||||
wtismycode_core::errors::WTIsMyCodeError::Io(_) => {},
|
||||
_ => panic!("Expected IO error"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scanner_file_instead_of_directory() {
|
||||
let config = Config::default();
|
||||
let scanner = FileScanner::new(config);
|
||||
|
||||
// Create a temporary file
|
||||
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let temp_file = temp_dir.path().join("test.txt");
|
||||
fs::write(&temp_file, "test content").expect("Failed to write test file");
|
||||
|
||||
// Try to scan a file instead of a directory
|
||||
let result = scanner.scan_python_files(&temp_file);
|
||||
assert!(result.is_err());
|
||||
|
||||
// Check that we get an IO error
|
||||
match result.unwrap_err() {
|
||||
wtismycode_core::errors::WTIsMyCodeError::Io(_) => {},
|
||||
_ => panic!("Expected IO error"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_analyzer_nonexistent_file() {
|
||||
let config = Config::default();
|
||||
let analyzer = PythonAnalyzer::new(config);
|
||||
|
||||
// Try to parse a nonexistent file
|
||||
let result = analyzer.parse_module(Path::new("/nonexistent/file.py"));
|
||||
assert!(result.is_err());
|
||||
|
||||
// Check that we get an IO error
|
||||
match result.unwrap_err() {
|
||||
wtismycode_core::errors::WTIsMyCodeError::Io(_) => {},
|
||||
_ => panic!("Expected IO error"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_analyzer_invalid_python_syntax() {
|
||||
let config = Config::default();
|
||||
let analyzer = PythonAnalyzer::new(config);
|
||||
|
||||
// Create a temporary file with invalid Python syntax
|
||||
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let temp_file = temp_dir.path().join("invalid.py");
|
||||
fs::write(&temp_file, "invalid python syntax @@#$%").expect("Failed to write test file");
|
||||
|
||||
// Try to parse the file
|
||||
let result = analyzer.parse_module(&temp_file);
|
||||
assert!(result.is_err());
|
||||
|
||||
// Check that we get a parse error
|
||||
match result.unwrap_err() {
|
||||
wtismycode_core::errors::WTIsMyCodeError::ParseError { .. } => {},
|
||||
_ => panic!("Expected parse error"),
|
||||
}
|
||||
}
|
||||
157
wtismycode-core/tests/full_pipeline.rs
Normal file
157
wtismycode-core/tests/full_pipeline.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
//! Full pipeline integration tests for WTIsMyCode
|
||||
//!
|
||||
//! Tests the complete scan → analyze → render pipeline using test-project/.
|
||||
|
||||
use wtismycode_core::config::Config;
|
||||
use wtismycode_core::cycle_detector;
|
||||
use wtismycode_core::model::{Module, ProjectModel};
|
||||
use wtismycode_core::renderer::Renderer;
|
||||
use wtismycode_core::scanner::FileScanner;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn test_config_load_and_validate() {
|
||||
let config_path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("test-project/wtismycode.toml");
|
||||
|
||||
let config = Config::load_from_file(&config_path).expect("Failed to load config");
|
||||
assert_eq!(config.project.language, "python");
|
||||
assert!(!config.scan.include.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_validate_on_test_project() {
|
||||
let config_path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("test-project/wtismycode.toml");
|
||||
|
||||
let mut config = Config::load_from_file(&config_path).expect("Failed to load config");
|
||||
// Set root to actual test-project path so validation passes
|
||||
config.project.root = config_path.parent().unwrap().to_string_lossy().to_string();
|
||||
assert!(config.validate().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_validate_rejects_bad_language() {
|
||||
let mut config = Config::default();
|
||||
config.project.language = "java".to_string();
|
||||
assert!(config.validate().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scan_test_project() {
|
||||
let test_project = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("test-project");
|
||||
|
||||
let config_path = test_project.join("wtismycode.toml");
|
||||
let mut config = Config::load_from_file(&config_path).expect("Failed to load config");
|
||||
config.project.root = test_project.to_string_lossy().to_string();
|
||||
|
||||
let scanner = FileScanner::new(config);
|
||||
let files = scanner.scan_python_files(&test_project).expect("Scan should succeed");
|
||||
assert!(!files.is_empty(), "Should find Python files in test-project");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cycle_detection_with_known_cycles() {
|
||||
let mut model = ProjectModel::new();
|
||||
|
||||
// Create a known cycle: a → b → c → a
|
||||
model.modules.insert(
|
||||
"mod_a".into(),
|
||||
Module {
|
||||
id: "mod_a".into(),
|
||||
path: "a.py".into(),
|
||||
files: vec![],
|
||||
doc_summary: None,
|
||||
outbound_modules: vec!["mod_b".into()],
|
||||
inbound_modules: vec!["mod_c".into()],
|
||||
symbols: vec![],
|
||||
},
|
||||
);
|
||||
model.modules.insert(
|
||||
"mod_b".into(),
|
||||
Module {
|
||||
id: "mod_b".into(),
|
||||
path: "b.py".into(),
|
||||
files: vec![],
|
||||
doc_summary: None,
|
||||
outbound_modules: vec!["mod_c".into()],
|
||||
inbound_modules: vec!["mod_a".into()],
|
||||
symbols: vec![],
|
||||
},
|
||||
);
|
||||
model.modules.insert(
|
||||
"mod_c".into(),
|
||||
Module {
|
||||
id: "mod_c".into(),
|
||||
path: "c.py".into(),
|
||||
files: vec![],
|
||||
doc_summary: None,
|
||||
outbound_modules: vec!["mod_a".into()],
|
||||
inbound_modules: vec!["mod_b".into()],
|
||||
symbols: vec![],
|
||||
},
|
||||
);
|
||||
|
||||
let cycles = cycle_detector::detect_cycles(&model);
|
||||
assert_eq!(cycles.len(), 1, "Should detect exactly one cycle");
|
||||
assert_eq!(cycles[0].len(), 3, "Cycle should have 3 modules");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cycle_detection_no_cycles() {
|
||||
let mut model = ProjectModel::new();
|
||||
|
||||
model.modules.insert(
|
||||
"mod_a".into(),
|
||||
Module {
|
||||
id: "mod_a".into(),
|
||||
path: "a.py".into(),
|
||||
files: vec![],
|
||||
doc_summary: None,
|
||||
outbound_modules: vec!["mod_b".into()],
|
||||
inbound_modules: vec![],
|
||||
symbols: vec![],
|
||||
},
|
||||
);
|
||||
model.modules.insert(
|
||||
"mod_b".into(),
|
||||
Module {
|
||||
id: "mod_b".into(),
|
||||
path: "b.py".into(),
|
||||
files: vec![],
|
||||
doc_summary: None,
|
||||
outbound_modules: vec![],
|
||||
inbound_modules: vec!["mod_a".into()],
|
||||
symbols: vec![],
|
||||
},
|
||||
);
|
||||
|
||||
let cycles = cycle_detector::detect_cycles(&model);
|
||||
assert!(cycles.is_empty(), "Should detect no cycles in DAG");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_renderer_produces_output() {
|
||||
let config = Config::default();
|
||||
let model = ProjectModel::new();
|
||||
let renderer = Renderer::new();
|
||||
let result = renderer.render_architecture_md(&model, None);
|
||||
assert!(result.is_ok(), "Renderer should produce output for empty model");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_duration_values() {
|
||||
use wtismycode_core::config::{parse_duration, parse_file_size};
|
||||
|
||||
assert_eq!(parse_duration("24h").unwrap(), 86400);
|
||||
assert_eq!(parse_duration("7d").unwrap(), 604800);
|
||||
assert_eq!(parse_file_size("10MB").unwrap(), 10 * 1024 * 1024);
|
||||
assert_eq!(parse_file_size("1GB").unwrap(), 1024 * 1024 * 1024);
|
||||
}
|
||||
60
wtismycode-core/tests/golden/files/example_architecture.md
Normal file
60
wtismycode-core/tests/golden/files/example_architecture.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Architecture Documentation
|
||||
|
||||
Generated at: 1970-01-01 00:00:00 UTC
|
||||
|
||||
## Overview
|
||||
|
||||
This document provides an overview of the architecture for the project.
|
||||
|
||||
## Modules
|
||||
|
||||
### example.py
|
||||
|
||||
File: `example.py`
|
||||
|
||||
#### Imports
|
||||
|
||||
- `os`
|
||||
- `typing.List`
|
||||
|
||||
#### Symbols
|
||||
|
||||
##### Calculator
|
||||
|
||||
- Type: Class
|
||||
- Signature: `class Calculator`
|
||||
- Purpose: extracted from AST
|
||||
|
||||
##### Calculator.__init__
|
||||
|
||||
- Type: Function
|
||||
- Signature: `def __init__(...)`
|
||||
- Purpose: extracted from AST
|
||||
|
||||
##### Calculator.add
|
||||
|
||||
- Type: Function
|
||||
- Signature: `def add(...)`
|
||||
- Purpose: extracted from AST
|
||||
|
||||
##### Calculator.multiply
|
||||
|
||||
- Type: Function
|
||||
- Signature: `def multiply(...)`
|
||||
- Purpose: extracted from AST
|
||||
|
||||
##### process_numbers
|
||||
|
||||
- Type: Function
|
||||
- Signature: `def process_numbers(...)`
|
||||
- Purpose: extracted from AST
|
||||
|
||||
## Metrics
|
||||
|
||||
### Critical Components
|
||||
|
||||
No critical components identified.
|
||||
|
||||
### Component Dependencies
|
||||
|
||||
Dependency analysis not yet implemented.
|
||||
107
wtismycode-core/tests/golden/mod.rs
Normal file
107
wtismycode-core/tests/golden/mod.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
//! Golden tests for WTIsMyCode
|
||||
//!
|
||||
//! These tests generate documentation for test projects and compare the output
|
||||
//! with expected "golden" files to ensure consistency.
|
||||
|
||||
mod test_utils;
|
||||
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use wtismycode_core::{Config, scanner::FileScanner, python_analyzer::PythonAnalyzer};
|
||||
|
||||
#[test]
|
||||
fn test_simple_project_generation() {
|
||||
// Print current directory for debugging
|
||||
let current_dir = std::env::current_dir().unwrap();
|
||||
println!("Current directory: {:?}", current_dir);
|
||||
|
||||
// Try different paths for the config file
|
||||
let possible_paths = [
|
||||
"tests/golden/test_project/wtismycode.toml",
|
||||
"../tests/golden/test_project/wtismycode.toml",
|
||||
];
|
||||
|
||||
let config_path = possible_paths.iter().find(|&path| {
|
||||
Path::new(path).exists()
|
||||
}).expect("Could not find config file in any expected location");
|
||||
|
||||
println!("Using config path: {:?}", config_path);
|
||||
|
||||
let config = Config::load_from_file(Path::new(config_path)).expect("Failed to load config");
|
||||
|
||||
// Initialize scanner with the correct root path
|
||||
let project_root = Path::new("tests/golden/test_project");
|
||||
let scanner = FileScanner::new(config.clone());
|
||||
|
||||
// Scan for Python files
|
||||
let python_files = scanner.scan_python_files(project_root)
|
||||
.expect("Failed to scan Python files");
|
||||
|
||||
println!("Found Python files: {:?}", python_files);
|
||||
|
||||
// Initialize Python analyzer
|
||||
let analyzer = PythonAnalyzer::new(config.clone());
|
||||
|
||||
// Parse each Python file
|
||||
let mut parsed_modules = Vec::new();
|
||||
for file_path in python_files {
|
||||
println!("Parsing file: {:?}", file_path);
|
||||
match analyzer.parse_module(&file_path) {
|
||||
Ok(module) => {
|
||||
println!("Successfully parsed module: {:?}", module.module_path);
|
||||
println!("Imports: {:?}", module.imports);
|
||||
println!("Symbols: {:?}", module.symbols.len());
|
||||
println!("Calls: {:?}", module.calls.len());
|
||||
parsed_modules.push(module);
|
||||
},
|
||||
Err(e) => {
|
||||
panic!("Failed to parse {}: {}", file_path.display(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Parsed {} modules", parsed_modules.len());
|
||||
|
||||
// Resolve symbols and build project model
|
||||
let project_model = analyzer.resolve_symbols(&parsed_modules)
|
||||
.expect("Failed to resolve symbols");
|
||||
|
||||
println!("Project model modules: {}", project_model.modules.len());
|
||||
println!("Project model files: {}", project_model.files.len());
|
||||
println!("Project model symbols: {}", project_model.symbols.len());
|
||||
|
||||
// Add assertions to verify the project model
|
||||
assert!(!project_model.modules.is_empty());
|
||||
assert!(!project_model.files.is_empty());
|
||||
assert!(!project_model.symbols.is_empty());
|
||||
|
||||
// Check specific details about the parsed modules
|
||||
// Now we have 2 modules (example.py and advanced_example.py)
|
||||
assert_eq!(project_model.modules.len(), 2);
|
||||
|
||||
// Find the example.py module
|
||||
let mut found_example_module = false;
|
||||
for (_, module) in project_model.modules.iter() {
|
||||
if module.path.contains("example.py") {
|
||||
found_example_module = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert!(found_example_module);
|
||||
|
||||
// Check that we found the Calculator class
|
||||
let calculator_symbol = project_model.symbols.values().find(|s| s.id.ends_with("::Calculator"));
|
||||
assert!(calculator_symbol.is_some());
|
||||
assert_eq!(calculator_symbol.unwrap().kind, wtismycode_core::model::SymbolKind::Class);
|
||||
|
||||
// Check that we found the process_numbers function
|
||||
let process_numbers_symbol = project_model.symbols.values().find(|s| s.id.ends_with("::process_numbers"));
|
||||
assert!(process_numbers_symbol.is_some());
|
||||
assert_eq!(process_numbers_symbol.unwrap().kind, wtismycode_core::model::SymbolKind::Function);
|
||||
|
||||
// Check file imports
|
||||
assert!(!project_model.files.is_empty());
|
||||
let file_entry = project_model.files.iter().next().unwrap();
|
||||
let file_doc = file_entry.1;
|
||||
assert!(!file_doc.imports.is_empty());
|
||||
}
|
||||
73
wtismycode-core/tests/golden/test_project/ARCHITECTURE.md
Normal file
73
wtismycode-core/tests/golden/test_project/ARCHITECTURE.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# ARCHITECTURE — New Project
|
||||
|
||||
<!-- MANUAL:BEGIN -->
|
||||
## Project summary
|
||||
**Name:** New Project
|
||||
**Description:** <FILL_MANUALLY: what this project does in 3–7 lines>
|
||||
|
||||
## Key decisions (manual)
|
||||
- <FILL_MANUALLY>
|
||||
|
||||
## Non-goals (manual)
|
||||
- <FILL_MANUALLY>
|
||||
<!-- MANUAL:END -->
|
||||
|
||||
---
|
||||
|
||||
## Document metadata
|
||||
- **Created:** 2026-01-25
|
||||
- **Updated:** 2026-01-25
|
||||
- **Generated by:** wtismycode (cli) v0.1
|
||||
|
||||
---
|
||||
|
||||
## Rails / Tooling
|
||||
<!-- ARCHDOC:BEGIN section=rails -->
|
||||
|
||||
No tooling information available.
|
||||
<!-- ARCHDOC:END section=rails -->
|
||||
|
||||
---
|
||||
|
||||
## Repository layout (top-level)
|
||||
<!-- ARCHDOC:BEGIN section=layout -->
|
||||
|
||||
| Path | Purpose | Link |
|
||||
|------|---------|------|
|
||||
| ./src/advanced_example.py | Source file | [details](docs/architecture/files/._src_advanced_example.py.md) |
|
||||
| ./src/example.py | Source file | [details](docs/architecture/files/._src_example.py.md) |
|
||||
<!-- ARCHDOC:END section=layout -->
|
||||
|
||||
---
|
||||
|
||||
## Modules index
|
||||
<!-- ARCHDOC:BEGIN section=modules_index -->
|
||||
|
||||
| Module | Symbols | Inbound | Outbound | Link |
|
||||
|--------|---------|---------|----------|------|
|
||||
| ./src/advanced_example.py | 10 | 0 | 0 | [details](docs/architecture/modules/._src_advanced_example.py.md) |
|
||||
| ./src/example.py | 5 | 0 | 0 | [details](docs/architecture/modules/._src_example.py.md) |
|
||||
<!-- ARCHDOC:END section=modules_index -->
|
||||
|
||||
---
|
||||
|
||||
## Critical dependency points
|
||||
<!-- ARCHDOC:BEGIN section=critical_points -->
|
||||
|
||||
### High Fan-in (Most Called)
|
||||
| Symbol | Fan-in | Critical |
|
||||
|--------|--------|----------|
|
||||
|
||||
### High Fan-out (Calls Many)
|
||||
| Symbol | Fan-out | Critical |
|
||||
|--------|---------|----------|
|
||||
|
||||
### Module Cycles
|
||||
<!-- ARCHDOC:END section=critical_points -->
|
||||
|
||||
---
|
||||
|
||||
<!-- MANUAL:BEGIN -->
|
||||
## Change notes (manual)
|
||||
- <FILL_MANUALLY>
|
||||
<!-- MANUAL:END -->
|
||||
@@ -0,0 +1,3 @@
|
||||
# File: ./src/advanced_example.py
|
||||
|
||||
TODO: Add file documentation
|
||||
@@ -0,0 +1,3 @@
|
||||
# File: ./src/example.py
|
||||
|
||||
TODO: Add file documentation
|
||||
@@ -0,0 +1,3 @@
|
||||
# Module: ./src/advanced_example.py
|
||||
|
||||
TODO: Add module documentation
|
||||
@@ -0,0 +1,3 @@
|
||||
# Module: ./src/example.py
|
||||
|
||||
TODO: Add module documentation
|
||||
@@ -0,0 +1,107 @@
|
||||
"""Advanced example module for testing with integrations."""
|
||||
|
||||
import requests
|
||||
import sqlite3
|
||||
import redis
|
||||
from typing import List, Dict
|
||||
|
||||
class UserService:
|
||||
"""A service for managing users with database integration."""
|
||||
|
||||
def __init__(self, db_path: str = "users.db"):
|
||||
"""Initialize the user service with database path."""
|
||||
self.db_path = db_path
|
||||
self._init_db()
|
||||
|
||||
def _init_db(self):
|
||||
"""Initialize the database."""
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT UNIQUE NOT NULL
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def create_user(self, name: str, email: str) -> Dict:
|
||||
"""Create a new user in the database."""
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"INSERT INTO users (name, email) VALUES (?, ?)",
|
||||
(name, email)
|
||||
)
|
||||
user_id = cursor.lastrowid
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
return {"id": user_id, "name": name, "email": email}
|
||||
|
||||
def get_user(self, user_id: int) -> Dict:
|
||||
"""Get a user by ID from the database."""
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
|
||||
row = cursor.fetchone()
|
||||
conn.close()
|
||||
|
||||
if row:
|
||||
return {"id": row[0], "name": row[1], "email": row[2]}
|
||||
return None
|
||||
|
||||
class NotificationService:
|
||||
"""A service for sending notifications with queue integration."""
|
||||
|
||||
def __init__(self, redis_url: str = "redis://localhost:6379"):
|
||||
"""Initialize the notification service with Redis URL."""
|
||||
self.redis_client = redis.Redis.from_url(redis_url)
|
||||
|
||||
def send_email_notification(self, user_id: int, message: str) -> bool:
|
||||
"""Send an email notification by queuing it."""
|
||||
notification = {
|
||||
"user_id": user_id,
|
||||
"message": message,
|
||||
"type": "email"
|
||||
}
|
||||
|
||||
# Push to Redis queue
|
||||
self.redis_client.lpush("notifications", str(notification))
|
||||
return True
|
||||
|
||||
def fetch_external_user_data(user_id: int) -> Dict:
|
||||
"""Fetch user data from an external API."""
|
||||
response = requests.get(f"https://api.example.com/users/{user_id}")
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
return {}
|
||||
|
||||
def process_users(user_ids: List[int]) -> List[Dict]:
|
||||
"""Process a list of users with various integrations."""
|
||||
# Database integration
|
||||
user_service = UserService()
|
||||
|
||||
# Queue integration
|
||||
notification_service = NotificationService()
|
||||
|
||||
results = []
|
||||
for user_id in user_ids:
|
||||
# Database operation
|
||||
user = user_service.get_user(user_id)
|
||||
if user:
|
||||
# External API integration
|
||||
external_data = fetch_external_user_data(user_id)
|
||||
user.update(external_data)
|
||||
|
||||
# Queue operation
|
||||
notification_service.send_email_notification(
|
||||
user_id,
|
||||
f"Processing user {user['name']}"
|
||||
)
|
||||
|
||||
results.append(user)
|
||||
|
||||
return results
|
||||
29
wtismycode-core/tests/golden/test_project/src/example.py
Normal file
29
wtismycode-core/tests/golden/test_project/src/example.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""Example module for testing."""
|
||||
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
class Calculator:
|
||||
"""A simple calculator class."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the calculator."""
|
||||
pass
|
||||
|
||||
def add(self, a: int, b: int) -> int:
|
||||
"""Add two numbers."""
|
||||
return a + b
|
||||
|
||||
def multiply(self, a: int, b: int) -> int:
|
||||
"""Multiply two numbers."""
|
||||
return a * b
|
||||
|
||||
def process_numbers(numbers: List[int]) -> List[int]:
|
||||
"""Process a list of numbers."""
|
||||
calc = Calculator()
|
||||
return [calc.add(n, 1) for n in numbers]
|
||||
|
||||
if __name__ == "__main__":
|
||||
numbers = [1, 2, 3, 4, 5]
|
||||
result = process_numbers(numbers)
|
||||
print(f"Processed numbers: {result}")
|
||||
62
wtismycode-core/tests/golden/test_project/wtismycode.toml
Normal file
62
wtismycode-core/tests/golden/test_project/wtismycode.toml
Normal file
@@ -0,0 +1,62 @@
|
||||
[project]
|
||||
root = "."
|
||||
out_dir = "docs/architecture"
|
||||
entry_file = "ARCHITECTURE.md"
|
||||
language = "python"
|
||||
|
||||
[scan]
|
||||
include = ["src", "app", "tests"]
|
||||
exclude = [
|
||||
".venv", "venv", "__pycache__", ".git", "dist", "build",
|
||||
".mypy_cache", ".ruff_cache", ".pytest_cache", "*.egg-info"
|
||||
]
|
||||
follow_symlinks = false
|
||||
max_file_size = "10MB"
|
||||
|
||||
[python]
|
||||
src_roots = ["src", "."]
|
||||
include_tests = true
|
||||
parse_docstrings = true
|
||||
max_parse_errors = 10
|
||||
|
||||
[analysis]
|
||||
resolve_calls = true
|
||||
resolve_inheritance = false
|
||||
detect_integrations = true
|
||||
integration_patterns = [
|
||||
{ type = "http", patterns = ["requests", "httpx", "aiohttp"] },
|
||||
{ type = "db", patterns = ["sqlalchemy", "psycopg", "mysql", "sqlite3"] },
|
||||
{ type = "queue", patterns = ["celery", "kafka", "pika", "redis"] }
|
||||
]
|
||||
|
||||
[output]
|
||||
single_file = false
|
||||
per_file_docs = true
|
||||
create_directories = true
|
||||
overwrite_manual_sections = false
|
||||
|
||||
[diff]
|
||||
update_timestamp_on_change_only = true
|
||||
hash_algorithm = "sha256"
|
||||
preserve_manual_content = true
|
||||
|
||||
[thresholds]
|
||||
critical_fan_in = 20
|
||||
critical_fan_out = 20
|
||||
high_complexity = 50
|
||||
|
||||
[rendering]
|
||||
template_engine = "handlebars"
|
||||
max_table_rows = 100
|
||||
truncate_long_descriptions = true
|
||||
description_max_length = 200
|
||||
|
||||
[logging]
|
||||
level = "info"
|
||||
file = "wtismycode.log"
|
||||
format = "compact"
|
||||
|
||||
[caching]
|
||||
enabled = true
|
||||
cache_dir = ".wtismycode/cache"
|
||||
max_cache_age = "24h"
|
||||
21
wtismycode-core/tests/golden/test_utils.rs
Normal file
21
wtismycode-core/tests/golden/test_utils.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
//! Test utilities for golden tests
|
||||
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
/// Read a file and return its contents
|
||||
pub fn read_test_file(path: &str) -> String {
|
||||
fs::read_to_string(path).expect(&format!("Failed to read test file: {}", path))
|
||||
}
|
||||
|
||||
/// Write content to a file for testing
|
||||
pub fn write_test_file(path: &str, content: &str) {
|
||||
fs::write(path, content).expect(&format!("Failed to write test file: {}", path))
|
||||
}
|
||||
|
||||
/// Compare two strings and panic if they don't match
|
||||
pub fn assert_strings_equal(actual: &str, expected: &str, message: &str) {
|
||||
if actual != expected {
|
||||
panic!("{}: Strings do not match\nActual:\n{}\nExpected:\n{}", message, actual, expected);
|
||||
}
|
||||
}
|
||||
137
wtismycode-core/tests/integration_detection.rs
Normal file
137
wtismycode-core/tests/integration_detection.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
//! Integration detection tests for WTIsMyCode
|
||||
//!
|
||||
//! These tests verify that the integration detection functionality works correctly.
|
||||
//! Integration detection now happens at module level during resolve_symbols,
|
||||
//! based on actual imports rather than AST body inspection.
|
||||
|
||||
use std::fs;
|
||||
use tempfile::TempDir;
|
||||
use wtismycode_core::{Config, python_analyzer::PythonAnalyzer};
|
||||
|
||||
#[test]
|
||||
fn test_http_integration_detection() {
|
||||
let mut config = Config::default();
|
||||
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
config.project.root = temp_dir.path().to_string_lossy().to_string();
|
||||
config.python.src_roots = vec![".".to_string()];
|
||||
let analyzer = PythonAnalyzer::new(config);
|
||||
|
||||
let temp_file = temp_dir.path().join("test.py");
|
||||
let python_code = r#"
|
||||
import requests
|
||||
|
||||
def fetch_data():
|
||||
response = requests.get("https://api.example.com/data")
|
||||
return response.json()
|
||||
"#;
|
||||
fs::write(&temp_file, python_code).expect("Failed to write test file");
|
||||
|
||||
let parsed_module = analyzer.parse_module(&temp_file)
|
||||
.expect("Failed to parse module");
|
||||
|
||||
let model = analyzer.resolve_symbols(&[parsed_module])
|
||||
.expect("Failed to resolve symbols");
|
||||
|
||||
// Find the symbol (now prefixed with module id)
|
||||
let symbol = model.symbols.values().find(|s| s.qualname == "fetch_data")
|
||||
.expect("fetch_data symbol not found");
|
||||
|
||||
assert!(symbol.integrations_flags.http);
|
||||
assert!(!symbol.integrations_flags.db);
|
||||
assert!(!symbol.integrations_flags.queue);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_db_integration_detection() {
|
||||
let mut config = Config::default();
|
||||
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
config.project.root = temp_dir.path().to_string_lossy().to_string();
|
||||
config.python.src_roots = vec![".".to_string()];
|
||||
let analyzer = PythonAnalyzer::new(config);
|
||||
|
||||
let temp_file = temp_dir.path().join("test.py");
|
||||
let python_code = r#"
|
||||
import sqlite3
|
||||
|
||||
def get_user(user_id):
|
||||
conn = sqlite3.connect("database.db")
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
|
||||
return cursor.fetchone()
|
||||
"#;
|
||||
fs::write(&temp_file, python_code).expect("Failed to write test file");
|
||||
|
||||
let parsed_module = analyzer.parse_module(&temp_file)
|
||||
.expect("Failed to parse module");
|
||||
|
||||
let model = analyzer.resolve_symbols(&[parsed_module])
|
||||
.expect("Failed to resolve symbols");
|
||||
|
||||
let symbol = model.symbols.values().find(|s| s.qualname == "get_user")
|
||||
.expect("get_user symbol not found");
|
||||
|
||||
assert!(!symbol.integrations_flags.http);
|
||||
assert!(symbol.integrations_flags.db);
|
||||
assert!(!symbol.integrations_flags.queue);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_queue_integration_detection() {
|
||||
let mut config = Config::default();
|
||||
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
config.project.root = temp_dir.path().to_string_lossy().to_string();
|
||||
config.python.src_roots = vec![".".to_string()];
|
||||
let analyzer = PythonAnalyzer::new(config);
|
||||
|
||||
let temp_file = temp_dir.path().join("test.py");
|
||||
let python_code = r#"
|
||||
import redis
|
||||
|
||||
def process_job(job_data):
|
||||
client = redis.Redis()
|
||||
client.lpush("job_queue", job_data)
|
||||
"#;
|
||||
fs::write(&temp_file, python_code).expect("Failed to write test file");
|
||||
|
||||
let parsed_module = analyzer.parse_module(&temp_file)
|
||||
.expect("Failed to parse module");
|
||||
|
||||
let model = analyzer.resolve_symbols(&[parsed_module])
|
||||
.expect("Failed to resolve symbols");
|
||||
|
||||
let symbol = model.symbols.values().find(|s| s.qualname == "process_job")
|
||||
.expect("process_job symbol not found");
|
||||
|
||||
assert!(!symbol.integrations_flags.http);
|
||||
assert!(!symbol.integrations_flags.db);
|
||||
assert!(symbol.integrations_flags.queue);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_integration_detection() {
|
||||
let mut config = Config::default();
|
||||
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
config.project.root = temp_dir.path().to_string_lossy().to_string();
|
||||
config.python.src_roots = vec![".".to_string()];
|
||||
let analyzer = PythonAnalyzer::new(config);
|
||||
|
||||
let temp_file = temp_dir.path().join("test.py");
|
||||
let python_code = r#"
|
||||
def calculate_sum(a, b):
|
||||
return a + b
|
||||
"#;
|
||||
fs::write(&temp_file, python_code).expect("Failed to write test file");
|
||||
|
||||
let parsed_module = analyzer.parse_module(&temp_file)
|
||||
.expect("Failed to parse module");
|
||||
|
||||
let model = analyzer.resolve_symbols(&[parsed_module])
|
||||
.expect("Failed to resolve symbols");
|
||||
|
||||
let symbol = model.symbols.values().find(|s| s.qualname == "calculate_sum")
|
||||
.expect("calculate_sum symbol not found");
|
||||
|
||||
assert!(!symbol.integrations_flags.http);
|
||||
assert!(!symbol.integrations_flags.db);
|
||||
assert!(!symbol.integrations_flags.queue);
|
||||
}
|
||||
13
wtismycode-core/tests/integration_tests.rs
Normal file
13
wtismycode-core/tests/integration_tests.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
//! Integration tests for WTIsMyCode
|
||||
|
||||
// Include golden tests
|
||||
mod golden;
|
||||
mod error_handling;
|
||||
mod caching;
|
||||
mod integration_detection;
|
||||
mod enhanced_analysis;
|
||||
|
||||
// Run all tests
|
||||
fn main() {
|
||||
// This is just a placeholder - tests are run by cargo test
|
||||
}
|
||||
93
wtismycode-core/tests/project_analysis.rs
Normal file
93
wtismycode-core/tests/project_analysis.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
//! Tests for analyzing the test project
|
||||
|
||||
use wtismycode_core::{
|
||||
config::Config,
|
||||
python_analyzer::PythonAnalyzer,
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn test_project_analysis() {
|
||||
// Load config from test project
|
||||
let config = Config::load_from_file(Path::new("../test-project/wtismycode.toml")).unwrap();
|
||||
|
||||
// Initialize analyzer
|
||||
let analyzer = PythonAnalyzer::new(config);
|
||||
|
||||
// Parse core module
|
||||
let core_module = analyzer.parse_module(Path::new("../test-project/src/core.py")).unwrap();
|
||||
|
||||
println!("Core module symbols: {}", core_module.symbols.len());
|
||||
for symbol in &core_module.symbols {
|
||||
println!(" Symbol: {} ({:?}), DB: {}, HTTP: {}", symbol.id, symbol.kind, symbol.integrations_flags.db, symbol.integrations_flags.http);
|
||||
}
|
||||
|
||||
println!("Core module calls: {}", core_module.calls.len());
|
||||
for call in &core_module.calls {
|
||||
println!(" Call: {} -> {}", call.caller_symbol, call.callee_expr);
|
||||
}
|
||||
|
||||
// Check that we found symbols
|
||||
assert!(!core_module.symbols.is_empty()); // Should find at least the main symbols
|
||||
|
||||
// Check that we found calls
|
||||
assert!(!core_module.calls.is_empty());
|
||||
|
||||
// Check that integrations are detected
|
||||
let db_integration_found = core_module.symbols.iter().any(|s| s.integrations_flags.db);
|
||||
let http_integration_found = core_module.symbols.iter().any(|s| s.integrations_flags.http);
|
||||
|
||||
assert!(db_integration_found, "Database integration should be detected");
|
||||
assert!(http_integration_found, "HTTP integration should be detected");
|
||||
|
||||
// Parse utils module
|
||||
let utils_module = analyzer.parse_module(Path::new("../test-project/src/utils.py")).unwrap();
|
||||
|
||||
println!("Utils module symbols: {}", utils_module.symbols.len());
|
||||
for symbol in &utils_module.symbols {
|
||||
println!(" Symbol: {} ({:?}), DB: {}, HTTP: {}", symbol.id, symbol.kind, symbol.integrations_flags.db, symbol.integrations_flags.http);
|
||||
}
|
||||
|
||||
// Check that we found symbols
|
||||
assert!(!utils_module.symbols.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_full_project_resolution() {
|
||||
// Load config from test project
|
||||
let config = Config::load_from_file(Path::new("../test-project/wtismycode.toml")).unwrap();
|
||||
|
||||
// Initialize analyzer
|
||||
let analyzer = PythonAnalyzer::new(config);
|
||||
|
||||
// Parse all modules
|
||||
let core_module = analyzer.parse_module(Path::new("../test-project/src/core.py")).unwrap();
|
||||
let utils_module = analyzer.parse_module(Path::new("../test-project/src/utils.py")).unwrap();
|
||||
|
||||
let modules = vec![core_module, utils_module];
|
||||
|
||||
// Resolve symbols
|
||||
let project_model = analyzer.resolve_symbols(&modules).unwrap();
|
||||
|
||||
// Check project model
|
||||
assert!(!project_model.modules.is_empty());
|
||||
assert!(!project_model.symbols.is_empty());
|
||||
assert!(!project_model.files.is_empty());
|
||||
|
||||
// Check that integrations are preserved in the project model
|
||||
let db_integration_found = project_model.symbols.values().any(|s| s.integrations_flags.db);
|
||||
let http_integration_found = project_model.symbols.values().any(|s| s.integrations_flags.http);
|
||||
|
||||
assert!(db_integration_found, "Database integration should be preserved in project model");
|
||||
assert!(http_integration_found, "HTTP integration should be preserved in project model");
|
||||
|
||||
println!("Project modules: {:?}", project_model.modules.keys().collect::<Vec<_>>());
|
||||
println!("Project symbols: {}", project_model.symbols.len());
|
||||
|
||||
// Print integration information
|
||||
for (id, symbol) in &project_model.symbols {
|
||||
if symbol.integrations_flags.db || symbol.integrations_flags.http {
|
||||
println!("Symbol {} has DB: {}, HTTP: {}", id, symbol.integrations_flags.db, symbol.integrations_flags.http);
|
||||
}
|
||||
}
|
||||
}
|
||||
89
wtismycode-core/tests/renderer_tests.rs
Normal file
89
wtismycode-core/tests/renderer_tests.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
//! Tests for the renderer functionality
|
||||
|
||||
use wtismycode_core::{
|
||||
model::{ProjectModel, Symbol, SymbolKind, IntegrationFlags, SymbolMetrics},
|
||||
renderer::Renderer,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn test_render_with_integrations() {
|
||||
// Create a mock project model with integration information
|
||||
let mut project_model = ProjectModel::new();
|
||||
|
||||
// Add a symbol with database integration
|
||||
let db_symbol = Symbol {
|
||||
id: "DatabaseManager".to_string(),
|
||||
kind: SymbolKind::Class,
|
||||
module_id: "test_module".to_string(),
|
||||
file_id: "test_file.py".to_string(),
|
||||
qualname: "DatabaseManager".to_string(),
|
||||
signature: "class DatabaseManager".to_string(),
|
||||
annotations: None,
|
||||
docstring_first_line: None,
|
||||
purpose: "test".to_string(),
|
||||
outbound_calls: vec![],
|
||||
inbound_calls: vec![],
|
||||
integrations_flags: IntegrationFlags {
|
||||
db: true,
|
||||
http: false,
|
||||
queue: false,
|
||||
storage: false,
|
||||
ai: false,
|
||||
},
|
||||
metrics: SymbolMetrics {
|
||||
fan_in: 0,
|
||||
fan_out: 0,
|
||||
is_critical: false,
|
||||
cycle_participant: false,
|
||||
},
|
||||
};
|
||||
|
||||
// Add a symbol with HTTP integration
|
||||
let http_symbol = Symbol {
|
||||
id: "fetch_data".to_string(),
|
||||
kind: SymbolKind::Function,
|
||||
module_id: "test_module".to_string(),
|
||||
file_id: "test_file.py".to_string(),
|
||||
qualname: "fetch_data".to_string(),
|
||||
signature: "def fetch_data()".to_string(),
|
||||
annotations: None,
|
||||
docstring_first_line: None,
|
||||
purpose: "test".to_string(),
|
||||
outbound_calls: vec![],
|
||||
inbound_calls: vec![],
|
||||
integrations_flags: IntegrationFlags {
|
||||
db: false,
|
||||
http: true,
|
||||
queue: false,
|
||||
storage: false,
|
||||
ai: false,
|
||||
},
|
||||
metrics: SymbolMetrics {
|
||||
fan_in: 0,
|
||||
fan_out: 0,
|
||||
is_critical: false,
|
||||
cycle_participant: false,
|
||||
},
|
||||
};
|
||||
|
||||
project_model.symbols.insert("DatabaseManager".to_string(), db_symbol);
|
||||
project_model.symbols.insert("fetch_data".to_string(), http_symbol);
|
||||
|
||||
// Initialize renderer
|
||||
let renderer = Renderer::new();
|
||||
|
||||
// Render architecture documentation
|
||||
let result = renderer.render_architecture_md(&project_model, None);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let rendered_content = result.unwrap();
|
||||
println!("Rendered content:\n{}", rendered_content);
|
||||
|
||||
// Check that integration sections are present
|
||||
assert!(rendered_content.contains("## Integrations"));
|
||||
assert!(rendered_content.contains("### Database Integrations"));
|
||||
assert!(rendered_content.contains("### HTTP/API Integrations"));
|
||||
assert!(rendered_content.contains("DatabaseManager in test_file.py"));
|
||||
assert!(rendered_content.contains("fetch_data in test_file.py"));
|
||||
}
|
||||
Reference in New Issue
Block a user