Skip to main content

torrust_tracker_deployer_lib/domain/provider/
hetzner.rs

1//! Hetzner Provider Domain Types
2//!
3//! This module contains domain types specific to the Hetzner provider.
4//! Hetzner is used for production deployments, providing cost-effective
5//! cloud infrastructure with good European presence.
6
7use serde::{Deserialize, Serialize};
8
9use crate::shared::ApiToken;
10
11/// Hetzner-specific configuration (Domain Type)
12///
13/// Hetzner is used for production deployments. It provides cost-effective
14/// cloud infrastructure with good European presence.
15///
16/// Note: This struct is defined for enum completeness but will be
17/// fully implemented in Phase 2 (Add Hetzner Provider task).
18///
19/// # Examples
20///
21/// ```rust
22/// use torrust_tracker_deployer_lib::domain::provider::HetznerConfig;
23/// use torrust_tracker_deployer_lib::shared::secrets::ApiToken;
24///
25/// let config = HetznerConfig {
26///     api_token: ApiToken::from("your-api-token"),
27///     server_type: "cx22".to_string(),
28///     location: "nbg1".to_string(),
29///     image: "ubuntu-24.04".to_string(),
30/// };
31/// ```
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33pub struct HetznerConfig {
34    /// Hetzner API token for authentication.
35    ///
36    /// This value is kept secure and not exposed in debug output.
37    pub api_token: ApiToken,
38
39    /// Hetzner server type (e.g., "cx22", "cx32", "cpx11").
40    ///
41    /// Determines the VM specifications (CPU, RAM, storage).
42    /// Note: Future improvement could use a validated `ServerType` type.
43    pub server_type: String,
44
45    /// Hetzner datacenter location (e.g., "fsn1", "nbg1", "hel1").
46    ///
47    /// Determines where the VM will be physically located.
48    /// Note: Future improvement could use a validated `Location` type.
49    pub location: String,
50
51    /// Operating system image (e.g., "ubuntu-24.04", "ubuntu-22.04", "debian-12").
52    ///
53    /// Determines the base operating system for the server.
54    /// Note: Future improvement could use a validated `Image` type.
55    pub image: String,
56}
57
58#[cfg(test)]
59mod tests {
60
61    use super::*;
62
63    fn create_hetzner_config() -> HetznerConfig {
64        HetznerConfig {
65            api_token: ApiToken::from("test-token"),
66            server_type: "cx22".to_string(),
67            location: "nbg1".to_string(),
68            image: "ubuntu-24.04".to_string(),
69        }
70    }
71
72    #[test]
73    fn it_should_store_all_fields_when_created() {
74        let config = HetznerConfig {
75            api_token: ApiToken::from("token123"),
76            server_type: "cx32".to_string(),
77            location: "fsn1".to_string(),
78            image: "ubuntu-22.04".to_string(),
79        };
80        assert_eq!(config.api_token.expose_secret(), "token123");
81        assert_eq!(config.server_type, "cx32");
82        assert_eq!(config.location, "fsn1");
83        assert_eq!(config.image, "ubuntu-22.04");
84    }
85
86    #[test]
87    fn it_should_serialize_to_json_when_valid_config_exists() {
88        let config = create_hetzner_config();
89        let json = serde_json::to_string(&config).unwrap();
90
91        assert!(json.contains("\"api_token\":\"test-token\""));
92        assert!(json.contains("\"server_type\":\"cx22\""));
93        assert!(json.contains("\"location\":\"nbg1\""));
94        assert!(json.contains("\"image\":\"ubuntu-24.04\""));
95    }
96
97    #[test]
98    fn it_should_deserialize_from_json_when_valid_json_provided() {
99        let json = r#"{"api_token":"token","server_type":"cx22","location":"nbg1","image":"ubuntu-24.04"}"#;
100        let config: HetznerConfig = serde_json::from_str(json).unwrap();
101
102        assert_eq!(config.api_token.expose_secret(), "token");
103        assert_eq!(config.server_type, "cx22");
104        assert_eq!(config.location, "nbg1");
105        assert_eq!(config.image, "ubuntu-24.04");
106    }
107
108    #[test]
109    fn it_should_be_cloneable_when_cloned() {
110        let config = create_hetzner_config();
111        let cloned = config.clone();
112        assert_eq!(config, cloned);
113    }
114
115    #[test]
116    fn it_should_implement_debug_trait_when_formatted() {
117        let config = create_hetzner_config();
118        let debug = format!("{config:?}");
119        assert!(debug.contains("HetznerConfig"));
120        assert!(debug.contains("api_token"));
121        assert!(debug.contains("server_type"));
122        assert!(debug.contains("location"));
123        assert!(debug.contains("image"));
124        // API token should be redacted in debug output
125        assert!(debug.contains("[REDACTED]"));
126        assert!(!debug.contains("test-token"));
127    }
128}