Skip to main content

torrust_tracker_deployer_lib/testing/integration/ssh_server/
config.rs

1//! Configuration for SSH server containers
2
3use std::path::PathBuf;
4
5use crate::shared::secrets::PlainPassword;
6
7use super::constants::{
8    CONTAINER_STARTUP_WAIT_SECS, DEFAULT_TEST_PASSWORD, DEFAULT_TEST_USERNAME, DOCKERFILE_DIR,
9    MOCK_SSH_PORT, SSH_SERVER_IMAGE_NAME, SSH_SERVER_IMAGE_TAG,
10};
11
12/// Configuration for SSH server containers
13///
14/// This struct defines all configurable parameters for SSH server containers.
15/// Use the builder pattern to create custom configurations, or use `default()`
16/// for standard test scenarios.
17///
18/// # Examples
19///
20/// ```rust
21/// use torrust_tracker_deployer_lib::testing::integration::ssh_server::SshServerConfig;
22///
23/// // Use default configuration
24/// let config = SshServerConfig::default();
25///
26/// // Customize configuration
27/// let config = SshServerConfig::builder()
28///     .username("customuser")
29///     .password("custompass")
30///     .startup_wait_secs(15)
31///     .build();
32/// ```
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct SshServerConfig {
35    /// Docker image name for the SSH server
36    pub image_name: String,
37
38    /// Docker image tag for the SSH server
39    pub image_tag: String,
40
41    /// Test username configured in the SSH server
42    pub username: String,
43
44    /// Test password configured in the SSH server
45    pub password: PlainPassword,
46
47    /// Container startup wait time in seconds
48    pub startup_wait_secs: u64,
49
50    /// Path to the Dockerfile directory
51    pub dockerfile_dir: PathBuf,
52
53    /// Port to use for mock container (default: 2222)
54    pub mock_port: u16,
55}
56
57impl SshServerConfig {
58    /// Create a builder for custom configuration
59    ///
60    /// # Example
61    ///
62    /// ```rust
63    /// use torrust_tracker_deployer_lib::testing::integration::ssh_server::SshServerConfig;
64    ///
65    /// let config = SshServerConfig::builder()
66    ///     .username("testuser")
67    ///     .password("testpass")
68    ///     .build();
69    /// ```
70    #[must_use]
71    pub fn builder() -> SshServerConfigBuilder {
72        SshServerConfigBuilder::default()
73    }
74}
75
76impl Default for SshServerConfig {
77    /// Create configuration with default values from constants
78    ///
79    /// Default values:
80    /// - Image: `torrust-ssh-server:latest`
81    /// - Username: `testuser`
82    /// - Password: `testpass`
83    /// - Startup wait: 10 seconds
84    /// - Dockerfile: `docker/ssh-server`
85    /// - Mock port: 2222
86    ///
87    /// Note: The SSH container always uses port 22 internally (this is not configurable)
88    fn default() -> Self {
89        Self {
90            image_name: SSH_SERVER_IMAGE_NAME.to_string(),
91            image_tag: SSH_SERVER_IMAGE_TAG.to_string(),
92            username: DEFAULT_TEST_USERNAME.to_string(),
93            password: DEFAULT_TEST_PASSWORD.to_string(),
94            startup_wait_secs: CONTAINER_STARTUP_WAIT_SECS,
95            dockerfile_dir: PathBuf::from(DOCKERFILE_DIR),
96            mock_port: MOCK_SSH_PORT,
97        }
98    }
99}
100
101/// Builder for SSH server configuration
102///
103/// Provides a fluent API for constructing custom SSH server configurations.
104/// Any field not explicitly set will use the default value.
105///
106/// # Example
107///
108/// ```rust
109/// use torrust_tracker_deployer_lib::testing::integration::ssh_server::SshServerConfig;
110///
111/// let config = SshServerConfig::builder()
112///     .image_name("custom-ssh-server")
113///     .image_tag("v2.0")
114///     .username("admin")
115///     .password("secret123")
116///     .startup_wait_secs(20)
117///     .build();
118/// ```
119#[derive(Debug, Default)]
120pub struct SshServerConfigBuilder {
121    image_name: Option<String>,
122    image_tag: Option<String>,
123    username: Option<String>,
124    password: Option<PlainPassword>,
125    startup_wait_secs: Option<u64>,
126    dockerfile_dir: Option<PathBuf>,
127    mock_port: Option<u16>,
128}
129
130impl SshServerConfigBuilder {
131    /// Set the Docker image name
132    #[must_use]
133    pub fn image_name(mut self, name: impl Into<String>) -> Self {
134        self.image_name = Some(name.into());
135        self
136    }
137
138    /// Set the Docker image tag
139    #[must_use]
140    pub fn image_tag(mut self, tag: impl Into<String>) -> Self {
141        self.image_tag = Some(tag.into());
142        self
143    }
144
145    /// Set the test username
146    #[must_use]
147    pub fn username(mut self, username: impl Into<String>) -> Self {
148        self.username = Some(username.into());
149        self
150    }
151
152    /// Set the test password
153    #[must_use]
154    pub fn password(mut self, password: impl Into<PlainPassword>) -> Self {
155        self.password = Some(password.into());
156        self
157    }
158
159    /// Set the container startup wait time in seconds
160    #[must_use]
161    pub fn startup_wait_secs(mut self, secs: u64) -> Self {
162        self.startup_wait_secs = Some(secs);
163        self
164    }
165
166    /// Set the Dockerfile directory path
167    #[must_use]
168    pub fn dockerfile_dir(mut self, dir: impl Into<PathBuf>) -> Self {
169        self.dockerfile_dir = Some(dir.into());
170        self
171    }
172
173    /// Set the mock SSH server port
174    #[must_use]
175    pub fn mock_port(mut self, port: u16) -> Self {
176        self.mock_port = Some(port);
177        self
178    }
179
180    /// Build the configuration
181    ///
182    /// Any fields not explicitly set will use default values from constants.
183    #[must_use]
184    pub fn build(self) -> SshServerConfig {
185        let defaults = SshServerConfig::default();
186        SshServerConfig {
187            image_name: self.image_name.unwrap_or(defaults.image_name),
188            image_tag: self.image_tag.unwrap_or(defaults.image_tag),
189            username: self.username.unwrap_or(defaults.username),
190            password: self.password.unwrap_or(defaults.password),
191            startup_wait_secs: self.startup_wait_secs.unwrap_or(defaults.startup_wait_secs),
192            dockerfile_dir: self.dockerfile_dir.unwrap_or(defaults.dockerfile_dir),
193            mock_port: self.mock_port.unwrap_or(defaults.mock_port),
194        }
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn it_should_create_config_with_default_values() {
204        let config = SshServerConfig::default();
205
206        assert_eq!(config.image_name, "torrust-ssh-server");
207        assert_eq!(config.image_tag, "latest");
208        assert_eq!(config.username, "testuser");
209        assert_eq!(config.password, "testpass");
210        assert_eq!(config.startup_wait_secs, 10);
211        assert_eq!(config.dockerfile_dir, PathBuf::from("docker/ssh-server"));
212        assert_eq!(config.mock_port, 2222);
213    }
214
215    #[test]
216    fn it_should_build_config_with_custom_values() {
217        let config = SshServerConfig::builder()
218            .image_name("custom-ssh")
219            .image_tag("v2.0")
220            .username("admin")
221            .password("secret")
222            .startup_wait_secs(15)
223            .dockerfile_dir("custom/path")
224            .mock_port(3333)
225            .build();
226
227        assert_eq!(config.image_name, "custom-ssh");
228        assert_eq!(config.image_tag, "v2.0");
229        assert_eq!(config.username, "admin");
230        assert_eq!(config.password, "secret");
231        assert_eq!(config.startup_wait_secs, 15);
232        assert_eq!(config.dockerfile_dir, PathBuf::from("custom/path"));
233        assert_eq!(config.mock_port, 3333);
234    }
235
236    #[test]
237    fn it_should_use_defaults_for_unset_builder_fields() {
238        let config = SshServerConfig::builder().username("customuser").build();
239
240        // Custom value
241        assert_eq!(config.username, "customuser");
242
243        // Default values for unset fields
244        assert_eq!(config.image_name, "torrust-ssh-server");
245        assert_eq!(config.image_tag, "latest");
246        assert_eq!(config.password, "testpass");
247        assert_eq!(config.startup_wait_secs, 10);
248        assert_eq!(config.mock_port, 2222);
249    }
250
251    #[test]
252    fn it_should_allow_chaining_builder_methods() {
253        let config = SshServerConfig::builder()
254            .image_name("test")
255            .image_tag("v1")
256            .username("user1")
257            .password("pass1")
258            .startup_wait_secs(5)
259            .build();
260
261        assert_eq!(config.image_name, "test");
262        assert_eq!(config.username, "user1");
263        assert_eq!(config.startup_wait_secs, 5);
264    }
265
266    #[test]
267    fn it_should_be_cloneable() {
268        let config1 = SshServerConfig::default();
269        let config2 = config1.clone();
270
271        assert_eq!(config1, config2);
272    }
273
274    #[test]
275    fn it_should_allow_customizing_mock_port() {
276        let config = SshServerConfig::builder().mock_port(5555).build();
277
278        assert_eq!(config.mock_port, 5555);
279
280        // Other fields should use defaults
281        assert_eq!(config.username, "testuser");
282        assert_eq!(config.password, "testpass");
283    }
284}