torrust_tracker_deployer_lib/domain/environment/state/
configure_failed.rs1use serde::{Deserialize, Serialize};
15
16use crate::domain::environment::state::{AnyEnvironmentState, BaseFailureContext, StateTypeError};
17use crate::domain::environment::Environment;
18use crate::shared::ErrorKind;
19
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29pub struct ConfigureFailureContext {
30 pub failed_step: ConfigureStep,
32
33 pub error_kind: ErrorKind,
35
36 #[serde(flatten)]
38 pub base: BaseFailureContext,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
43pub enum ConfigureStep {
44 InstallDocker,
46 InstallDockerCompose,
48 ConfigureSecurityUpdates,
50 ConfigureFirewall,
52}
53
54#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
65pub struct ConfigureFailed {
66 pub context: ConfigureFailureContext,
68}
69
70impl Environment<ConfigureFailed> {
72 #[must_use]
74 pub fn into_any(self) -> AnyEnvironmentState {
75 AnyEnvironmentState::ConfigureFailed(self)
76 }
77}
78
79impl AnyEnvironmentState {
81 pub fn try_into_configure_failed(self) -> Result<Environment<ConfigureFailed>, StateTypeError> {
87 match self {
88 Self::ConfigureFailed(env) => Ok(env),
89 other => Err(StateTypeError::UnexpectedState {
90 expected: "configure_failed",
91 actual: other.state_name().to_string(),
92 }),
93 }
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use crate::domain::environment::TraceId;
101 use chrono::Utc;
102 use std::time::Duration;
103
104 fn create_test_context() -> ConfigureFailureContext {
105 ConfigureFailureContext {
106 failed_step: ConfigureStep::InstallDocker,
107 error_kind: ErrorKind::CommandExecution,
108 base: BaseFailureContext {
109 error_summary: "Docker installation failed".to_string(),
110 failed_at: Utc::now(),
111 execution_started_at: Utc::now(),
112 execution_duration: Duration::from_secs(15),
113 trace_id: TraceId::new(),
114 trace_file_path: None,
115 },
116 }
117 }
118
119 #[test]
120 fn it_should_create_configure_failed_state_with_context() {
121 let context = create_test_context();
122 let state = ConfigureFailed {
123 context: context.clone(),
124 };
125 assert_eq!(state.context.failed_step, ConfigureStep::InstallDocker);
126 assert_eq!(state.context.error_kind, ErrorKind::CommandExecution);
127 }
128
129 #[test]
130 fn it_should_serialize_configure_failed_state_to_json() {
131 let state = ConfigureFailed {
132 context: create_test_context(),
133 };
134 let json = serde_json::to_string(&state).unwrap();
135 assert!(json.contains("InstallDocker"));
136 assert!(json.contains("CommandExecution"));
137 }
138
139 #[test]
140 fn it_should_deserialize_configure_failed_state_from_json() {
141 let state = ConfigureFailed {
142 context: create_test_context(),
143 };
144 let json = serde_json::to_string(&state).unwrap();
145 let deserialized: ConfigureFailed = serde_json::from_str(&json).unwrap();
146 assert_eq!(
147 deserialized.context.failed_step,
148 ConfigureStep::InstallDocker
149 );
150 }
151
152 mod conversion_tests {
153 use super::*;
154 use crate::adapters::ssh::SshCredentials;
155 use crate::domain::environment::name::EnvironmentName;
156 use crate::domain::environment::runtime_outputs::ProvisionMethod;
157 use crate::domain::provider::{LxdConfig, ProviderConfig};
158 use crate::domain::ProfileName;
159 use crate::shared::Username;
160 use std::net::{IpAddr, Ipv4Addr};
161 use std::path::PathBuf;
162
163 fn default_lxd_provider_config(env_name: &EnvironmentName) -> ProviderConfig {
164 ProviderConfig::Lxd(LxdConfig {
165 profile_name: ProfileName::new(format!("lxd-{}", env_name.as_str())).unwrap(),
166 })
167 }
168
169 fn create_test_ssh_credentials() -> SshCredentials {
170 let username = Username::new("test-user".to_string()).unwrap();
171 SshCredentials::new(
172 PathBuf::from("/tmp/test_key"),
173 PathBuf::from("/tmp/test_key.pub"),
174 username,
175 )
176 }
177
178 fn create_test_environment_configure_failed() -> Environment<ConfigureFailed> {
179 let name = EnvironmentName::new("test-env".to_string()).unwrap();
180 let ssh_creds = create_test_ssh_credentials();
181 Environment::new(
182 name.clone(),
183 default_lxd_provider_config(&name),
184 ssh_creds,
185 22,
186 chrono::Utc::now(),
187 )
188 .start_provisioning()
189 .provisioned(
190 IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)),
191 ProvisionMethod::Provisioned,
192 )
193 .start_configuring()
194 .configure_failed(super::create_test_context())
195 }
196
197 #[test]
198 fn it_should_convert_configure_failed_environment_into_any() {
199 let env = create_test_environment_configure_failed();
200 let any_env = env.into_any();
201 assert!(matches!(any_env, AnyEnvironmentState::ConfigureFailed(_)));
202 }
203
204 #[test]
205 fn it_should_convert_any_to_configure_failed_successfully() {
206 let env = create_test_environment_configure_failed();
207 let any_env = env.into_any();
208 let result = any_env.try_into_configure_failed();
209 assert!(result.is_ok());
210 }
211 }
212
213 mod context_tests {
214 use super::*;
215
216 #[test]
217 fn it_should_serialize_configure_failure_context() {
218 let context = ConfigureFailureContext {
219 failed_step: ConfigureStep::InstallDocker,
220 error_kind: ErrorKind::CommandExecution,
221 base: BaseFailureContext {
222 error_summary: "Docker installation failed".to_string(),
223 failed_at: Utc::now(),
224 execution_started_at: Utc::now(),
225 execution_duration: Duration::from_secs(15),
226 trace_id: TraceId::new(),
227 trace_file_path: None,
228 },
229 };
230
231 let json = serde_json::to_string(&context).unwrap();
232 assert!(json.contains("InstallDocker"));
233 assert!(json.contains("CommandExecution"));
234 }
235
236 #[test]
237 fn it_should_deserialize_configure_failure_context() {
238 let trace_id = TraceId::new();
239 let json = format!(
240 r#"{{
241 "failed_step": "InstallDockerCompose",
242 "error_kind": "CommandExecution",
243 "error_summary": "Command execution failed",
244 "failed_at": "2025-10-06T10:00:00Z",
245 "execution_started_at": "2025-10-06T09:59:30Z",
246 "execution_duration": {{"secs": 30, "nanos": 0}},
247 "trace_id": "{trace_id}",
248 "trace_file_path": null
249 }}"#
250 );
251
252 let context: ConfigureFailureContext = serde_json::from_str(&json).unwrap();
253 assert_eq!(context.failed_step, ConfigureStep::InstallDockerCompose);
254 assert_eq!(context.error_kind, ErrorKind::CommandExecution);
255 }
256 }
257}