#![cfg(test)]
use context_creator::cli::Config;
use context_creator::core::cache::FileCache;
use context_creator::core::semantic_graph::perform_semantic_analysis_graph;
use context_creator::core::walker::{walk_directory, WalkOptions};
use std::fs;
use tempfile::TempDir;
#[test]
fn test_import_edge_type() {
let temp_dir = TempDir::new().unwrap();
let base_path = temp_dir.path();
fs::create_dir_all(base_path.join(".git")).unwrap();
fs::create_dir_all(base_path.join("src")).unwrap();
fs::write(
base_path.join("src/utils.rs"),
r#"
pub fn helper() -> String {
"helper".to_string()
}
"#,
)
.unwrap();
fs::write(
base_path.join("src/main.rs"),
r#"
mod utils;
use utils::helper;
fn main() {
let result = helper();
println!("{}", result);
}
"#,
)
.unwrap();
let walk_options = WalkOptions::default();
let mut files = walk_directory(base_path, walk_options).unwrap();
files.retain(|f| f.path.extension().is_some_and(|ext| ext == "rs"));
let config = Config {
trace_imports: true,
..Default::default()
};
let cache = FileCache::new();
perform_semantic_analysis_graph(&mut files, &config, &cache).unwrap();
let main_file = files
.iter()
.find(|f| f.path.to_string_lossy().contains("main.rs"))
.unwrap();
let utils_file = files
.iter()
.find(|f| f.path.to_string_lossy().contains("utils.rs"))
.unwrap();
assert!(
main_file.imports.contains(&utils_file.path),
"main.rs should import utils.rs"
);
assert!(
utils_file.imported_by.contains(&main_file.path),
"utils.rs should be imported by main.rs"
);
}
#[test]
fn test_function_call_edge_type() {
let temp_dir = TempDir::new().unwrap();
let base_path = temp_dir.path();
fs::create_dir_all(base_path.join(".git")).unwrap();
fs::create_dir_all(base_path.join("src")).unwrap();
fs::write(
base_path.join("src/math.rs"),
r#"
pub fn calculate(a: i32, b: i32) -> i32 {
a + b
}
pub fn multiply(a: i32, b: i32) -> i32 {
a * b
}
"#,
)
.unwrap();
fs::write(
base_path.join("src/processor.rs"),
r#"
mod math;
use math::{calculate, multiply};
pub fn process_data(values: Vec<i32>) -> i32 {
let sum = calculate(values[0], values[1]);
let product = multiply(sum, values[2]);
product
}
"#,
)
.unwrap();
let walk_options = WalkOptions::default();
let mut files = walk_directory(base_path, walk_options).unwrap();
files.retain(|f| f.path.extension().is_some_and(|ext| ext == "rs"));
let config = Config {
trace_imports: true,
include_callers: true,
..Default::default()
};
let cache = FileCache::new();
perform_semantic_analysis_graph(&mut files, &config, &cache).unwrap();
let processor_file = files
.iter()
.find(|f| f.path.to_string_lossy().contains("processor.rs"))
.unwrap();
let math_file = files
.iter()
.find(|f| f.path.to_string_lossy().contains("math.rs"))
.unwrap();
assert!(
!processor_file.function_calls.is_empty(),
"processor.rs should have function calls"
);
let called_functions: Vec<&str> = processor_file
.function_calls
.iter()
.map(|fc| fc.name.as_str())
.collect();
assert!(
called_functions.contains(&"calculate"),
"processor.rs should call calculate function"
);
assert!(
called_functions.contains(&"multiply"),
"processor.rs should call multiply function"
);
assert!(
processor_file.imports.contains(&math_file.path)
|| math_file.imported_by.contains(&processor_file.path),
"There should be an import relationship between processor.rs and math.rs"
);
}
#[test]
fn test_inheritance_edge_type() {
let temp_dir = TempDir::new().unwrap();
let base_path = temp_dir.path();
fs::create_dir_all(base_path.join(".git")).unwrap();
fs::create_dir_all(base_path.join("src")).unwrap();
fs::write(
base_path.join("src/base.py"),
r#"
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
pass
"#,
)
.unwrap();
fs::write(
base_path.join("src/dog.py"),
r#"
from base import Animal
class Dog(Animal):
def speak(self):
return f"{self.name} barks!"
"#,
)
.unwrap();
let walk_options = WalkOptions::default();
let mut files = walk_directory(base_path, walk_options).unwrap();
files.retain(|f| f.path.extension().is_some_and(|ext| ext == "py"));
let config = Config {
trace_imports: true,
include_types: true,
..Default::default()
};
let cache = FileCache::new();
perform_semantic_analysis_graph(&mut files, &config, &cache).unwrap();
let dog_file = files
.iter()
.find(|f| f.path.to_string_lossy().contains("dog.py"))
.unwrap();
let base_file = files
.iter()
.find(|f| f.path.to_string_lossy().contains("base.py"))
.unwrap();
assert!(
dog_file.imports.contains(&base_file.path),
"dog.py should import base.py"
);
assert!(
base_file.imported_by.contains(&dog_file.path),
"base.py should be imported by dog.py"
);
assert!(
!dog_file.type_references.is_empty() || !dog_file.imports.is_empty(),
"dog.py should have type references or imports for inheritance"
);
}
#[test]
fn test_mixed_dependency_types() {
let temp_dir = TempDir::new().unwrap();
let base_path = temp_dir.path();
fs::create_dir_all(base_path.join(".git")).unwrap();
fs::create_dir_all(base_path.join("src")).unwrap();
fs::write(
base_path.join("src/interfaces.ts"),
r#"
export interface Logger {
log(message: string): void;
}
export interface Config {
debug: boolean;
level: string;
}
"#,
)
.unwrap();
fs::write(
base_path.join("src/base-logger.ts"),
r#"
import { Logger, Config } from './interfaces';
export abstract class BaseLogger implements Logger {
protected config: Config;
constructor(config: Config) {
this.config = config;
}
abstract log(message: string): void;
}
"#,
)
.unwrap();
fs::write(
base_path.join("src/console-logger.ts"),
r#"
import { Config } from './interfaces';
import { BaseLogger } from './base-logger';
export class ConsoleLogger extends BaseLogger {
constructor(config: Config) {
super(config);
}
log(message: string): void {
if (this.config.debug) {
console.log(`[${this.config.level}] ${message}`);
}
}
}
"#,
)
.unwrap();
let walk_options = WalkOptions::default();
let mut files = walk_directory(base_path, walk_options).unwrap();
files.retain(|f| f.path.extension().is_some_and(|ext| ext == "ts"));
let config = Config {
trace_imports: true,
include_types: true,
..Default::default()
};
let cache = FileCache::new();
perform_semantic_analysis_graph(&mut files, &config, &cache).unwrap();
let base_logger = files
.iter()
.find(|f| f.path.to_string_lossy().contains("base-logger.ts"))
.unwrap();
let console_logger = files
.iter()
.find(|f| f.path.to_string_lossy().contains("console-logger.ts"))
.unwrap();
let interfaces = files
.iter()
.find(|f| f.path.to_string_lossy().contains("interfaces.ts"))
.unwrap();
assert!(
base_logger.imports.contains(&interfaces.path),
"base-logger.ts should import from interfaces.ts"
);
let base_type_refs: Vec<&str> = base_logger
.type_references
.iter()
.map(|tr| tr.name.as_str())
.collect();
assert!(
base_type_refs.contains(&"Logger") || base_type_refs.contains(&"Config"),
"base-logger.ts should reference Logger or Config types"
);
assert!(
console_logger.imports.contains(&base_logger.path),
"console-logger.ts should import from base-logger.ts"
);
assert!(
console_logger.imports.contains(&interfaces.path),
"console-logger.ts should import from interfaces.ts"
);
assert!(
!console_logger.imports.is_empty(),
"console-logger.ts should have imports"
);
}
#[test]
fn test_edge_type_querying() {
let temp_dir = TempDir::new().unwrap();
let base_path = temp_dir.path();
fs::create_dir_all(base_path.join(".git")).unwrap();
fs::create_dir_all(base_path.join("src")).unwrap();
fs::write(
base_path.join("src/models.rs"),
r#"
pub struct User {
pub name: String,
}
pub trait Validator {
fn validate(&self) -> bool;
}
"#,
)
.unwrap();
fs::write(
base_path.join("src/validators.rs"),
r#"
use crate::models::{User, Validator};
impl Validator for User {
fn validate(&self) -> bool {
!self.name.is_empty()
}
}
"#,
)
.unwrap();
fs::write(
base_path.join("src/main.rs"),
r#"
mod models;
mod validators;
use models::{User, Validator};
fn main() {
let user = User { name: "Alice".to_string() };
if user.validate() {
println!("Valid user");
}
}
"#,
)
.unwrap();
let walk_options = WalkOptions::default();
let mut files = walk_directory(base_path, walk_options).unwrap();
files.retain(|f| f.path.extension().is_some_and(|ext| ext == "rs"));
let config = Config {
trace_imports: true,
include_types: true,
include_callers: true,
..Default::default()
};
let cache = FileCache::new();
perform_semantic_analysis_graph(&mut files, &config, &cache).unwrap();
let main_file = files
.iter()
.find(|f| f.path.to_string_lossy().contains("main.rs"))
.unwrap();
let models_file = files
.iter()
.find(|f| f.path.to_string_lossy().contains("models.rs"))
.unwrap();
let validators_file = files
.iter()
.find(|f| f.path.to_string_lossy().contains("validators.rs"))
.unwrap();
assert!(!main_file.imports.is_empty(), "main.rs should have imports");
assert!(
!validators_file.imports.is_empty() || !models_file.imported_by.is_empty(),
"There should be import relationships between files"
);
let main_type_refs: Vec<&str> = main_file
.type_references
.iter()
.map(|tr| tr.name.as_str())
.collect();
assert!(
main_type_refs.contains(&"User") || !main_type_refs.is_empty(),
"main.rs should have type references"
);
let main_func_calls: Vec<&str> = main_file
.function_calls
.iter()
.map(|fc| fc.name.as_str())
.collect();
assert!(
main_func_calls.contains(&"validate") || !main_func_calls.is_empty(),
"main.rs should call validate method"
);
}
#[test]
fn test_edge_type_serialization() {
let temp_dir = TempDir::new().unwrap();
let base_path = temp_dir.path();
fs::create_dir_all(base_path.join(".git")).unwrap();
fs::write(
base_path.join("test.rs"),
r#"
fn main() {
println!("Hello");
}
"#,
)
.unwrap();
let walk_options = WalkOptions::default();
let mut files = walk_directory(base_path, walk_options).unwrap();
files.retain(|f| f.path.extension().is_some_and(|ext| ext == "rs"));
let config = Config {
trace_imports: true,
..Default::default()
};
let cache = FileCache::new();
assert!(
perform_semantic_analysis_graph(&mut files, &config, &cache).is_ok(),
"Semantic analysis should complete successfully"
);
}