docker_wrapper/compose/
exec.rs

1//! Docker Compose exec command implementation.
2
3use super::{execute_compose_command, ComposeCommand, ComposeConfig, ComposeOutput};
4use crate::error::Result;
5use async_trait::async_trait;
6
7/// Docker Compose exec command builder
8#[derive(Debug, Clone, Default)]
9#[allow(dead_code)] // Stub implementation
10pub struct ComposeExecCommand {
11    config: ComposeConfig,
12    service: String,
13    command: Vec<String>,
14    detach: bool,
15    user: Option<String>,
16    workdir: Option<String>,
17}
18
19impl ComposeExecCommand {
20    /// Create a new compose exec command
21    #[must_use]
22    pub fn new(service: impl Into<String>) -> Self {
23        Self {
24            service: service.into(),
25            ..Self::default()
26        }
27    }
28
29    /// Execute the exec command
30    ///
31    /// # Errors
32    ///
33    /// Returns an error if the docker compose exec command fails
34    pub async fn run(&self) -> Result<ComposeOutput> {
35        self.execute().await
36    }
37}
38
39#[async_trait]
40impl ComposeCommand for ComposeExecCommand {
41    type Output = ComposeOutput;
42
43    fn subcommand(&self) -> &'static str {
44        "exec"
45    }
46
47    fn build_args(&self) -> Vec<String> {
48        let mut args = vec![self.service.clone()];
49        args.extend(self.command.clone());
50        args
51    }
52
53    async fn execute(&self) -> Result<Self::Output> {
54        execute_compose_command(&self.config, self.subcommand(), self.build_args()).await
55    }
56
57    fn config(&self) -> &ComposeConfig {
58        &self.config
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn test_compose_exec_basic() {
68        let cmd = ComposeExecCommand::new("web");
69        assert_eq!(cmd.service, "web");
70        assert_eq!(cmd.subcommand(), "exec");
71        let args = cmd.build_args();
72        assert_eq!(args, vec!["web"]);
73    }
74
75    #[test]
76    fn test_compose_exec_with_command() {
77        let mut cmd = ComposeExecCommand::new("db");
78        cmd.command = vec!["psql".to_string(), "-U".to_string(), "postgres".to_string()];
79
80        let args = cmd.build_args();
81        assert_eq!(args, vec!["db", "psql", "-U", "postgres"]);
82    }
83
84    #[test]
85    fn test_compose_exec_with_config() {
86        let config = ComposeConfig::new()
87            .file("docker-compose.yml")
88            .project_name("myapp");
89
90        let mut cmd = ComposeExecCommand::new("web");
91        cmd.config = config;
92        cmd.command = vec!["bash".to_string()];
93
94        assert_eq!(cmd.config().project_name, Some("myapp".to_string()));
95        assert_eq!(cmd.build_args(), vec!["web", "bash"]);
96    }
97
98    #[test]
99    fn test_compose_exec_future_options() {
100        // Test that fields exist for future implementation
101        let cmd = ComposeExecCommand {
102            config: ComposeConfig::new(),
103            service: "app".to_string(),
104            command: vec!["sh".to_string()],
105            detach: true,
106            user: Some("root".to_string()),
107            workdir: Some("/app".to_string()),
108        };
109
110        // Currently only includes service and command
111        let args = cmd.build_args();
112        assert_eq!(args, vec!["app", "sh"]);
113
114        // When fully implemented, it should include options:
115        // assert!(args.contains(&"--detach".to_string()));
116        // assert!(args.contains(&"--user".to_string()));
117        // assert!(args.contains(&"root".to_string()));
118        // assert!(args.contains(&"--workdir".to_string()));
119        // assert!(args.contains(&"/app".to_string()));
120    }
121}