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