1use crate::error::{WorkflowError, WorkflowResult};
6use crate::models::{CommandStep, Workflow, WorkflowState};
7use crate::state::StateManager;
8use std::time::Instant;
9
10pub struct CommandExecutor;
12
13impl CommandExecutor {
14 pub fn execute_command_step(
30 _workflow: &Workflow,
31 state: &mut WorkflowState,
32 step_id: &str,
33 command_step: &CommandStep,
34 ) -> WorkflowResult<()> {
35 StateManager::start_step(state, step_id.to_string());
37
38 let start_time = Instant::now();
39
40 let command_output = Self::execute_command_internal(command_step)?;
49
50 let duration_ms = start_time.elapsed().as_millis() as u64;
51
52 StateManager::complete_step(
54 state,
55 step_id.to_string(),
56 Some(command_output),
57 duration_ms,
58 );
59
60 Ok(())
61 }
62
63 fn execute_command_internal(command_step: &CommandStep) -> WorkflowResult<serde_json::Value> {
67 Ok(serde_json::json!({
76 "command": command_step.command,
77 "args": command_step.args,
78 "exit_code": 0,
79 "stdout": "Command executed successfully",
80 "stderr": ""
81 }))
82 }
83
84 pub fn execute_command_step_with_timeout(
102 _workflow: &Workflow,
103 state: &mut WorkflowState,
104 step_id: &str,
105 command_step: &CommandStep,
106 timeout_ms: u64,
107 ) -> WorkflowResult<()> {
108 StateManager::start_step(state, step_id.to_string());
110
111 let start_time = Instant::now();
112
113 let command_output = Self::execute_command_internal(command_step)?;
116
117 let elapsed_ms = start_time.elapsed().as_millis() as u64;
118
119 if elapsed_ms > timeout_ms {
121 StateManager::fail_step(
122 state,
123 step_id.to_string(),
124 format!("Command execution timed out after {}ms", timeout_ms),
125 elapsed_ms,
126 );
127 return Err(WorkflowError::StepFailed(format!(
128 "Command step {} timed out after {}ms",
129 step_id, timeout_ms
130 )));
131 }
132
133 StateManager::complete_step(state, step_id.to_string(), Some(command_output), elapsed_ms);
135
136 Ok(())
137 }
138
139 pub fn get_command(command_step: &CommandStep) -> &str {
141 &command_step.command
142 }
143
144 pub fn get_args(command_step: &CommandStep) -> &[String] {
146 &command_step.args
147 }
148
149 pub fn get_timeout(command_step: &CommandStep) -> u64 {
151 command_step.timeout
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use crate::models::{
159 ErrorAction, RiskFactors, StepConfig, StepStatus, StepType, WorkflowConfig, WorkflowStep,
160 };
161
162 fn create_workflow_with_command_step() -> Workflow {
163 Workflow {
164 id: "test-workflow".to_string(),
165 name: "Test Workflow".to_string(),
166 description: "A test workflow".to_string(),
167 parameters: vec![],
168 steps: vec![WorkflowStep {
169 id: "command-step".to_string(),
170 name: "Command Step".to_string(),
171 step_type: StepType::Command(CommandStep {
172 command: "echo".to_string(),
173 args: vec!["hello".to_string()],
174 timeout: 5000,
175 }),
176 config: StepConfig {
177 config: serde_json::json!({}),
178 },
179 dependencies: vec![],
180 approval_required: false,
181 on_error: ErrorAction::Fail,
182 risk_score: None,
183 risk_factors: RiskFactors::default(),
184 }],
185 config: WorkflowConfig {
186 timeout_ms: None,
187 max_parallel: None,
188 },
189 }
190 }
191
192 #[test]
193 fn test_execute_command_step() {
194 let workflow = create_workflow_with_command_step();
195 let mut state = StateManager::create_state(&workflow);
196 let command_step = CommandStep {
197 command: "echo".to_string(),
198 args: vec!["hello".to_string()],
199 timeout: 5000,
200 };
201
202 let result = CommandExecutor::execute_command_step(
203 &workflow,
204 &mut state,
205 "command-step",
206 &command_step,
207 );
208 assert!(result.is_ok());
209
210 let step_result = state.step_results.get("command-step");
212 assert!(step_result.is_some());
213 assert_eq!(step_result.unwrap().status, StepStatus::Completed);
214 }
215
216 #[test]
217 fn test_execute_command_step_with_timeout() {
218 let workflow = create_workflow_with_command_step();
219 let mut state = StateManager::create_state(&workflow);
220 let command_step = CommandStep {
221 command: "echo".to_string(),
222 args: vec!["hello".to_string()],
223 timeout: 5000,
224 };
225
226 let result = CommandExecutor::execute_command_step_with_timeout(
227 &workflow,
228 &mut state,
229 "command-step",
230 &command_step,
231 10000, );
233 assert!(result.is_ok());
234
235 let step_result = state.step_results.get("command-step");
237 assert!(step_result.is_some());
238 assert_eq!(step_result.unwrap().status, StepStatus::Completed);
239 }
240
241 #[test]
242 fn test_get_command() {
243 let command_step = CommandStep {
244 command: "ls".to_string(),
245 args: vec!["-la".to_string()],
246 timeout: 5000,
247 };
248
249 assert_eq!(CommandExecutor::get_command(&command_step), "ls");
250 }
251
252 #[test]
253 fn test_get_args() {
254 let command_step = CommandStep {
255 command: "ls".to_string(),
256 args: vec!["-la".to_string(), "-h".to_string()],
257 timeout: 5000,
258 };
259
260 assert_eq!(
261 CommandExecutor::get_args(&command_step),
262 &["-la".to_string(), "-h".to_string()]
263 );
264 }
265
266 #[test]
267 fn test_get_timeout() {
268 let command_step = CommandStep {
269 command: "ls".to_string(),
270 args: vec![],
271 timeout: 3000,
272 };
273
274 assert_eq!(CommandExecutor::get_timeout(&command_step), 3000);
275 }
276}