Skip to main content

atlas_local/client/
stop_deployment.rs

1use bollard::query_parameters::StopContainerOptions;
2
3use crate::{
4    client::Client,
5    docker::{DockerInspectContainer, DockerStopContainer},
6};
7
8use super::GetDeploymentError;
9
10#[derive(Debug, thiserror::Error)]
11pub enum StopDeploymentError {
12    #[error("Failed to stop container: {0}")]
13    ContainerStop(String),
14    #[error("Failed to get deployment: {0}")]
15    GetDeployment(#[from] GetDeploymentError),
16}
17
18impl<D: DockerStopContainer + DockerInspectContainer> Client<D> {
19    /// Stops a local Atlas deployment.
20    pub async fn stop_deployment(&self, name: &str) -> Result<(), StopDeploymentError> {
21        // Check that a deployment with that name exists and get the container ID.
22        // This ensures we only try to stop valid Atlas local deployments.
23        let deployment = self.get_deployment(name).await?;
24        let container_id = deployment.container_id.as_str();
25
26        // Stop the container gracefully.
27        self.docker
28            .stop_container(container_id, None::<StopContainerOptions>)
29            .await
30            .map_err(|e| StopDeploymentError::ContainerStop(e.to_string()))?;
31
32        Ok(())
33    }
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39    use bollard::{
40        errors::Error as BollardError, query_parameters::InspectContainerOptions,
41        secret::ContainerInspectResponse,
42    };
43    use mockall::mock;
44
45    mock! {
46        Docker {}
47
48        impl DockerStopContainer for Docker {
49            async fn stop_container(
50                &self,
51                container_id: &str,
52                options: Option<StopContainerOptions>,
53            ) -> Result<(), BollardError>;
54        }
55
56        impl DockerInspectContainer for Docker {
57            async fn inspect_container(
58                &self,
59                container_id: &str,
60                options: Option<InspectContainerOptions>,
61            ) -> Result<ContainerInspectResponse, BollardError>;
62        }
63    }
64
65    fn create_test_container_inspect_response() -> ContainerInspectResponse {
66        use bollard::secret::{ContainerConfig, ContainerState, ContainerStateStatusEnum};
67        use std::collections::HashMap;
68
69        let mut labels = HashMap::new();
70        labels.insert("mongodb-atlas-local".to_string(), "container".to_string());
71        labels.insert("version".to_string(), "8.0.0".to_string());
72        labels.insert("mongodb-type".to_string(), "community".to_string());
73
74        let env_vars = vec!["TOOL=ATLASCLI".to_string()];
75
76        ContainerInspectResponse {
77            id: Some("test_container_id".to_string()),
78            name: Some("/test-deployment".to_string()),
79            config: Some(ContainerConfig {
80                labels: Some(labels),
81                env: Some(env_vars),
82                ..Default::default()
83            }),
84            state: Some(ContainerState {
85                status: Some(ContainerStateStatusEnum::RUNNING),
86                ..Default::default()
87            }),
88            ..Default::default()
89        }
90    }
91
92    #[tokio::test]
93    async fn test_stop_deployment() {
94        // Arrange
95        let mut mock_docker = MockDocker::new();
96
97        // Set up expectations
98        mock_docker
99            .expect_inspect_container()
100            .with(
101                mockall::predicate::eq("test-deployment"),
102                mockall::predicate::eq(None::<InspectContainerOptions>),
103            )
104            .times(1)
105            .returning(move |_, _| Ok(create_test_container_inspect_response()));
106
107        mock_docker
108            .expect_stop_container()
109            .with(
110                mockall::predicate::eq("test_container_id"),
111                mockall::predicate::eq(None::<StopContainerOptions>),
112            )
113            .times(1)
114            .returning(|_, _| Ok(()));
115
116        let client = Client::new(mock_docker);
117
118        // Act
119        let result = client.stop_deployment("test-deployment").await;
120
121        // Assert
122        assert!(result.is_ok());
123    }
124
125    #[tokio::test]
126    async fn test_stop_deployment_get_deployment_error() {
127        // Arrange
128        let mut mock_docker = MockDocker::new();
129
130        // Set up expectations
131        mock_docker
132            .expect_inspect_container()
133            .times(1)
134            .returning(|_, _| {
135                Err(BollardError::DockerResponseServerError {
136                    status_code: 404,
137                    message: "No such container".to_string(),
138                })
139            });
140
141        let client = Client::new(mock_docker);
142
143        // Act
144        let result = client.stop_deployment("nonexistent-deployment").await;
145
146        // Assert
147        assert!(result.is_err());
148        assert!(matches!(
149            result.unwrap_err(),
150            StopDeploymentError::GetDeployment(_)
151        ));
152    }
153
154    #[tokio::test]
155    async fn test_stop_deployment_stop_container_error() {
156        // Arrange
157        let mut mock_docker = MockDocker::new();
158
159        // Set up expectations
160        mock_docker
161            .expect_inspect_container()
162            .times(1)
163            .returning(move |_, _| Ok(create_test_container_inspect_response()));
164
165        mock_docker
166            .expect_stop_container()
167            .times(1)
168            .returning(|_, _| {
169                Err(BollardError::DockerResponseServerError {
170                    status_code: 500,
171                    message: "Internal Server Error".to_string(),
172                })
173            });
174
175        let client = Client::new(mock_docker);
176
177        // Act
178        let result = client.stop_deployment("test-deployment").await;
179
180        // Assert
181        assert!(result.is_err());
182        assert!(matches!(
183            result.unwrap_err(),
184            StopDeploymentError::ContainerStop(_)
185        ));
186    }
187}