Skip to main content

atlas_local/client/
get_deployment_id.rs

1use crate::{
2    Client,
3    client::{get_deployment::GetDeploymentError, get_mongodb_secret::get_mongodb_secret},
4    docker::{DockerInspectContainer, RunCommandInContainer, RunCommandInContainerError},
5};
6
7#[derive(Debug, thiserror::Error)]
8pub enum GetDeploymentIdError {
9    #[error("Failed to get deployment: {0}")]
10    GetDeployment(#[from] GetDeploymentError),
11    #[error("Failed to get MongoDB username: {0}")]
12    GetMongodbUsername(RunCommandInContainerError),
13    #[error("Failed to get MongoDB password: {0}")]
14    GetMongodbPassword(RunCommandInContainerError),
15    #[error("Failed to run mongosh command: {0}")]
16    RunMongoshCommand(RunCommandInContainerError),
17    #[error("Deployment ID is empty")]
18    DeploymentIdEmpty,
19}
20
21impl<D: DockerInspectContainer + RunCommandInContainer> Client<D> {
22    /// Gets the Atlas deployment ID for a local Atlas deployment.
23    pub async fn get_deployment_id(
24        &self,
25        cluster_id_or_name: &str,
26    ) -> Result<String, GetDeploymentIdError> {
27        let deployment = self.get_deployment(cluster_id_or_name).await?;
28
29        // Try to get the MongoDB root username
30        let mongodb_root_username = get_mongodb_secret(
31            self.docker.as_ref(),
32            &deployment,
33            |d| d.mongodb_initdb_root_username.as_deref(),
34            |d| d.mongodb_initdb_root_username_file.as_deref(),
35        )
36        .await
37        .map_err(GetDeploymentIdError::GetMongodbUsername)?;
38
39        // Try to get the MongoDB root password
40        let mongodb_root_password = get_mongodb_secret(
41            self.docker.as_ref(),
42            &deployment,
43            |d| d.mongodb_initdb_root_password.as_deref(),
44            |d| d.mongodb_initdb_root_password_file.as_deref(),
45        )
46        .await
47        .map_err(GetDeploymentIdError::GetMongodbPassword)?;
48
49        // Build the mongosh command
50        let mut mongosh_command = vec![
51            "mongosh".to_string(),
52            "mongodb://127.0.0.1:27017/?directConnection=true".to_string(),
53        ];
54        if let Some(username) = mongodb_root_username {
55            mongosh_command.push(format!("--username={}", username));
56        }
57        if let Some(password) = mongodb_root_password {
58            mongosh_command.push(format!("--password={}", password));
59        }
60
61        mongosh_command.push("--eval".to_string());
62        mongosh_command.push("db.getSiblingDB('admin').atlascli.findOne()?.uuid".to_string());
63        mongosh_command.push("--quiet".to_string());
64
65        // Run the mongosh command
66        let command_output = self
67            .docker
68            .run_command_in_container(&deployment.container_id, mongosh_command)
69            .await
70            .map_err(GetDeploymentIdError::RunMongoshCommand)?;
71
72        match command_output.stdout.into_iter().next() {
73            Some(line) if line.is_empty() => Err(GetDeploymentIdError::DeploymentIdEmpty),
74            Some(line) => Ok(line),
75            None => Err(GetDeploymentIdError::DeploymentIdEmpty),
76        }
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use crate::{client::get_deployment::GetDeploymentError, docker::CommandOutput};
84    use bollard::{
85        errors::Error as BollardError,
86        query_parameters::InspectContainerOptions,
87        secret::{
88            ContainerConfig, ContainerInspectResponse, ContainerState, ContainerStateStatusEnum,
89        },
90    };
91    use maplit::hashmap;
92    use mockall::{mock, predicate::eq};
93
94    mock! {
95        Docker {}
96
97        impl DockerInspectContainer for Docker {
98            async fn inspect_container(
99                &self,
100                container_id: &str,
101                options: Option<InspectContainerOptions>,
102            ) -> Result<ContainerInspectResponse, BollardError>;
103        }
104
105        impl RunCommandInContainer for Docker {
106            async fn run_command_in_container(
107                &self,
108                container_id: &str,
109                command: Vec<String>,
110            ) -> Result<CommandOutput, RunCommandInContainerError>;
111        }
112    }
113
114    // Helper function to create a test container inspect response
115    fn create_test_container_inspect_response() -> ContainerInspectResponse {
116        ContainerInspectResponse {
117            id: Some("test_container_id".to_string()),
118            name: Some("/test-deployment".to_string()),
119            config: Some(ContainerConfig {
120                labels: Some(hashmap! {
121                    "mongodb-atlas-local".to_string() => "container".to_string(),
122                    "version".to_string() => "8.0.0".to_string(),
123                    "mongodb-type".to_string() => "community".to_string(),
124                }),
125                env: Some(vec!["TOOL=ATLASCLI".to_string()]),
126                ..Default::default()
127            }),
128            state: Some(ContainerState {
129                status: Some(ContainerStateStatusEnum::RUNNING),
130                ..Default::default()
131            }),
132            ..Default::default()
133        }
134    }
135
136    #[tokio::test]
137    async fn test_get_deployment_id_happy_path_no_auth() {
138        // Arrange
139        let mut mock_docker = MockDocker::new();
140        let container_inspect_response = create_test_container_inspect_response();
141
142        // Mock get_deployment call
143        mock_docker
144            .expect_inspect_container()
145            .with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
146            .times(1)
147            .returning(move |_, _| Ok(container_inspect_response.clone()));
148
149        // Mock mongosh command execution
150        mock_docker
151            .expect_run_command_in_container()
152            .with(
153                eq("test_container_id"),
154                eq(vec![
155                    "mongosh".to_string(),
156                    "mongodb://127.0.0.1:27017/?directConnection=true".to_string(),
157                    "--eval".to_string(),
158                    "db.getSiblingDB('admin').atlascli.findOne()?.uuid".to_string(),
159                    "--quiet".to_string(),
160                ]),
161            )
162            .times(1)
163            .returning(|_, _| {
164                Ok(CommandOutput {
165                    stdout: vec!["deployment-uuid-123".to_string()],
166                    stderr: vec![],
167                })
168            });
169
170        let client = Client::new(mock_docker);
171
172        // Act
173        let result = client.get_deployment_id("test-deployment").await;
174
175        // Assert
176        assert!(result.is_ok());
177        assert_eq!(result.unwrap(), "deployment-uuid-123");
178    }
179
180    #[tokio::test]
181    async fn test_get_deployment_id_happy_path_env_auth() {
182        // Arrange
183        let mut mock_docker = MockDocker::new();
184        let mut container_inspect_response = create_test_container_inspect_response();
185
186        // Add username and password environment variables
187        if let Some(config) = container_inspect_response.config.as_mut()
188            && let Some(env) = config.env.as_mut()
189        {
190            env.push("MONGODB_INITDB_ROOT_USERNAME=testuser".to_string());
191            env.push("MONGODB_INITDB_ROOT_PASSWORD=testpass".to_string());
192        }
193
194        // Mock get_deployment call
195        mock_docker
196            .expect_inspect_container()
197            .with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
198            .times(1)
199            .returning(move |_, _| Ok(container_inspect_response.clone()));
200
201        // Mock mongosh command execution with auth
202        mock_docker
203            .expect_run_command_in_container()
204            .with(
205                eq("test_container_id"),
206                eq(vec![
207                    "mongosh".to_string(),
208                    "mongodb://127.0.0.1:27017/?directConnection=true".to_string(),
209                    "--username=testuser".to_string(),
210                    "--password=testpass".to_string(),
211                    "--eval".to_string(),
212                    "db.getSiblingDB('admin').atlascli.findOne()?.uuid".to_string(),
213                    "--quiet".to_string(),
214                ]),
215            )
216            .times(1)
217            .returning(|_, _| {
218                Ok(CommandOutput {
219                    stdout: vec!["deployment-uuid-456".to_string()],
220                    stderr: vec![],
221                })
222            });
223
224        let client = Client::new(mock_docker);
225
226        // Act
227        let result = client.get_deployment_id("test-deployment").await;
228
229        // Assert
230        assert!(result.is_ok());
231        assert_eq!(result.unwrap(), "deployment-uuid-456");
232    }
233
234    #[tokio::test]
235    async fn test_get_deployment_id_happy_path_file_auth() {
236        // Arrange
237        let mut mock_docker = MockDocker::new();
238        let mut container_inspect_response = create_test_container_inspect_response();
239
240        // Add username and password file environment variables
241        if let Some(config) = container_inspect_response.config.as_mut()
242            && let Some(env) = config.env.as_mut()
243        {
244            env.push("MONGODB_INITDB_ROOT_USERNAME_FILE=/run/secrets/username".to_string());
245            env.push("MONGODB_INITDB_ROOT_PASSWORD_FILE=/run/secrets/password".to_string());
246        }
247
248        // Mock get_deployment call
249        mock_docker
250            .expect_inspect_container()
251            .with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
252            .times(1)
253            .returning(move |_, _| Ok(container_inspect_response.clone()));
254
255        // Mock reading username file
256        mock_docker
257            .expect_run_command_in_container()
258            .with(
259                eq("test_container_id"),
260                eq(vec!["cat".to_string(), "/run/secrets/username".to_string()]),
261            )
262            .times(1)
263            .returning(|_, _| {
264                Ok(CommandOutput {
265                    stdout: vec!["fileuser".to_string()],
266                    stderr: vec![],
267                })
268            });
269
270        // Mock reading password file
271        mock_docker
272            .expect_run_command_in_container()
273            .with(
274                eq("test_container_id"),
275                eq(vec!["cat".to_string(), "/run/secrets/password".to_string()]),
276            )
277            .times(1)
278            .returning(|_, _| {
279                Ok(CommandOutput {
280                    stdout: vec!["filepass".to_string()],
281                    stderr: vec![],
282                })
283            });
284
285        // Mock mongosh command execution with file-based auth
286        mock_docker
287            .expect_run_command_in_container()
288            .with(
289                eq("test_container_id"),
290                eq(vec![
291                    "mongosh".to_string(),
292                    "mongodb://127.0.0.1:27017/?directConnection=true".to_string(),
293                    "--username=fileuser".to_string(),
294                    "--password=filepass".to_string(),
295                    "--eval".to_string(),
296                    "db.getSiblingDB('admin').atlascli.findOne()?.uuid".to_string(),
297                    "--quiet".to_string(),
298                ]),
299            )
300            .times(1)
301            .returning(|_, _| {
302                Ok(CommandOutput {
303                    stdout: vec!["deployment-uuid-789".to_string()],
304                    stderr: vec![],
305                })
306            });
307
308        let client = Client::new(mock_docker);
309
310        // Act
311        let result = client.get_deployment_id("test-deployment").await;
312
313        // Assert
314        assert!(result.is_ok());
315        assert_eq!(result.unwrap(), "deployment-uuid-789");
316    }
317
318    #[tokio::test]
319    async fn test_get_deployment_id_get_deployment_error() {
320        // Arrange
321        let mut mock_docker = MockDocker::new();
322
323        // Mock get_deployment call to fail
324        mock_docker
325            .expect_inspect_container()
326            .with(
327                eq("nonexistent-deployment"),
328                eq(None::<InspectContainerOptions>),
329            )
330            .times(1)
331            .returning(|_, _| {
332                Err(BollardError::DockerResponseServerError {
333                    status_code: 404,
334                    message: "No such container".to_string(),
335                })
336            });
337
338        let client = Client::new(mock_docker);
339
340        // Act
341        let result = client.get_deployment_id("nonexistent-deployment").await;
342
343        // Assert
344        assert!(result.is_err());
345        match result.unwrap_err() {
346            GetDeploymentIdError::GetDeployment(GetDeploymentError::ContainerInspect(_)) => {
347                // Expected error
348            }
349            other => panic!("Expected GetDeployment error, got: {:?}", other),
350        }
351    }
352
353    #[tokio::test]
354    async fn test_get_deployment_id_get_mongodb_username_error() {
355        // Arrange
356        let mut mock_docker = MockDocker::new();
357        let mut container_inspect_response = create_test_container_inspect_response();
358
359        // Add username file environment variable (but no direct username)
360        if let Some(config) = container_inspect_response.config.as_mut()
361            && let Some(env) = config.env.as_mut()
362        {
363            env.push("MONGODB_INITDB_ROOT_USERNAME_FILE=/run/secrets/username".to_string());
364        }
365
366        // Mock get_deployment call
367        mock_docker
368            .expect_inspect_container()
369            .with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
370            .times(1)
371            .returning(move |_, _| Ok(container_inspect_response.clone()));
372
373        // Mock reading username file to fail
374        mock_docker
375            .expect_run_command_in_container()
376            .with(
377                eq("test_container_id"),
378                eq(vec!["cat".to_string(), "/run/secrets/username".to_string()]),
379            )
380            .times(1)
381            .returning(|_, _| {
382                Err(RunCommandInContainerError::CreateExec(
383                    BollardError::DockerResponseServerError {
384                        status_code: 500,
385                        message: "Failed to read file".to_string(),
386                    },
387                ))
388            });
389
390        let client = Client::new(mock_docker);
391
392        // Act
393        let result = client.get_deployment_id("test-deployment").await;
394
395        // Assert
396        assert!(result.is_err());
397        match result.unwrap_err() {
398            GetDeploymentIdError::GetMongodbUsername(_) => {
399                // Expected error
400            }
401            other => panic!("Expected GetMongodbUsername error, got: {:?}", other),
402        }
403    }
404
405    #[tokio::test]
406    async fn test_get_deployment_id_get_mongodb_password_error() {
407        // Arrange
408        let mut mock_docker = MockDocker::new();
409        let mut container_inspect_response = create_test_container_inspect_response();
410
411        // Add password file environment variable (but no direct password)
412        if let Some(config) = container_inspect_response.config.as_mut()
413            && let Some(env) = config.env.as_mut()
414        {
415            env.push("MONGODB_INITDB_ROOT_PASSWORD_FILE=/run/secrets/password".to_string());
416        }
417
418        // Mock get_deployment call
419        mock_docker
420            .expect_inspect_container()
421            .with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
422            .times(1)
423            .returning(move |_, _| Ok(container_inspect_response.clone()));
424
425        // Mock reading password file to fail
426        mock_docker
427            .expect_run_command_in_container()
428            .with(
429                eq("test_container_id"),
430                eq(vec!["cat".to_string(), "/run/secrets/password".to_string()]),
431            )
432            .times(1)
433            .returning(|_, _| {
434                Err(RunCommandInContainerError::StartExec(
435                    BollardError::DockerResponseServerError {
436                        status_code: 500,
437                        message: "Failed to start exec".to_string(),
438                    },
439                ))
440            });
441
442        let client = Client::new(mock_docker);
443
444        // Act
445        let result = client.get_deployment_id("test-deployment").await;
446
447        // Assert
448        assert!(result.is_err());
449        match result.unwrap_err() {
450            GetDeploymentIdError::GetMongodbPassword(_) => {
451                // Expected error
452            }
453            other => panic!("Expected GetMongodbPassword error, got: {:?}", other),
454        }
455    }
456
457    #[tokio::test]
458    async fn test_get_deployment_id_run_mongosh_command_error() {
459        // Arrange
460        let mut mock_docker = MockDocker::new();
461        let container_inspect_response = create_test_container_inspect_response();
462
463        // Mock get_deployment call
464        mock_docker
465            .expect_inspect_container()
466            .with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
467            .times(1)
468            .returning(move |_, _| Ok(container_inspect_response.clone()));
469
470        // Mock mongosh command execution to fail
471        mock_docker
472            .expect_run_command_in_container()
473            .with(
474                eq("test_container_id"),
475                eq(vec![
476                    "mongosh".to_string(),
477                    "mongodb://127.0.0.1:27017/?directConnection=true".to_string(),
478                    "--eval".to_string(),
479                    "db.getSiblingDB('admin').atlascli.findOne()?.uuid".to_string(),
480                    "--quiet".to_string(),
481                ]),
482            )
483            .times(1)
484            .returning(|_, _| Err(RunCommandInContainerError::GetOutput));
485
486        let client = Client::new(mock_docker);
487
488        // Act
489        let result = client.get_deployment_id("test-deployment").await;
490
491        // Assert
492        assert!(result.is_err());
493        match result.unwrap_err() {
494            GetDeploymentIdError::RunMongoshCommand(_) => {
495                // Expected error
496            }
497            other => panic!("Expected RunMongoshCommand error, got: {:?}", other),
498        }
499    }
500
501    #[tokio::test]
502    async fn test_get_deployment_id_mixed_auth_env_username_file_password() {
503        // Arrange
504        let mut mock_docker = MockDocker::new();
505        let mut container_inspect_response = create_test_container_inspect_response();
506
507        // Add username from env and password from file
508        if let Some(config) = container_inspect_response.config.as_mut()
509            && let Some(env) = config.env.as_mut()
510        {
511            env.push("MONGODB_INITDB_ROOT_USERNAME=envuser".to_string());
512            env.push("MONGODB_INITDB_ROOT_PASSWORD_FILE=/run/secrets/password".to_string());
513        }
514
515        // Mock get_deployment call
516        mock_docker
517            .expect_inspect_container()
518            .with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
519            .times(1)
520            .returning(move |_, _| Ok(container_inspect_response.clone()));
521
522        // Mock reading password file
523        mock_docker
524            .expect_run_command_in_container()
525            .with(
526                eq("test_container_id"),
527                eq(vec!["cat".to_string(), "/run/secrets/password".to_string()]),
528            )
529            .times(1)
530            .returning(|_, _| {
531                Ok(CommandOutput {
532                    stdout: vec!["filepass".to_string()],
533                    stderr: vec![],
534                })
535            });
536
537        // Mock mongosh command execution with mixed auth
538        mock_docker
539            .expect_run_command_in_container()
540            .with(
541                eq("test_container_id"),
542                eq(vec![
543                    "mongosh".to_string(),
544                    "mongodb://127.0.0.1:27017/?directConnection=true".to_string(),
545                    "--username=envuser".to_string(),
546                    "--password=filepass".to_string(),
547                    "--eval".to_string(),
548                    "db.getSiblingDB('admin').atlascli.findOne()?.uuid".to_string(),
549                    "--quiet".to_string(),
550                ]),
551            )
552            .times(1)
553            .returning(|_, _| {
554                Ok(CommandOutput {
555                    stdout: vec!["deployment-uuid-mixed".to_string()],
556                    stderr: vec![],
557                })
558            });
559
560        let client = Client::new(mock_docker);
561
562        // Act
563        let result = client.get_deployment_id("test-deployment").await;
564
565        // Assert
566        assert!(result.is_ok());
567        assert_eq!(result.unwrap(), "deployment-uuid-mixed");
568    }
569
570    #[tokio::test]
571    async fn test_get_deployment_id_all_run_command_in_container_error_variants() {
572        // Test all variants of RunCommandInContainerError to ensure full coverage
573
574        // Test GetOutputError
575        let mut mock_docker = MockDocker::new();
576        let container_inspect_response = create_test_container_inspect_response();
577
578        mock_docker
579            .expect_inspect_container()
580            .with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
581            .times(1)
582            .returning(move |_, _| Ok(container_inspect_response.clone()));
583
584        mock_docker
585            .expect_run_command_in_container()
586            .with(
587                eq("test_container_id"),
588                eq(vec![
589                    "mongosh".to_string(),
590                    "mongodb://127.0.0.1:27017/?directConnection=true".to_string(),
591                    "--eval".to_string(),
592                    "db.getSiblingDB('admin').atlascli.findOne()?.uuid".to_string(),
593                    "--quiet".to_string(),
594                ]),
595            )
596            .times(1)
597            .returning(|_, _| {
598                Err(RunCommandInContainerError::GetOutputError(
599                    BollardError::DockerResponseServerError {
600                        status_code: 500,
601                        message: "Failed to get output".to_string(),
602                    },
603                ))
604            });
605
606        let client = Client::new(mock_docker);
607        let result = client.get_deployment_id("test-deployment").await;
608
609        assert!(result.is_err());
610        match result.unwrap_err() {
611            GetDeploymentIdError::RunMongoshCommand(
612                RunCommandInContainerError::GetOutputError(_),
613            ) => {
614                // Expected error
615            }
616            other => panic!(
617                "Expected RunMongoshCommand GetOutputError, got: {:?}",
618                other
619            ),
620        }
621    }
622
623    #[tokio::test]
624    async fn test_get_deployment_id_empty_stdout() {
625        // Test behavior when mongosh returns empty stdout
626        let mut mock_docker = MockDocker::new();
627        let container_inspect_response = create_test_container_inspect_response();
628
629        mock_docker
630            .expect_inspect_container()
631            .with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
632            .times(1)
633            .returning(move |_, _| Ok(container_inspect_response.clone()));
634
635        mock_docker
636            .expect_run_command_in_container()
637            .with(
638                eq("test_container_id"),
639                eq(vec![
640                    "mongosh".to_string(),
641                    "mongodb://127.0.0.1:27017/?directConnection=true".to_string(),
642                    "--eval".to_string(),
643                    "db.getSiblingDB('admin').atlascli.findOne()?.uuid".to_string(),
644                    "--quiet".to_string(),
645                ]),
646            )
647            .times(1)
648            .returning(|_, _| {
649                Ok(CommandOutput {
650                    stdout: vec![],
651                    stderr: vec![],
652                })
653            });
654
655        let client = Client::new(mock_docker);
656        let result = client.get_deployment_id("test-deployment").await;
657
658        assert!(result.is_err());
659    }
660
661    #[tokio::test]
662    async fn test_get_deployment_id_username_only() {
663        // Test when only username is provided (no password)
664        let mut mock_docker = MockDocker::new();
665        let mut container_inspect_response = create_test_container_inspect_response();
666
667        // Add only username environment variable
668        if let Some(config) = container_inspect_response.config.as_mut()
669            && let Some(env) = config.env.as_mut()
670        {
671            env.push("MONGODB_INITDB_ROOT_USERNAME=onlyuser".to_string());
672        }
673
674        mock_docker
675            .expect_inspect_container()
676            .with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
677            .times(1)
678            .returning(move |_, _| Ok(container_inspect_response.clone()));
679
680        mock_docker
681            .expect_run_command_in_container()
682            .with(
683                eq("test_container_id"),
684                eq(vec![
685                    "mongosh".to_string(),
686                    "mongodb://127.0.0.1:27017/?directConnection=true".to_string(),
687                    "--username=onlyuser".to_string(),
688                    "--eval".to_string(),
689                    "db.getSiblingDB('admin').atlascli.findOne()?.uuid".to_string(),
690                    "--quiet".to_string(),
691                ]),
692            )
693            .times(1)
694            .returning(|_, _| {
695                Ok(CommandOutput {
696                    stdout: vec!["deployment-uuid-username-only".to_string()],
697                    stderr: vec![],
698                })
699            });
700
701        let client = Client::new(mock_docker);
702        let result = client.get_deployment_id("test-deployment").await;
703
704        assert!(result.is_ok());
705        assert_eq!(result.unwrap(), "deployment-uuid-username-only");
706    }
707
708    #[tokio::test]
709    async fn test_get_deployment_id_password_only() {
710        // Test when only password is provided (no username)
711        let mut mock_docker = MockDocker::new();
712        let mut container_inspect_response = create_test_container_inspect_response();
713
714        // Add only password environment variable
715        if let Some(config) = container_inspect_response.config.as_mut()
716            && let Some(env) = config.env.as_mut()
717        {
718            env.push("MONGODB_INITDB_ROOT_PASSWORD=onlypass".to_string());
719        }
720
721        mock_docker
722            .expect_inspect_container()
723            .with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
724            .times(1)
725            .returning(move |_, _| Ok(container_inspect_response.clone()));
726
727        mock_docker
728            .expect_run_command_in_container()
729            .with(
730                eq("test_container_id"),
731                eq(vec![
732                    "mongosh".to_string(),
733                    "mongodb://127.0.0.1:27017/?directConnection=true".to_string(),
734                    "--password=onlypass".to_string(),
735                    "--eval".to_string(),
736                    "db.getSiblingDB('admin').atlascli.findOne()?.uuid".to_string(),
737                    "--quiet".to_string(),
738                ]),
739            )
740            .times(1)
741            .returning(|_, _| {
742                Ok(CommandOutput {
743                    stdout: vec!["deployment-uuid-password-only".to_string()],
744                    stderr: vec![],
745                })
746            });
747
748        let client = Client::new(mock_docker);
749        let result = client.get_deployment_id("test-deployment").await;
750
751        assert!(result.is_ok());
752        assert_eq!(result.unwrap(), "deployment-uuid-password-only");
753    }
754}