use assert_cmd::Command;
use std::fs;
use std::path::Path;
use tempfile::TempDir;
fn create_file(base: &Path, path: &str, content: &str) {
let file_path = base.join(path);
if let Some(parent) = file_path.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(file_path, content).unwrap();
}
fn list_files_recursively(dir: &Path, indent: &str) {
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
eprintln!("{}{}/", indent, path.file_name().unwrap().to_string_lossy());
list_files_recursively(&path, &format!("{indent} "));
} else {
eprintln!("{}{}", indent, path.file_name().unwrap().to_string_lossy());
}
}
}
}
#[test]
fn test_cli_include_callers_cross_module() {
let temp_dir = TempDir::new().unwrap();
let root = temp_dir.path();
fs::create_dir_all(root.join(".git")).unwrap();
create_file(
root,
"core/src/commands/rule/rule.rs",
r#"
//! Rule processing module
/// Calculate a value based on the given rule
pub fn calculate(value: i32, rule: &str) -> i32 {
match rule {
"double" => value * 2,
"square" => value * value,
"increment" => value + 1,
_ => value,
}
}
/// Apply a rule to a list of values
pub fn apply_rule(values: &[i32], rule: &str) -> Vec<i32> {
values.iter().map(|v| calculate(*v, rule)).collect()
}
"#,
);
create_file(
root,
"core/src/commands/rule/mod.rs",
r#"
pub mod rule;
pub use rule::{calculate, apply_rule};
"#,
);
create_file(
root,
"balances/src/account.rs",
r#"
//! Account balance management
pub struct Account {
balance: i32,
}
impl Account {
pub fn new(initial_balance: i32) -> Self {
Account { balance: initial_balance }
}
pub fn apply_interest(&mut self, interest_rule: &str) {
// Direct function call to calculate
self.balance = calculate(self.balance, interest_rule);
}
pub fn get_balance(&self) -> i32 {
self.balance
}
}
// Test function that directly calls calculate
pub fn test_calculation() {
let result = calculate(42, "double");
println!("Result: {}", result);
}
"#,
);
create_file(
root,
"utils/src/logger.rs",
r#"
//! Logging utilities
pub fn log_message(msg: &str) {
println!("[LOG] {}", msg);
}
"#,
);
let mut cmd = Command::cargo_bin("context-creator").unwrap();
let output = cmd
.current_dir(root)
.args([
"--include",
"core/src/commands/rule/*.rs",
"--include-callers",
"--enhanced-context", ])
.output()
.expect("Failed to execute command");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
eprintln!("STDOUT:\n{stdout}");
eprintln!("STDERR:\n{stderr}");
eprintln!("\nFiles in test directory:");
list_files_recursively(root, "");
assert!(
output.status.success(),
"Command failed with exit code: {}",
output.status
);
assert!(stdout.contains("rule.rs"), "Output should contain rule.rs");
assert!(
stdout.contains("account.rs"),
"Output should contain account.rs as it calls calculate()"
);
assert!(
!stdout.contains("logger.rs"),
"Output should not contain logger.rs as it doesn't call functions from rule.rs"
);
}
#[test]
fn test_cli_include_callers_with_multiple_callers() {
let temp_dir = TempDir::new().unwrap();
let root = temp_dir.path();
fs::create_dir_all(root.join(".git")).unwrap();
create_file(
root,
"shared/validation.rs",
r#"
//! Validation utilities
pub fn validate_email(email: &str) -> bool {
email.contains('@') && email.contains('.')
}
pub fn validate_phone(phone: &str) -> bool {
phone.len() >= 10 && phone.chars().all(|c| c.is_numeric())
}
"#,
);
create_file(
root,
"api/user.rs",
r#"
use crate::shared::validation::validate_email;
pub fn create_user(email: &str, name: &str) -> Result<(), String> {
if !validate_email(email) {
return Err("Invalid email".to_string());
}
Ok(())
}
"#,
);
create_file(
root,
"cli/commands.rs",
r#"
use crate::shared::validation::{validate_email, validate_phone};
pub fn register_command(email: &str, phone: &str) {
if validate_email(email) && validate_phone(phone) {
println!("Registration successful");
}
}
"#,
);
create_file(
root,
"tests/validation_tests.rs",
r#"
use crate::shared::validation::validate_email;
#[cfg(test)]
mod tests {
use crate::shared::validation::validate_phone;
#[test]
fn test_email_validation() {
let is_valid = super::validate_email("test@example.com");
assert!(is_valid);
}
#[test]
fn test_phone_validation() {
let is_valid = validate_phone("1234567890");
assert!(is_valid);
}
}
// Direct function call for testing
pub fn check_email(email: &str) -> bool {
validate_email(email)
}
"#,
);
let mut cmd = Command::cargo_bin("context-creator").unwrap();
let output = cmd
.current_dir(root)
.args(["--include", "shared/validation.rs", "--include-callers"])
.output()
.expect("Failed to execute command");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("validation.rs"));
assert!(stdout.contains("user.rs"));
assert!(stdout.contains("commands.rs"));
assert!(stdout.contains("validation_tests.rs"));
}
#[test]
fn test_cli_include_callers_depth_limiting() {
let temp_dir = TempDir::new().unwrap();
let root = temp_dir.path();
fs::create_dir_all(root.join(".git")).unwrap();
create_file(
root,
"core.rs",
r#"
pub fn core_function() -> i32 {
42
}
"#,
);
create_file(
root,
"middle.rs",
r#"
use crate::core::core_function;
pub fn middle_function() -> i32 {
core_function() * 2
}
"#,
);
create_file(
root,
"outer.rs",
r#"
use crate::middle::middle_function;
pub fn outer_function() -> i32 {
middle_function() + 10
}
"#,
);
let mut cmd = Command::cargo_bin("context-creator").unwrap();
let output = cmd
.current_dir(root)
.args([
"--include",
"core.rs",
"--include-callers",
"--semantic-depth",
"1",
])
.output()
.expect("Failed to execute command");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("core.rs"));
assert!(stdout.contains("middle.rs"));
assert!(!stdout.contains("outer.rs"));
}