Skip to main content

greentic_setup/platform_setup/
mod.rs

1//! Bundle-level platform setup types and static routes policy handling.
2
3mod persistence;
4mod prompts;
5mod types;
6mod url;
7
8// Re-export public types
9pub use persistence::{
10    load_effective_static_routes_defaults, load_runtime_public_base_url,
11    load_static_routes_artifact, load_telemetry_artifact, load_tunnel_artifact,
12    persist_static_routes_artifact, persist_telemetry_artifact, persist_tunnel_artifact,
13    static_routes_artifact_path, telemetry_artifact_path, tunnel_artifact_path,
14};
15pub use prompts::{
16    prompt_static_routes_policy, prompt_static_routes_policy_with_answers, prompt_tunnel_mode,
17};
18pub use types::{
19    PlatformSetupAnswers, StaticRoutesAnswers, StaticRoutesPolicy, TelemetryAnswers, TunnelAnswers,
20};
21
22#[cfg(test)]
23mod tests {
24    use super::prompts::merge_prompt_seed;
25    use super::types::{
26        PACK_DECLARED_POLICY, STATIC_ROUTES_VERSION, SURFACE_DISABLED, SURFACE_ENABLED,
27        StaticRoutesAnswers, StaticRoutesPolicy,
28    };
29    use super::{load_effective_static_routes_defaults, persist_static_routes_artifact};
30
31    #[test]
32    fn disabled_is_default() {
33        let policy = StaticRoutesPolicy::normalize(None, "dev").unwrap();
34        assert_eq!(policy, StaticRoutesPolicy::disabled());
35    }
36
37    #[test]
38    fn enabled_requires_base_url() {
39        let err = StaticRoutesPolicy::normalize(
40            Some(&StaticRoutesAnswers {
41                public_web_enabled: Some(true),
42                ..Default::default()
43            }),
44            "dev",
45        )
46        .unwrap_err();
47        assert!(err.to_string().contains("public_base_url is required"));
48    }
49
50    #[test]
51    fn normalizes_public_base_url() {
52        let policy = StaticRoutesPolicy::normalize(
53            Some(&StaticRoutesAnswers {
54                public_web_enabled: Some(true),
55                public_base_url: Some("https://example.com/base/".into()),
56                ..Default::default()
57            }),
58            "prod",
59        )
60        .unwrap();
61        assert_eq!(
62            policy.public_base_url.as_deref(),
63            Some("https://example.com/base")
64        );
65        assert_eq!(policy.public_surface_policy, SURFACE_ENABLED);
66        assert_eq!(policy.default_route_prefix_policy, PACK_DECLARED_POLICY);
67        assert_eq!(policy.tenant_path_policy, PACK_DECLARED_POLICY);
68    }
69
70    #[test]
71    fn rejects_query_and_fragment() {
72        let err = StaticRoutesPolicy::normalize(
73            Some(&StaticRoutesAnswers {
74                public_web_enabled: Some(true),
75                public_base_url: Some("https://example.com?x=1".into()),
76                ..Default::default()
77            }),
78            "prod",
79        )
80        .unwrap_err();
81        assert!(err.to_string().contains("query string"));
82
83        let err = StaticRoutesPolicy::normalize(
84            Some(&StaticRoutesAnswers {
85                public_web_enabled: Some(true),
86                public_base_url: Some("https://example.com#frag".into()),
87                ..Default::default()
88            }),
89            "prod",
90        )
91        .unwrap_err();
92        assert!(err.to_string().contains("fragment"));
93    }
94
95    #[test]
96    fn allows_http_loopback_in_dev_only() {
97        let policy = StaticRoutesPolicy::normalize(
98            Some(&StaticRoutesAnswers {
99                public_web_enabled: Some(true),
100                public_base_url: Some("http://127.0.0.1:3000/".into()),
101                ..Default::default()
102            }),
103            "dev",
104        )
105        .unwrap();
106        assert_eq!(
107            policy.public_base_url.as_deref(),
108            Some("http://127.0.0.1:3000")
109        );
110
111        let err = StaticRoutesPolicy::normalize(
112            Some(&StaticRoutesAnswers {
113                public_web_enabled: Some(true),
114                public_base_url: Some("http://127.0.0.1:3000".into()),
115                ..Default::default()
116            }),
117            "prod",
118        )
119        .unwrap_err();
120        assert!(err.to_string().contains("dev"));
121    }
122
123    #[test]
124    fn rejects_enabled_with_disabled_surface_policy() {
125        let err = StaticRoutesPolicy::normalize(
126            Some(&StaticRoutesAnswers {
127                public_web_enabled: Some(true),
128                public_base_url: Some("https://example.com".into()),
129                public_surface_policy: Some("disabled".into()),
130                ..Default::default()
131            }),
132            "prod",
133        )
134        .unwrap_err();
135        assert!(err.to_string().contains("incompatible"));
136    }
137
138    #[test]
139    fn persists_and_loads_artifact() {
140        let temp = tempfile::tempdir().unwrap();
141        let policy = StaticRoutesPolicy::normalize(
142            Some(&StaticRoutesAnswers {
143                public_web_enabled: Some(true),
144                public_base_url: Some("https://example.com".into()),
145                ..Default::default()
146            }),
147            "prod",
148        )
149        .unwrap();
150        let path = persist_static_routes_artifact(temp.path(), &policy).unwrap();
151        assert!(path.exists());
152        let loaded = super::load_static_routes_artifact(temp.path())
153            .unwrap()
154            .unwrap();
155        assert_eq!(loaded, policy);
156    }
157
158    #[test]
159    fn effective_defaults_fall_back_to_runtime_endpoint() {
160        let temp = tempfile::tempdir().unwrap();
161        let runtime_dir = temp
162            .path()
163            .join("state")
164            .join("runtime")
165            .join("demo.default");
166        std::fs::create_dir_all(&runtime_dir).unwrap();
167        std::fs::write(
168            runtime_dir.join("endpoints.json"),
169            r#"{"tenant":"demo","team":"default","public_base_url":"https://runtime.example.com"}"#,
170        )
171        .unwrap();
172
173        let loaded =
174            load_effective_static_routes_defaults(temp.path(), "demo", Some("default")).unwrap();
175        assert_eq!(
176            loaded.and_then(|policy| policy.public_base_url),
177            Some("https://runtime.example.com".to_string())
178        );
179    }
180
181    #[test]
182    fn merge_prompt_seed_overlays_partial_answers_on_existing_policy() {
183        let existing = StaticRoutesPolicy {
184            version: STATIC_ROUTES_VERSION,
185            public_web_enabled: false,
186            public_base_url: Some("https://existing.example.com".into()),
187            public_surface_policy: SURFACE_DISABLED.into(),
188            default_route_prefix_policy: PACK_DECLARED_POLICY.into(),
189            tenant_path_policy: PACK_DECLARED_POLICY.into(),
190        };
191        let answers = StaticRoutesAnswers {
192            public_web_enabled: Some(true),
193            public_base_url: None,
194            public_surface_policy: Some(SURFACE_ENABLED.into()),
195            default_route_prefix_policy: None,
196            tenant_path_policy: None,
197        };
198
199        let merged = merge_prompt_seed(Some(&answers), Some(&existing));
200        assert!(merged.public_web_enabled);
201        assert_eq!(
202            merged.public_base_url.as_deref(),
203            Some("https://existing.example.com")
204        );
205        assert_eq!(merged.public_surface_policy, SURFACE_ENABLED);
206    }
207}