-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathbuilders.rs
More file actions
347 lines (309 loc) · 12.1 KB
/
Copy pathbuilders.rs
File metadata and controls
347 lines (309 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
//! Test builders for Create Command
//!
//! This module provides test builders that simplify test setup by managing
//! dependencies and lifecycle for `CreateCommandHandler` tests.
use std::path::Path;
use std::sync::Arc;
use chrono::{DateTime, Utc};
use tempfile::TempDir;
use crate::application::command_handlers::create::config::tracker::TrackerSection;
use crate::application::command_handlers::create::config::{
EnvironmentCreationConfig, EnvironmentSection, LxdProviderSection, ProviderSection,
SshCredentialsConfig,
};
use crate::application::command_handlers::create::CreateCommandHandler;
use crate::domain::environment::{Environment, EnvironmentName};
use crate::domain::provider::{LxdConfig, ProviderConfig};
use crate::domain::ProfileName;
use crate::infrastructure::persistence::file_repository_factory::FileRepositoryFactory;
use crate::shared::Clock;
use crate::testing::MockClock;
/// Test builder for `CreateCommandHandler` with sensible defaults and customization options
///
/// This builder simplifies test setup by:
/// - Managing `TempDir` lifecycle
/// - Providing sensible defaults for all dependencies
/// - Allowing selective customization of dependencies
/// - Supporting pre-populated test environments
///
/// # Examples
///
/// ```rust,no_run
/// use torrust_tracker_deployer_lib::application::command_handlers::create::tests::CreateCommandHandlerTestBuilder;
///
/// // Simple command with defaults
/// let (command, _temp_dir) = CreateCommandHandlerTestBuilder::new().build();
///
/// // Command with fixed time for deterministic testing
/// use chrono::{TimeZone, Utc};
/// let fixed_time = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap();
/// let (command, _temp_dir) = CreateCommandHandlerTestBuilder::new()
/// .with_fixed_time(fixed_time)
/// .build();
///
/// // Command with existing environment to test conflict detection
/// let (command, _temp_dir) = CreateCommandHandlerTestBuilder::new()
/// .with_existing_environment("production")
/// .build();
/// ```
pub struct CreateCommandHandlerTestBuilder {
/// Optional base directory for environment storage
base_directory: Option<std::path::PathBuf>,
/// Optional fixed time for deterministic testing
fixed_time: Option<DateTime<Utc>>,
/// List of environment names that should already exist
existing_environments: Vec<String>,
}
impl CreateCommandHandlerTestBuilder {
/// Create a new test builder with default settings
///
/// # Examples
///
/// ```rust,no_run
/// use torrust_tracker_deployer_lib::application::command_handlers::create::tests::CreateCommandHandlerTestBuilder;
///
/// let builder = CreateCommandHandlerTestBuilder::new();
/// ```
#[must_use]
pub fn new() -> Self {
Self {
base_directory: None,
fixed_time: None,
existing_environments: Vec::new(),
}
}
/// Set a custom base directory for the test environment
///
/// By default, a temporary directory is created. Use this method to
/// specify a custom location.
///
/// # Examples
///
/// ```rust,no_run
/// use torrust_tracker_deployer_lib::application::command_handlers::create::tests::CreateCommandHandlerTestBuilder;
/// use tempfile::TempDir;
///
/// let temp_dir = TempDir::new().unwrap();
/// let (command, _temp_dir) = CreateCommandHandlerTestBuilder::new()
/// .with_base_directory(temp_dir.path())
/// .build();
/// ```
#[must_use]
pub fn with_base_directory<P: AsRef<Path>>(mut self, path: P) -> Self {
self.base_directory = Some(path.as_ref().to_path_buf());
self
}
/// Set a fixed time for deterministic testing
///
/// This configures the mock clock to return a specific timestamp,
/// making tests reproducible.
///
/// # Examples
///
/// ```rust,no_run
/// use torrust_tracker_deployer_lib::application::command_handlers::create::tests::CreateCommandHandlerTestBuilder;
/// use chrono::{TimeZone, Utc};
///
/// let fixed_time = Utc.with_ymd_and_hms(2025, 1, 1, 12, 0, 0).unwrap();
/// let (command, _temp_dir) = CreateCommandHandlerTestBuilder::new()
/// .with_fixed_time(fixed_time)
/// .build();
/// ```
#[must_use]
pub fn with_fixed_time(mut self, time: DateTime<Utc>) -> Self {
self.fixed_time = Some(time);
self
}
/// Add an existing environment to simulate conflicts
///
/// This method pre-creates an environment in the repository so that
/// tests can verify conflict detection behavior.
///
/// # Examples
///
/// ```rust,no_run
/// use torrust_tracker_deployer_lib::application::command_handlers::create::tests::CreateCommandHandlerTestBuilder;
///
/// let (command, _temp_dir) = CreateCommandHandlerTestBuilder::new()
/// .with_existing_environment("production")
/// .with_existing_environment("staging")
/// .build();
/// ```
#[must_use]
pub fn with_existing_environment(mut self, name: &str) -> Self {
self.existing_environments.push(name.to_string());
self
}
/// Build the `CreateCommandHandler` with configured dependencies
///
/// Returns a tuple of (`CreateCommandHandler`, `TempDir`). The `TempDir` must be
/// kept alive for the duration of the test to prevent cleanup.
///
/// # Examples
///
/// ```rust,no_run
/// use torrust_tracker_deployer_lib::application::command_handlers::create::tests::CreateCommandHandlerTestBuilder;
///
/// let (command, _temp_dir) = CreateCommandHandlerTestBuilder::new().build();
/// // Use command for testing
/// ```
#[must_use]
pub fn build(self) -> (CreateCommandHandler, TempDir) {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let base_dir = self
.base_directory
.clone()
.unwrap_or_else(|| temp_dir.path().to_path_buf());
// Create mock clock with fixed or current time
let clock_time = self.fixed_time.unwrap_or_else(Utc::now);
let clock: Arc<dyn Clock> = Arc::new(MockClock::new(clock_time));
// Create repository with file-based persistence
let file_repository_factory =
FileRepositoryFactory::new(std::time::Duration::from_secs(30));
let repository = file_repository_factory.create(base_dir.clone());
// Pre-create existing environments if specified
for env_name in &self.existing_environments {
self.create_existing_environment(&repository, env_name, &base_dir);
}
let command = CreateCommandHandler::new(repository, clock);
(command, temp_dir)
}
/// Helper to create an existing environment in the repository
#[allow(clippy::unused_self)] // Builder pattern - self is consumed in build()
fn create_existing_environment(
&self,
repository: &Arc<
dyn crate::domain::environment::repository::EnvironmentRepository + Send + Sync,
>,
name: &str,
base_dir: &Path,
) {
use crate::adapters::ssh::SshCredentials;
use crate::shared::Username;
// Create temporary SSH key files
let private_key = base_dir.join(format!("{name}_key"));
let public_key = base_dir.join(format!("{name}_key.pub"));
std::fs::write(&private_key, "test_private_key").expect("Failed to write private key");
std::fs::write(&public_key, "test_public_key").expect("Failed to write public key");
// Create environment
let env_name = EnvironmentName::new(name).expect("Invalid environment name in test");
let username = Username::new("torrust".to_string()).expect("Invalid username in test");
let ssh_credentials = SshCredentials::new(private_key, public_key, username);
let provider_config = ProviderConfig::Lxd(LxdConfig {
profile_name: ProfileName::new(format!("lxd-{}", env_name.as_str())).unwrap(),
});
let environment = Environment::new(
env_name,
provider_config,
ssh_credentials,
22,
chrono::Utc::now(),
);
// Save to repository
repository
.save(&environment.into_any())
.expect("Failed to save existing environment in test");
}
}
impl Default for CreateCommandHandlerTestBuilder {
fn default() -> Self {
Self::new()
}
}
/// Helper function to create a valid test configuration
///
/// This function creates a complete `EnvironmentCreationConfig` with temporary
/// SSH key files for testing.
///
/// # Arguments
///
/// * `temp_dir` - Temporary directory where SSH keys will be created
/// * `env_name` - Name for the environment
///
/// # Examples
///
/// ```rust,no_run
/// use torrust_tracker_deployer_lib::application::command_handlers::create::tests::create_valid_test_config;
/// use tempfile::TempDir;
///
/// let temp_dir = TempDir::new().unwrap();
/// let config = create_valid_test_config(&temp_dir, "test-environment");
/// ```
#[must_use]
pub fn create_valid_test_config(temp_dir: &TempDir, env_name: &str) -> EnvironmentCreationConfig {
use std::fs;
// Create temporary SSH key files
let private_key = temp_dir.path().join("id_rsa");
let public_key = temp_dir.path().join("id_rsa.pub");
fs::write(&private_key, "test_private_key").expect("Failed to write private key");
fs::write(&public_key, "test_public_key").expect("Failed to write public key");
EnvironmentCreationConfig::new(
EnvironmentSection {
name: env_name.to_string(),
description: None,
instance_name: None, // Auto-generate from environment name
},
SshCredentialsConfig::new(
private_key.to_string_lossy().to_string(),
public_key.to_string_lossy().to_string(),
"torrust".to_string(),
22,
),
ProviderSection::Lxd(LxdProviderSection {
profile_name: format!("lxd-{env_name}"),
}),
TrackerSection::default(),
None,
None,
None, // HTTPS configuration
None, // Backup configuration
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_should_build_command_with_defaults() {
let (command, _temp_dir) = CreateCommandHandlerTestBuilder::new().build();
// Verify command is created (basic smoke test)
assert_eq!(Arc::strong_count(&command.environment_repository), 1);
}
#[test]
fn it_should_build_command_with_custom_time() {
use chrono::TimeZone;
let fixed_time = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap();
let (command, _temp_dir) = CreateCommandHandlerTestBuilder::new()
.with_fixed_time(fixed_time)
.build();
// The clock should be set to the fixed time
assert_eq!(command.clock.now(), fixed_time);
}
#[test]
fn it_should_build_command_with_existing_environments() {
let (command, _temp_dir) = CreateCommandHandlerTestBuilder::new()
.with_existing_environment("production")
.with_existing_environment("staging")
.build();
// Verify environments exist in repository
let prod_name = EnvironmentName::new("production").unwrap();
let staging_name = EnvironmentName::new("staging").unwrap();
assert!(command.environment_repository.exists(&prod_name).unwrap());
assert!(command
.environment_repository
.exists(&staging_name)
.unwrap());
}
#[test]
fn it_should_create_valid_test_config() {
let temp_dir = TempDir::new().unwrap();
let config = create_valid_test_config(&temp_dir, "test-env");
assert_eq!(config.environment.name, "test-env");
assert_eq!(config.ssh_credentials.username, "torrust");
assert_eq!(config.ssh_credentials.port, 22);
// Verify SSH key files were created
let private_key = temp_dir.path().join("id_rsa");
let public_key = temp_dir.path().join("id_rsa.pub");
assert!(private_key.exists());
assert!(public_key.exists());
}
}