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_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}