ricecoder_execution/
step_action_handler.rs1use crate::error::{ExecutionError, ExecutionResult};
14use crate::file_operations::FileOperations;
15use std::process::Command;
16use tracing::{debug, error, info};
17
18pub struct CreateFileHandler;
20
21impl CreateFileHandler {
22 pub fn handle(path: &str, content: &str) -> ExecutionResult<()> {
31 debug!(path = %path, content_len = content.len(), "Creating file");
32
33 FileOperations::create_file(path, content)?;
35
36 info!(path = %path, "File created successfully");
37 Ok(())
38 }
39}
40
41pub struct ModifyFileHandler;
43
44impl ModifyFileHandler {
45 pub fn handle(path: &str, diff: &str) -> ExecutionResult<()> {
54 debug!(path = %path, diff_len = diff.len(), "Modifying file");
55
56 FileOperations::modify_file(path, diff)?;
58
59 info!(path = %path, "File modified successfully");
60 Ok(())
61 }
62}
63
64pub struct DeleteFileHandler;
66
67impl DeleteFileHandler {
68 pub fn handle(path: &str) -> ExecutionResult<()> {
76 debug!(path = %path, "Deleting file");
77
78 FileOperations::delete_file(path)?;
80
81 info!(path = %path, "File deleted successfully");
82 Ok(())
83 }
84}
85
86pub struct CommandHandler;
88
89impl CommandHandler {
90 pub fn handle(command: &str, args: &[String]) -> ExecutionResult<()> {
99 debug!(command = %command, args_count = args.len(), "Running command");
100
101 let mut cmd = Command::new(command);
103 cmd.args(args);
104
105 let output = cmd.output().map_err(|e| {
106 ExecutionError::StepFailed(format!("Failed to execute command {}: {}", command, e))
107 })?;
108
109 if !output.status.success() {
111 let stderr = String::from_utf8_lossy(&output.stderr);
112 let exit_code = output.status.code().unwrap_or(-1);
113 error!(
114 command = %command,
115 exit_code = exit_code,
116 stderr = %stderr,
117 "Command failed"
118 );
119 return Err(ExecutionError::StepFailed(format!(
120 "Command {} failed with exit code {}: {}",
121 command, exit_code, stderr
122 )));
123 }
124
125 let stdout = String::from_utf8_lossy(&output.stdout);
126 info!(
127 command = %command,
128 output_len = stdout.len(),
129 "Command executed successfully"
130 );
131
132 Ok(())
133 }
134}
135
136pub struct TestHandler;
138
139impl TestHandler {
140 pub fn handle(pattern: &Option<String>) -> ExecutionResult<()> {
148 debug!(pattern = ?pattern, "Running tests");
149
150 let framework = Self::detect_test_framework()?;
152
153 let (command, args) = Self::build_test_command(&framework, pattern)?;
155
156 CommandHandler::handle(&command, &args)?;
158
159 info!("Tests executed successfully");
160 Ok(())
161 }
162
163 fn detect_test_framework() -> ExecutionResult<TestFramework> {
165 let current_dir = std::env::current_dir().map_err(|e| {
166 ExecutionError::ValidationError(format!("Failed to get current dir: {}", e))
167 })?;
168
169 if current_dir.join("Cargo.toml").exists() {
171 debug!("Detected Rust project");
172 return Ok(TestFramework::Rust);
173 }
174
175 if current_dir.join("package.json").exists() {
177 debug!("Detected TypeScript/Node.js project");
178 return Ok(TestFramework::TypeScript);
179 }
180
181 if current_dir.join("pytest.ini").exists() || current_dir.join("setup.py").exists() {
183 debug!("Detected Python project");
184 return Ok(TestFramework::Python);
185 }
186
187 Err(ExecutionError::ValidationError(
188 "Could not detect test framework".to_string(),
189 ))
190 }
191
192 fn build_test_command(
194 framework: &TestFramework,
195 pattern: &Option<String>,
196 ) -> ExecutionResult<(String, Vec<String>)> {
197 match framework {
198 TestFramework::Rust => {
199 let mut args = vec!["test".to_string()];
200 if let Some(p) = pattern {
201 args.push(p.clone());
202 }
203 Ok(("cargo".to_string(), args))
204 }
205 TestFramework::TypeScript => {
206 let mut args = vec![];
207 if let Some(p) = pattern {
208 args.push(p.clone());
209 }
210 Ok(("npm".to_string(), args))
211 }
212 TestFramework::Python => {
213 let mut args = vec![];
214 if let Some(p) = pattern {
215 args.push(p.clone());
216 }
217 Ok(("pytest".to_string(), args))
218 }
219 }
220 }
221}
222
223#[derive(Debug, Clone, Copy, PartialEq, Eq)]
225enum TestFramework {
226 Rust,
228 TypeScript,
230 Python,
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use tempfile::TempDir;
238
239 #[test]
240 fn test_create_file_handler() {
241 let temp_dir = TempDir::new().unwrap();
242 let file_path = temp_dir.path().join("test.txt");
243 let path_str = file_path.to_string_lossy().to_string();
244
245 let result = CreateFileHandler::handle(&path_str, "test content");
246 assert!(result.is_ok());
247 assert!(file_path.exists());
248
249 let content = std::fs::read_to_string(&file_path).unwrap();
250 assert_eq!(content, "test content");
251 }
252
253 #[test]
254 fn test_create_file_with_parent_dirs() {
255 let temp_dir = TempDir::new().unwrap();
256 let file_path = temp_dir.path().join("subdir/nested/test.txt");
257 let path_str = file_path.to_string_lossy().to_string();
258
259 let result = CreateFileHandler::handle(&path_str, "nested content");
260 assert!(result.is_ok());
261 assert!(file_path.exists());
262 }
263
264 #[test]
265 fn test_delete_file_handler() {
266 let temp_dir = TempDir::new().unwrap();
267 let file_path = temp_dir.path().join("test.txt");
268 let path_str = file_path.to_string_lossy().to_string();
269
270 std::fs::write(&file_path, "content").unwrap();
272 assert!(file_path.exists());
273
274 let result = DeleteFileHandler::handle(&path_str);
276 assert!(result.is_ok());
277 assert!(!file_path.exists());
278 }
279
280 #[test]
281 fn test_delete_nonexistent_file() {
282 let result = DeleteFileHandler::handle("/nonexistent/path/file.txt");
283 assert!(result.is_err());
284 }
285
286 #[test]
287 fn test_modify_file_handler() {
288 let temp_dir = TempDir::new().unwrap();
289 let file_path = temp_dir.path().join("test.txt");
290 let path_str = file_path.to_string_lossy().to_string();
291
292 std::fs::write(&file_path, "original content").unwrap();
294
295 let result = ModifyFileHandler::handle(&path_str, "some diff");
297 assert!(result.is_ok());
298 }
299
300 #[test]
301 fn test_modify_nonexistent_file() {
302 let result = ModifyFileHandler::handle("/nonexistent/path/file.txt", "diff");
303 assert!(result.is_err());
304 }
305
306 #[test]
307 fn test_modify_with_empty_diff() {
308 let temp_dir = TempDir::new().unwrap();
309 let file_path = temp_dir.path().join("test.txt");
310 let path_str = file_path.to_string_lossy().to_string();
311
312 std::fs::write(&file_path, "content").unwrap();
313
314 let result = ModifyFileHandler::handle(&path_str, "");
315 assert!(result.is_err());
316 }
317
318 #[test]
319 fn test_command_handler_success() {
320 let result = CommandHandler::handle("echo", &["hello".to_string()]);
321 assert!(result.is_ok());
322 }
323
324 #[test]
325 fn test_command_handler_failure() {
326 let result = CommandHandler::handle("false", &[]);
327 assert!(result.is_err());
328 }
329
330 #[test]
331 fn test_command_handler_nonexistent() {
332 let result = CommandHandler::handle("nonexistent_command_xyz", &[]);
333 assert!(result.is_err());
334 }
335}