torrust_tracker_deployer_lib/application/command_handlers/validate/
handler.rs1use std::convert::TryInto;
7use std::fs;
8use std::path::Path;
9
10use crate::application::command_handlers::create::config::EnvironmentCreationConfig;
11use crate::domain::environment::EnvironmentParams;
12
13use super::errors::ValidateCommandHandlerError;
14
15pub struct ValidateCommandHandler;
23
24impl ValidateCommandHandler {
25 #[must_use]
27 pub fn new() -> Self {
28 Self
29 }
30
31 pub fn validate(
72 &self,
73 config_path: &Path,
74 ) -> Result<ValidationResult, ValidateCommandHandlerError> {
75 let content = fs::read_to_string(config_path).map_err(|source| {
77 ValidateCommandHandlerError::FileReadFailed {
78 path: config_path.to_path_buf(),
79 source,
80 }
81 })?;
82
83 let config: EnvironmentCreationConfig =
86 serde_json::from_str(&content).map_err(|source| {
87 ValidateCommandHandlerError::JsonParsingFailed {
88 path: config_path.to_path_buf(),
89 source,
90 }
91 })?;
92
93 let _validated_params: EnvironmentParams = config
100 .clone()
101 .try_into()
102 .map_err(ValidateCommandHandlerError::DomainValidationFailed)?;
103
104 Ok(ValidationResult {
106 environment_name: config.environment.name.clone(),
107 provider: config.provider.provider().to_string(),
108 has_prometheus: config.prometheus.is_some(),
109 has_grafana: config.grafana.is_some(),
110 has_https: config.https.is_some(),
111 has_backup: config.backup.is_some(),
112 })
113 }
114}
115
116impl Default for ValidateCommandHandler {
117 fn default() -> Self {
118 Self::new()
119 }
120}
121
122#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)]
128pub struct ValidationResult {
129 pub environment_name: String,
131
132 pub provider: String,
134
135 pub has_prometheus: bool,
137
138 pub has_grafana: bool,
140
141 pub has_https: bool,
143
144 pub has_backup: bool,
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use std::env;
152 use std::fs;
153 use tempfile::TempDir;
154
155 #[test]
156 fn it_should_validate_valid_configuration_when_all_fields_are_correct() {
157 let handler = ValidateCommandHandler::new();
158
159 let temp_dir = TempDir::new().expect("Failed to create temp directory");
161 let config_path = temp_dir.path().join("test-config.json");
162
163 let project_root = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
165 let private_key_path = format!("{project_root}/fixtures/testing_rsa");
166 let public_key_path = format!("{project_root}/fixtures/testing_rsa.pub");
167
168 let config_json = format!(
170 r#"{{
171 "environment": {{
172 "name": "test-validation"
173 }},
174 "ssh_credentials": {{
175 "private_key_path": "{private_key_path}",
176 "public_key_path": "{public_key_path}",
177 "username": "torrust",
178 "port": 22
179 }},
180 "provider": {{
181 "provider": "lxd",
182 "profile_name": "test-profile"
183 }},
184 "tracker": {{
185 "core": {{
186 "database": {{
187 "driver": "sqlite3",
188 "database_name": "tracker.db"
189 }},
190 "private": false
191 }},
192 "udp_trackers": [
193 {{
194 "bind_address": "0.0.0.0:6969",
195 "domain": "udp.tracker.local"
196 }}
197 ],
198 "http_trackers": [
199 {{
200 "bind_address": "0.0.0.0:7070",
201 "domain": "http.tracker.local"
202 }}
203 ],
204 "http_api": {{
205 "bind_address": "0.0.0.0:1212",
206 "admin_token": "MyAccessToken",
207 "domain": "api.tracker.local"
208 }},
209 "health_check_api": {{
210 "bind_address": "0.0.0.0:1313",
211 "domain": "health.tracker.local"
212 }}
213 }},
214 "grafana": {{
215 "admin_user": "admin",
216 "admin_password": "admin-password",
217 "domain": "grafana.tracker.local"
218 }},
219 "prometheus": {{
220 "scrape_interval_in_secs": 15
221 }}
222}}"#
223 );
224
225 fs::write(&config_path, config_json).expect("Failed to write test config");
226
227 let result = handler.validate(&config_path);
229
230 assert!(result.is_ok(), "Valid configuration should pass validation");
231 }
232
233 #[test]
234 fn it_should_return_error_when_file_does_not_exist() {
235 let handler = ValidateCommandHandler::new();
236
237 let result = handler.validate(Path::new("/tmp/nonexistent.json"));
238
239 assert!(
240 matches!(
241 result,
242 Err(ValidateCommandHandlerError::FileReadFailed { .. })
243 ),
244 "Non-existent file should return FileReadFailed error"
245 );
246 }
247
248 #[test]
249 fn it_should_return_error_when_json_is_malformed() {
250 let handler = ValidateCommandHandler::new();
251
252 let temp_file = std::env::temp_dir().join("invalid.json");
254 fs::write(&temp_file, "{ invalid json }").unwrap();
255
256 let result = handler.validate(&temp_file);
257
258 drop(fs::remove_file(temp_file));
260
261 assert!(
262 matches!(
263 result,
264 Err(ValidateCommandHandlerError::JsonParsingFailed { .. })
265 ),
266 "Malformed JSON should return JsonParsingFailed error"
267 );
268 }
269}