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