-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathmod.rs
More file actions
373 lines (340 loc) · 11.5 KB
/
Copy pathmod.rs
File metadata and controls
373 lines (340 loc) · 11.5 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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
//! Shared Test Utilities for Command Handlers
//!
//! This module provides common test utilities, fixtures, and helpers for testing
//! command handlers in the presentation layer. It reduces duplication and ensures
//! consistent test patterns across all commands.
//!
//! # Purpose
//!
//! - Provides shared test context with temporary directory management
//! - Offers reusable test configuration generators
//! - Ensures consistent test setup across command tests
//! - Reduces code duplication in test modules
//!
//! # Usage
//!
//! ```rust,no_run
//! use torrust_tracker_deployer_lib::presentation::commands::tests::{TestContext, create_valid_config};
//!
//! fn example_usage() {
//! let context = TestContext::new();
//! let config_path = create_valid_config(context.working_dir(), "test-env");
//!
//! // Use the config path for testing
//! assert!(config_path.exists());
//! }
//! ```
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use tempfile::TempDir;
use crate::presentation::user_output::{UserOutput, VerbosityLevel};
// ============================================================================
// PUBLIC API - Test Context
// ============================================================================
/// Test context with temporary directory and common test dependencies
///
/// Provides a clean, isolated testing environment with automatic cleanup.
/// The temporary directory is automatically deleted when the context is dropped.
///
/// # Example
///
/// ```rust,no_run
/// use torrust_tracker_deployer_lib::presentation::commands::tests::TestContext;
///
/// fn example_usage() {
/// let context = TestContext::new();
///
/// // Use the working directory for tests
/// let config_path = context.working_dir().join("config.json");
/// std::fs::write(&config_path, "{}").unwrap();
///
/// assert!(config_path.exists());
/// // Cleanup happens automatically when context is dropped
/// }
/// ```
pub struct TestContext {
_temp_dir: TempDir,
working_dir: PathBuf,
user_output: Arc<Mutex<UserOutput>>,
}
impl TestContext {
/// Create a new test context with a temporary working directory
///
/// # Panics
///
/// Panics if the temporary directory cannot be created
#[must_use]
pub fn new() -> Self {
let temp_dir = TempDir::new().expect("Failed to create temporary directory");
let working_dir = temp_dir.path().to_path_buf();
let user_output = Arc::new(Mutex::new(UserOutput::new(VerbosityLevel::Normal)));
Self {
_temp_dir: temp_dir,
working_dir,
user_output,
}
}
/// Get the working directory path for this test context
///
/// The working directory is guaranteed to exist and will be automatically
/// cleaned up when the context is dropped.
#[must_use]
pub fn working_dir(&self) -> &Path {
&self.working_dir
}
/// Get a reference to the shared user output
#[must_use]
pub fn user_output(&self) -> Arc<Mutex<UserOutput>> {
Arc::clone(&self.user_output)
}
}
impl Default for TestContext {
fn default() -> Self {
Self::new()
}
}
// ============================================================================
// PUBLIC API - Configuration Generators
// ============================================================================
/// Create a valid environment configuration JSON file
///
/// This function creates a properly formatted configuration file with valid
/// SSH credentials pointing to test fixtures. The generated configuration
/// can be used for testing successful environment creation flows.
///
/// # Arguments
///
/// * `path` - Directory where the config file should be created
/// * `env_name` - Name of the environment (must be valid)
///
/// # Returns
///
/// Returns the full path to the created configuration file
///
/// # Panics
///
/// Panics if the config file cannot be written to the filesystem
///
/// # Example
///
/// ```rust,no_run
/// use tempfile::TempDir;
/// use torrust_tracker_deployer_lib::presentation::commands::tests::create_valid_config;
///
/// let temp_dir = TempDir::new().unwrap();
/// let config_path = create_valid_config(temp_dir.path(), "my-env");
///
/// assert!(config_path.exists());
/// // Config can now be used with config loaders
/// ```
#[must_use]
pub fn create_valid_config(path: &Path, env_name: &str) -> PathBuf {
// Use absolute paths to SSH keys to ensure they work regardless of current directory
let project_root = env!("CARGO_MANIFEST_DIR");
let private_key_path = format!("{project_root}/fixtures/testing_rsa");
let public_key_path = format!("{project_root}/fixtures/testing_rsa.pub");
let config_json = format!(
r#"{{
"environment": {{
"name": "{env_name}"
}},
"ssh_credentials": {{
"private_key_path": "{private_key_path}",
"public_key_path": "{public_key_path}"
}}
}}"#
);
let config_path = path.join("config.json");
fs::write(&config_path, config_json).expect("Failed to write config file");
config_path
}
/// Create an invalid JSON configuration file for error testing
///
/// This function creates a malformed JSON file to test error handling
/// for JSON parsing failures. The file is syntactically invalid.
///
/// # Arguments
///
/// * `path` - Directory where the config file should be created
///
/// # Returns
///
/// Returns the full path to the created configuration file
///
/// # Panics
///
/// Panics if the config file cannot be written to the filesystem
///
/// # Example
///
/// ```rust,no_run
/// use tempfile::TempDir;
/// use torrust_tracker_deployer_lib::presentation::commands::tests::create_invalid_json_config;
///
/// let temp_dir = TempDir::new().unwrap();
/// let config_path = create_invalid_json_config(temp_dir.path());
///
/// // This file will contain invalid JSON for testing error handling
/// ```
#[must_use]
pub fn create_invalid_json_config(path: &Path) -> PathBuf {
let invalid_json = r#"{"environment": {"name": "test"#; // Missing closing braces
let config_path = path.join("invalid.json");
fs::write(&config_path, invalid_json).expect("Failed to write invalid config file");
config_path
}
/// Create a configuration with an invalid environment name
///
/// This function creates a valid JSON file but with an environment name
/// that violates naming rules (contains underscores). Useful for testing
/// validation logic.
///
/// # Arguments
///
/// * `path` - Directory where the config file should be created
///
/// # Returns
///
/// Returns the full path to the created configuration file
///
/// # Panics
///
/// Panics if the config file cannot be written to the filesystem
///
/// # Example
///
/// ```rust,no_run
/// use tempfile::TempDir;
/// use torrust_tracker_deployer_lib::presentation::commands::tests::create_config_with_invalid_name;
///
/// let temp_dir = TempDir::new().unwrap();
/// let config_path = create_config_with_invalid_name(temp_dir.path());
///
/// // Config contains an invalid environment name for testing validation
/// ```
#[must_use]
pub fn create_config_with_invalid_name(path: &Path) -> PathBuf {
// Use absolute paths to SSH keys to ensure they work regardless of current directory
let project_root = env!("CARGO_MANIFEST_DIR");
let private_key_path = format!("{project_root}/fixtures/testing_rsa");
let public_key_path = format!("{project_root}/fixtures/testing_rsa.pub");
let config_json = format!(
r#"{{
"environment": {{
"name": "Invalid_Name_With_Underscore"
}},
"ssh_credentials": {{
"private_key_path": "{private_key_path}",
"public_key_path": "{public_key_path}"
}}
}}"#
);
let config_path = path.join("invalid_name.json");
fs::write(&config_path, config_json).expect("Failed to write config file with invalid name");
config_path
}
/// Create a configuration with missing SSH key files
///
/// This function creates a valid JSON configuration that references
/// non-existent SSH key files. Useful for testing file validation logic.
///
/// # Arguments
///
/// * `path` - Directory where the config file should be created
///
/// # Returns
///
/// Returns the full path to the created configuration file
///
/// # Panics
///
/// Panics if the config file cannot be written to the filesystem
///
/// # Example
///
/// ```rust,no_run
/// use tempfile::TempDir;
/// use torrust_tracker_deployer_lib::presentation::commands::tests::create_config_with_missing_keys;
///
/// let temp_dir = TempDir::new().unwrap();
/// let config_path = create_config_with_missing_keys(temp_dir.path());
///
/// // Config references non-existent SSH keys for testing validation
/// ```
#[must_use]
pub fn create_config_with_missing_keys(path: &Path) -> PathBuf {
let config_json = r#"{
"environment": {
"name": "test-env"
},
"ssh_credentials": {
"private_key_path": "/nonexistent/private_key",
"public_key_path": "/nonexistent/public_key.pub"
}
}"#;
let config_path = path.join("missing_keys.json");
fs::write(&config_path, config_json).expect("Failed to write config file with missing keys");
config_path
}
// ============================================================================
// TESTS
// ============================================================================
#[cfg(test)]
mod unit_tests {
use super::*;
#[test]
fn it_should_create_test_context_with_working_directory() {
let context = TestContext::new();
assert!(context.working_dir().exists());
assert!(context.working_dir().is_dir());
}
#[test]
fn it_should_clean_up_temp_directory_after_test() {
let temp_path = {
let context = TestContext::new();
let path = context.working_dir().to_path_buf();
assert!(path.exists());
path
}; // context dropped here
// Verify cleanup happened
assert!(
!temp_path.exists(),
"Temporary directory should be cleaned up after context is dropped"
);
}
#[test]
fn it_should_create_valid_config_file() {
let test_context = TestContext::new();
let config_path = create_valid_config(test_context.working_dir(), "test-env");
assert!(config_path.exists());
let content = fs::read_to_string(&config_path).unwrap();
assert!(content.contains("test-env"));
assert!(content.contains("fixtures/testing_rsa"));
}
#[test]
fn it_should_create_invalid_json_config_file() {
let test_context = TestContext::new();
let config_path = create_invalid_json_config(test_context.working_dir());
assert!(config_path.exists());
let content = fs::read_to_string(&config_path).unwrap();
// Verify it's actually invalid JSON
assert!(serde_json::from_str::<serde_json::Value>(&content).is_err());
}
#[test]
fn it_should_create_config_with_invalid_environment_name() {
let test_context = TestContext::new();
let config_path = create_config_with_invalid_name(test_context.working_dir());
assert!(config_path.exists());
let content = fs::read_to_string(&config_path).unwrap();
assert!(content.contains("Invalid_Name_With_Underscore"));
}
#[test]
fn it_should_create_config_with_missing_keys() {
let test_context = TestContext::new();
let config_path = create_config_with_missing_keys(test_context.working_dir());
assert!(config_path.exists());
let content = fs::read_to_string(&config_path).unwrap();
assert!(content.contains("/nonexistent/private_key"));
}
}