Skip to main content

torrust_tracker_deployer_lib/domain/environment/state/
released.rs

1//! Released State
2//!
3//! Final state - Release preparation completed successfully
4//!
5//! The environment is fully prepared and ready to run the application.
6//!
7//! **Valid Transitions:**
8//! - `Running` (start application)
9
10use serde::{Deserialize, Serialize};
11
12use crate::domain::environment::runtime_outputs::ServiceEndpoints;
13use crate::domain::environment::state::{AnyEnvironmentState, Running, StateTypeError};
14use crate::domain::environment::Environment;
15
16/// Final state - Release preparation completed successfully
17///
18/// The environment is fully prepared and ready to run the application.
19///
20/// **Valid Transitions:**
21/// - `Running` (start application)
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23pub struct Released;
24
25// State transition implementations
26impl Environment<Released> {
27    /// Transitions from Released to Running state with service endpoints
28    ///
29    /// This method indicates that the application has started running and stores
30    /// the service endpoints for later display.
31    ///
32    /// # Arguments
33    ///
34    /// * `service_endpoints` - The URLs for all running services
35    #[must_use]
36    pub fn start_running_with_endpoints(
37        mut self,
38        service_endpoints: ServiceEndpoints,
39    ) -> Environment<Running> {
40        self.context
41            .runtime_outputs
42            .record_services_started(service_endpoints);
43        self.with_state(Running)
44    }
45
46    /// Transitions from Released to Running state
47    ///
48    /// This method indicates that the application has started running.
49    /// Consider using `start_running_with_endpoints` to also store service URLs.
50    #[must_use]
51    pub fn start_running(self) -> Environment<Running> {
52        self.with_state(Running)
53    }
54}
55
56// Type Erasure: Typed → Runtime conversion (into_any)
57impl Environment<Released> {
58    /// Converts typed `Environment<Released>` into type-erased `AnyEnvironmentState`
59    #[must_use]
60    pub fn into_any(self) -> AnyEnvironmentState {
61        AnyEnvironmentState::Released(self)
62    }
63}
64
65// Type Restoration: Runtime → Typed conversion (try_into_released)
66impl AnyEnvironmentState {
67    /// Attempts to convert `AnyEnvironmentState` to `Environment<Released>`
68    ///
69    /// # Errors
70    ///
71    /// Returns `StateTypeError::UnexpectedState` if the environment is not in `Released` state.
72    pub fn try_into_released(self) -> Result<Environment<Released>, StateTypeError> {
73        match self {
74            Self::Released(env) => Ok(env),
75            other => Err(StateTypeError::UnexpectedState {
76                expected: "released",
77                actual: other.state_name().to_string(),
78            }),
79        }
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn it_should_create_released_state() {
89        let state = Released;
90        assert_eq!(state, Released);
91    }
92
93    mod conversion_tests {
94        use super::*;
95        use crate::adapters::ssh::SshCredentials;
96        use crate::domain::environment::name::EnvironmentName;
97        use crate::domain::environment::runtime_outputs::ProvisionMethod;
98        use crate::domain::provider::{LxdConfig, ProviderConfig};
99        use crate::domain::ProfileName;
100        use crate::shared::Username;
101        use std::net::{IpAddr, Ipv4Addr};
102        use std::path::PathBuf;
103
104        fn default_lxd_provider_config(env_name: &EnvironmentName) -> ProviderConfig {
105            ProviderConfig::Lxd(LxdConfig {
106                profile_name: ProfileName::new(format!("lxd-{}", env_name.as_str())).unwrap(),
107            })
108        }
109
110        fn create_test_ssh_credentials() -> SshCredentials {
111            let username = Username::new("test-user".to_string()).unwrap();
112            SshCredentials::new(
113                PathBuf::from("/tmp/test_key"),
114                PathBuf::from("/tmp/test_key.pub"),
115                username,
116            )
117        }
118
119        fn create_test_environment_released() -> Environment<Released> {
120            let name = EnvironmentName::new("test-env".to_string()).unwrap();
121            let ssh_creds = create_test_ssh_credentials();
122            Environment::new(
123                name.clone(),
124                default_lxd_provider_config(&name),
125                ssh_creds,
126                22,
127                chrono::Utc::now(),
128            )
129            .start_provisioning()
130            .provisioned(
131                IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)),
132                ProvisionMethod::Provisioned,
133            )
134            .start_configuring()
135            .configured()
136            .start_releasing()
137            .released()
138        }
139
140        #[test]
141        fn it_should_convert_released_environment_into_any() {
142            let env = create_test_environment_released();
143            let any_env = env.into_any();
144            assert!(matches!(any_env, AnyEnvironmentState::Released(_)));
145        }
146
147        #[test]
148        fn it_should_convert_any_to_released_successfully() {
149            let env = create_test_environment_released();
150            let any_env = env.into_any();
151            let result = any_env.try_into_released();
152            assert!(result.is_ok());
153        }
154    }
155
156    mod transition_tests {
157        use super::*;
158        use crate::adapters::ssh::SshCredentials;
159        use crate::domain::environment::name::EnvironmentName;
160        use crate::domain::environment::runtime_outputs::ProvisionMethod;
161        use crate::domain::environment::state::Running;
162        use crate::domain::provider::{LxdConfig, ProviderConfig};
163        use crate::domain::ProfileName;
164        use crate::shared::Username;
165        use std::net::{IpAddr, Ipv4Addr};
166        use std::path::PathBuf;
167
168        fn default_lxd_provider_config(env_name: &EnvironmentName) -> ProviderConfig {
169            ProviderConfig::Lxd(LxdConfig {
170                profile_name: ProfileName::new(format!("lxd-{}", env_name.as_str())).unwrap(),
171            })
172        }
173
174        fn create_test_environment() -> Environment<Released> {
175            let env_name = EnvironmentName::new("test-state".to_string()).unwrap();
176            let ssh_username = Username::new("torrust".to_string()).unwrap();
177            let ssh_credentials = SshCredentials::new(
178                PathBuf::from("test_key"),
179                PathBuf::from("test_key.pub"),
180                ssh_username,
181            );
182            Environment::new(
183                env_name.clone(),
184                default_lxd_provider_config(&env_name),
185                ssh_credentials,
186                22,
187                chrono::Utc::now(),
188            )
189            .start_provisioning()
190            .provisioned(
191                IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)),
192                ProvisionMethod::Provisioned,
193            )
194            .start_configuring()
195            .configured()
196            .start_releasing()
197            .released()
198        }
199
200        #[test]
201        fn it_should_transition_from_released_to_running() {
202            let env = create_test_environment();
203            let env = env.start_running();
204
205            assert_eq!(*env.state(), Running);
206            assert_eq!(env.name().as_str(), "test-state");
207        }
208    }
209}