docker_wrapper/command/
wait.rs1use super::{CommandExecutor, CommandOutput, DockerCommand};
6use crate::error::Result;
7use async_trait::async_trait;
8
9#[derive(Debug, Clone)]
34pub struct WaitCommand {
35 containers: Vec<String>,
37 pub executor: CommandExecutor,
39}
40
41impl WaitCommand {
42 #[must_use]
52 pub fn new(container: impl Into<String>) -> Self {
53 Self {
54 containers: vec![container.into()],
55 executor: CommandExecutor::new(),
56 }
57 }
58
59 #[must_use]
69 pub fn new_multiple(containers: Vec<impl Into<String>>) -> Self {
70 Self {
71 containers: containers.into_iter().map(Into::into).collect(),
72 executor: CommandExecutor::new(),
73 }
74 }
75
76 #[must_use]
78 pub fn container(mut self, container: impl Into<String>) -> Self {
79 self.containers.push(container.into());
80 self
81 }
82
83 pub async fn run(&self) -> Result<WaitResult> {
107 let output = self.execute().await?;
108
109 let exit_codes = Self::parse_exit_codes(&output.stdout);
111
112 Ok(WaitResult {
113 output,
114 containers: self.containers.clone(),
115 exit_codes,
116 })
117 }
118
119 fn parse_exit_codes(stdout: &str) -> Vec<i32> {
121 stdout
122 .lines()
123 .filter_map(|line| line.trim().parse().ok())
124 .collect()
125 }
126}
127
128#[async_trait]
129impl DockerCommand for WaitCommand {
130 type Output = CommandOutput;
131
132 fn get_executor(&self) -> &CommandExecutor {
133 &self.executor
134 }
135
136 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
137 &mut self.executor
138 }
139
140 fn build_command_args(&self) -> Vec<String> {
141 let mut args = vec!["wait".to_string()];
142 args.extend(self.containers.clone());
143 args.extend(self.executor.raw_args.clone());
144 args
145 }
146
147 async fn execute(&self) -> Result<Self::Output> {
148 if self.containers.is_empty() {
149 return Err(crate::error::Error::invalid_config(
150 "No containers specified for waiting",
151 ));
152 }
153
154 let args = self.build_command_args();
155 let command_name = args[0].clone();
156 let command_args = args[1..].to_vec();
157 self.executor
158 .execute_command(&command_name, command_args)
159 .await
160 }
161}
162
163#[derive(Debug, Clone)]
165pub struct WaitResult {
166 pub output: CommandOutput,
168 pub containers: Vec<String>,
170 pub exit_codes: Vec<i32>,
172}
173
174impl WaitResult {
175 #[must_use]
177 pub fn success(&self) -> bool {
178 self.output.success
179 }
180
181 #[must_use]
183 pub fn containers(&self) -> &[String] {
184 &self.containers
185 }
186
187 #[must_use]
189 pub fn exit_codes(&self) -> &[i32] {
190 &self.exit_codes
191 }
192
193 #[must_use]
195 pub fn all_successful(&self) -> bool {
196 self.exit_codes.iter().all(|&code| code == 0)
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn test_wait_single_container() {
206 let cmd = WaitCommand::new("test-container");
207 let args = cmd.build_command_args();
208 assert_eq!(args, vec!["wait", "test-container"]);
209 }
210
211 #[test]
212 fn test_wait_multiple_containers() {
213 let cmd = WaitCommand::new_multiple(vec!["web", "db", "cache"]);
214 let args = cmd.build_command_args();
215 assert_eq!(args, vec!["wait", "web", "db", "cache"]);
216 }
217
218 #[test]
219 fn test_wait_add_container() {
220 let cmd = WaitCommand::new("web").container("db").container("cache");
221 let args = cmd.build_command_args();
222 assert_eq!(args, vec!["wait", "web", "db", "cache"]);
223 }
224
225 #[test]
226 fn test_parse_exit_codes() {
227 let output = "0\n1\n130";
228 let codes = WaitCommand::parse_exit_codes(output);
229 assert_eq!(codes, vec![0, 1, 130]);
230 }
231
232 #[test]
233 fn test_parse_exit_codes_empty() {
234 let codes = WaitCommand::parse_exit_codes("");
235 assert!(codes.is_empty());
236 }
237
238 #[test]
239 fn test_all_successful() {
240 let result = WaitResult {
241 output: CommandOutput {
242 stdout: "0\n0".to_string(),
243 stderr: String::new(),
244 exit_code: 0,
245 success: true,
246 },
247 containers: vec!["web".to_string(), "db".to_string()],
248 exit_codes: vec![0, 0],
249 };
250 assert!(result.all_successful());
251
252 let result_with_failure = WaitResult {
253 output: CommandOutput {
254 stdout: "0\n1".to_string(),
255 stderr: String::new(),
256 exit_code: 0,
257 success: true,
258 },
259 containers: vec!["web".to_string(), "db".to_string()],
260 exit_codes: vec![0, 1],
261 };
262 assert!(!result_with_failure.all_successful());
263 }
264}