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