use crate::error::{ExecutionError, ExecutionResult};
use crate::file_operations::FileOperations;
use std::process::Command;
use tracing::{debug, error, info};
pub struct CreateFileHandler;
impl CreateFileHandler {
pub fn handle(path: &str, content: &str) -> ExecutionResult<()> {
debug!(path = %path, content_len = content.len(), "Creating file");
FileOperations::create_file(path, content)?;
info!(path = %path, "File created successfully");
Ok(())
}
}
pub struct ModifyFileHandler;
impl ModifyFileHandler {
pub fn handle(path: &str, diff: &str) -> ExecutionResult<()> {
debug!(path = %path, diff_len = diff.len(), "Modifying file");
FileOperations::modify_file(path, diff)?;
info!(path = %path, "File modified successfully");
Ok(())
}
}
pub struct DeleteFileHandler;
impl DeleteFileHandler {
pub fn handle(path: &str) -> ExecutionResult<()> {
debug!(path = %path, "Deleting file");
FileOperations::delete_file(path)?;
info!(path = %path, "File deleted successfully");
Ok(())
}
}
pub struct CommandHandler;
impl CommandHandler {
pub fn handle(command: &str, args: &[String]) -> ExecutionResult<()> {
debug!(command = %command, args_count = args.len(), "Running command");
let mut cmd = Command::new(command);
cmd.args(args);
let output = cmd.output().map_err(|e| {
ExecutionError::StepFailed(format!("Failed to execute command {}: {}", command, e))
})?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
let exit_code = output.status.code().unwrap_or(-1);
error!(
command = %command,
exit_code = exit_code,
stderr = %stderr,
"Command failed"
);
return Err(ExecutionError::StepFailed(format!(
"Command {} failed with exit code {}: {}",
command, exit_code, stderr
)));
}
let stdout = String::from_utf8_lossy(&output.stdout);
info!(
command = %command,
output_len = stdout.len(),
"Command executed successfully"
);
Ok(())
}
}
pub struct TestHandler;
impl TestHandler {
pub fn handle(pattern: &Option<String>) -> ExecutionResult<()> {
debug!(pattern = ?pattern, "Running tests");
let framework = Self::detect_test_framework()?;
let (command, args) = Self::build_test_command(&framework, pattern)?;
CommandHandler::handle(&command, &args)?;
info!("Tests executed successfully");
Ok(())
}
fn detect_test_framework() -> ExecutionResult<TestFramework> {
let current_dir = std::env::current_dir().map_err(|e| {
ExecutionError::ValidationError(format!("Failed to get current dir: {}", e))
})?;
if current_dir.join("Cargo.toml").exists() {
debug!("Detected Rust project");
return Ok(TestFramework::Rust);
}
if current_dir.join("package.json").exists() {
debug!("Detected TypeScript/Node.js project");
return Ok(TestFramework::TypeScript);
}
if current_dir.join("pytest.ini").exists() || current_dir.join("setup.py").exists() {
debug!("Detected Python project");
return Ok(TestFramework::Python);
}
Err(ExecutionError::ValidationError(
"Could not detect test framework".to_string(),
))
}
fn build_test_command(
framework: &TestFramework,
pattern: &Option<String>,
) -> ExecutionResult<(String, Vec<String>)> {
match framework {
TestFramework::Rust => {
let mut args = vec!["test".to_string()];
if let Some(p) = pattern {
args.push(p.clone());
}
Ok(("cargo".to_string(), args))
}
TestFramework::TypeScript => {
let mut args = vec![];
if let Some(p) = pattern {
args.push(p.clone());
}
Ok(("npm".to_string(), args))
}
TestFramework::Python => {
let mut args = vec![];
if let Some(p) = pattern {
args.push(p.clone());
}
Ok(("pytest".to_string(), args))
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TestFramework {
Rust,
TypeScript,
Python,
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_create_file_handler() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
let path_str = file_path.to_string_lossy().to_string();
let result = CreateFileHandler::handle(&path_str, "test content");
assert!(result.is_ok());
assert!(file_path.exists());
let content = std::fs::read_to_string(&file_path).unwrap();
assert_eq!(content, "test content");
}
#[test]
fn test_create_file_with_parent_dirs() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("subdir/nested/test.txt");
let path_str = file_path.to_string_lossy().to_string();
let result = CreateFileHandler::handle(&path_str, "nested content");
assert!(result.is_ok());
assert!(file_path.exists());
}
#[test]
fn test_delete_file_handler() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
let path_str = file_path.to_string_lossy().to_string();
std::fs::write(&file_path, "content").unwrap();
assert!(file_path.exists());
let result = DeleteFileHandler::handle(&path_str);
assert!(result.is_ok());
assert!(!file_path.exists());
}
#[test]
fn test_delete_nonexistent_file() {
let result = DeleteFileHandler::handle("/nonexistent/path/file.txt");
assert!(result.is_err());
}
#[test]
fn test_modify_file_handler() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
let path_str = file_path.to_string_lossy().to_string();
std::fs::write(&file_path, "original content").unwrap();
let result = ModifyFileHandler::handle(&path_str, "some diff");
assert!(result.is_ok());
}
#[test]
fn test_modify_nonexistent_file() {
let result = ModifyFileHandler::handle("/nonexistent/path/file.txt", "diff");
assert!(result.is_err());
}
#[test]
fn test_modify_with_empty_diff() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
let path_str = file_path.to_string_lossy().to_string();
std::fs::write(&file_path, "content").unwrap();
let result = ModifyFileHandler::handle(&path_str, "");
assert!(result.is_err());
}
#[test]
fn test_command_handler_success() {
let result = CommandHandler::handle("echo", &["hello".to_string()]);
assert!(result.is_ok());
}
#[test]
fn test_command_handler_failure() {
let result = CommandHandler::handle("false", &[]);
assert!(result.is_err());
}
#[test]
fn test_command_handler_nonexistent() {
let result = CommandHandler::handle("nonexistent_command_xyz", &[]);
assert!(result.is_err());
}
}