greentic_setup/platform_setup/
mod.rs1mod persistence;
4mod prompts;
5mod types;
6mod url;
7
8pub use persistence::{
10 load_effective_static_routes_defaults, load_runtime_local_base_url,
11 load_runtime_public_base_url, load_static_routes_artifact, load_telemetry_artifact,
12 load_tunnel_artifact, load_tunnel_handoff_artifact, persist_static_routes_artifact,
13 persist_telemetry_artifact, persist_tunnel_artifact, persist_tunnel_handoff_artifact,
14 static_routes_artifact_path, telemetry_artifact_path, tunnel_artifact_path,
15 tunnel_handoff_artifact_path,
16};
17pub use prompts::{
18 prompt_static_routes_policy, prompt_static_routes_policy_with_answers, prompt_tunnel_mode,
19};
20pub use types::{
21 PlatformSetupAnswers, StaticRoutesAnswers, StaticRoutesPolicy, TelemetryAnswers, TunnelAnswers,
22 TunnelHandoff,
23};
24
25#[cfg(test)]
26mod tests {
27 use super::prompts::merge_prompt_seed;
28 use super::types::{
29 PACK_DECLARED_POLICY, STATIC_ROUTES_VERSION, SURFACE_DISABLED, SURFACE_ENABLED,
30 StaticRoutesAnswers, StaticRoutesPolicy,
31 };
32 use super::{
33 load_effective_static_routes_defaults, load_runtime_local_base_url,
34 load_tunnel_handoff_artifact, persist_static_routes_artifact,
35 persist_tunnel_handoff_artifact, tunnel_handoff_artifact_path,
36 };
37
38 #[test]
39 fn disabled_is_default() {
40 let policy = StaticRoutesPolicy::normalize(None, "dev").unwrap();
41 assert_eq!(policy, StaticRoutesPolicy::disabled());
42 }
43
44 #[test]
45 fn enabled_requires_base_url() {
46 let err = StaticRoutesPolicy::normalize(
47 Some(&StaticRoutesAnswers {
48 public_web_enabled: Some(true),
49 ..Default::default()
50 }),
51 "dev",
52 )
53 .unwrap_err();
54 assert!(err.to_string().contains("public_base_url is required"));
55 }
56
57 #[test]
58 fn normalizes_public_base_url() {
59 let policy = StaticRoutesPolicy::normalize(
60 Some(&StaticRoutesAnswers {
61 public_web_enabled: Some(true),
62 public_base_url: Some("https://example.com/base/".into()),
63 ..Default::default()
64 }),
65 "prod",
66 )
67 .unwrap();
68 assert_eq!(
69 policy.public_base_url.as_deref(),
70 Some("https://example.com/base")
71 );
72 assert_eq!(policy.public_surface_policy, SURFACE_ENABLED);
73 assert_eq!(policy.default_route_prefix_policy, PACK_DECLARED_POLICY);
74 assert_eq!(policy.tenant_path_policy, PACK_DECLARED_POLICY);
75 }
76
77 #[test]
78 fn rejects_query_and_fragment() {
79 let err = StaticRoutesPolicy::normalize(
80 Some(&StaticRoutesAnswers {
81 public_web_enabled: Some(true),
82 public_base_url: Some("https://example.com?x=1".into()),
83 ..Default::default()
84 }),
85 "prod",
86 )
87 .unwrap_err();
88 assert!(err.to_string().contains("query string"));
89
90 let err = StaticRoutesPolicy::normalize(
91 Some(&StaticRoutesAnswers {
92 public_web_enabled: Some(true),
93 public_base_url: Some("https://example.com#frag".into()),
94 ..Default::default()
95 }),
96 "prod",
97 )
98 .unwrap_err();
99 assert!(err.to_string().contains("fragment"));
100 }
101
102 #[test]
103 fn allows_http_loopback_in_dev_only() {
104 let policy = StaticRoutesPolicy::normalize(
105 Some(&StaticRoutesAnswers {
106 public_web_enabled: Some(true),
107 public_base_url: Some("http://127.0.0.1:3000/".into()),
108 ..Default::default()
109 }),
110 "dev",
111 )
112 .unwrap();
113 assert_eq!(
114 policy.public_base_url.as_deref(),
115 Some("http://127.0.0.1:3000")
116 );
117
118 let err = StaticRoutesPolicy::normalize(
119 Some(&StaticRoutesAnswers {
120 public_web_enabled: Some(true),
121 public_base_url: Some("http://127.0.0.1:3000".into()),
122 ..Default::default()
123 }),
124 "prod",
125 )
126 .unwrap_err();
127 assert!(err.to_string().contains("dev"));
128 }
129
130 #[test]
131 fn rejects_enabled_with_disabled_surface_policy() {
132 let err = StaticRoutesPolicy::normalize(
133 Some(&StaticRoutesAnswers {
134 public_web_enabled: Some(true),
135 public_base_url: Some("https://example.com".into()),
136 public_surface_policy: Some("disabled".into()),
137 ..Default::default()
138 }),
139 "prod",
140 )
141 .unwrap_err();
142 assert!(err.to_string().contains("incompatible"));
143 }
144
145 #[test]
146 fn persists_and_loads_artifact() {
147 let temp = tempfile::tempdir().unwrap();
148 let policy = StaticRoutesPolicy::normalize(
149 Some(&StaticRoutesAnswers {
150 public_web_enabled: Some(true),
151 public_base_url: Some("https://example.com".into()),
152 ..Default::default()
153 }),
154 "prod",
155 )
156 .unwrap();
157 let path = persist_static_routes_artifact(temp.path(), &policy).unwrap();
158 assert!(path.exists());
159 let loaded = super::load_static_routes_artifact(temp.path())
160 .unwrap()
161 .unwrap();
162 assert_eq!(loaded, policy);
163 }
164
165 #[test]
166 fn effective_defaults_fall_back_to_runtime_endpoint() {
167 let temp = tempfile::tempdir().unwrap();
168 let runtime_dir = temp
169 .path()
170 .join("state")
171 .join("runtime")
172 .join("demo.default");
173 std::fs::create_dir_all(&runtime_dir).unwrap();
174 std::fs::write(
175 runtime_dir.join("endpoints.json"),
176 r#"{"tenant":"demo","team":"default","public_base_url":"https://runtime.example.com"}"#,
177 )
178 .unwrap();
179
180 let loaded =
181 load_effective_static_routes_defaults(temp.path(), "demo", Some("default")).unwrap();
182 assert_eq!(
183 loaded.and_then(|policy| policy.public_base_url),
184 Some("https://runtime.example.com".to_string())
185 );
186 }
187
188 #[test]
189 fn runtime_local_base_url_uses_gateway_endpoint() {
190 let temp = tempfile::tempdir().unwrap();
191 let runtime_dir = temp
192 .path()
193 .join("state")
194 .join("runtime")
195 .join("demo.default");
196 std::fs::create_dir_all(&runtime_dir).unwrap();
197 std::fs::write(
198 runtime_dir.join("endpoints.json"),
199 r#"{"tenant":"demo","team":"default","gateway_listen_addr":"127.0.0.1","gateway_port":8081}"#,
200 )
201 .unwrap();
202
203 assert_eq!(
204 load_runtime_local_base_url(temp.path(), "demo", Some("default")).unwrap(),
205 Some("http://127.0.0.1:8081".to_string())
206 );
207 }
208
209 #[test]
210 fn tunnel_handoff_round_trips_through_the_bundle_artifact() {
211 let temp = tempfile::tempdir().unwrap();
212 assert_eq!(load_tunnel_handoff_artifact(temp.path()).unwrap(), None);
213
214 let handoff = super::TunnelHandoff {
215 service: "cloudflared".to_string(),
216 local_port: 8080,
217 public_base_url: "https://survey-revenues.trycloudflare.com".to_string(),
218 };
219 persist_tunnel_handoff_artifact(temp.path(), &handoff).unwrap();
220
221 assert_eq!(
222 load_tunnel_handoff_artifact(temp.path()).unwrap(),
223 Some(handoff)
224 );
225 assert!(
228 tunnel_handoff_artifact_path(temp.path())
229 .parent()
230 .unwrap()
231 .ends_with("state/config/platform")
232 );
233 }
234
235 #[test]
236 fn merge_prompt_seed_overlays_partial_answers_on_existing_policy() {
237 let existing = StaticRoutesPolicy {
238 version: STATIC_ROUTES_VERSION,
239 public_web_enabled: false,
240 public_base_url: Some("https://existing.example.com".into()),
241 public_surface_policy: SURFACE_DISABLED.into(),
242 default_route_prefix_policy: PACK_DECLARED_POLICY.into(),
243 tenant_path_policy: PACK_DECLARED_POLICY.into(),
244 };
245 let answers = StaticRoutesAnswers {
246 public_web_enabled: Some(true),
247 public_base_url: None,
248 public_surface_policy: Some(SURFACE_ENABLED.into()),
249 default_route_prefix_policy: None,
250 tenant_path_policy: None,
251 };
252
253 let merged = merge_prompt_seed(Some(&answers), Some(&existing));
254 assert!(merged.public_web_enabled);
255 assert_eq!(
256 merged.public_base_url.as_deref(),
257 Some("https://existing.example.com")
258 );
259 assert_eq!(merged.public_surface_policy, SURFACE_ENABLED);
260 }
261}