eggress_testkit/oracle/
profile.rs1use super::report::CertificationProfile;
2use super::scenario::{OracleScenario, ScenarioCategory};
3
4pub const DIFFERENTIAL_GATE: &str = "EGRESS_PPROXY_CERTIFY";
5pub const PLATFORM_GATE: &str = "EGRESS_PPROXY_PLATFORM";
6
7pub fn profile_enabled(profile: CertificationProfile) -> bool {
8 let var = match profile {
9 CertificationProfile::Differential => DIFFERENTIAL_GATE,
10 CertificationProfile::Platform => PLATFORM_GATE,
11 };
12 std::env::var(var).map(|v| v == "1").unwrap_or(false)
13}
14
15pub fn certification_profile(scenario: &OracleScenario) -> Option<CertificationProfile> {
16 if scenario.platform.requires_root || scenario.platform.required_os.is_some() {
17 return Some(CertificationProfile::Platform);
18 }
19
20 match scenario.category {
21 ScenarioCategory::CliDefaults => None,
22 ScenarioCategory::HttpSocksTcp
23 | ScenarioCategory::Chains
24 | ScenarioCategory::Rules
25 | ScenarioCategory::Udp => Some(CertificationProfile::Differential),
26 }
27}
28
29pub fn assign_profiles(
30 scenarios: &[OracleScenario],
31) -> Vec<(Option<CertificationProfile>, &OracleScenario)> {
32 scenarios
33 .iter()
34 .map(|s| (certification_profile(s), s))
35 .collect()
36}
37
38pub fn scenarios_for_profile(
39 scenarios: &[OracleScenario],
40 profile: CertificationProfile,
41) -> Vec<&OracleScenario> {
42 scenarios
43 .iter()
44 .filter(|s| certification_profile(s) == Some(profile))
45 .collect()
46}
47
48#[derive(Debug, Clone)]
49pub struct CertificationProfileConfig {
50 pub profile: CertificationProfile,
51 pub gate_var: &'static str,
52 pub description: &'static str,
53 pub required: bool,
54}
55
56pub fn all_profile_configs() -> Vec<CertificationProfileConfig> {
57 vec![
58 CertificationProfileConfig {
59 profile: CertificationProfile::Differential,
60 gate_var: DIFFERENTIAL_GATE,
61 description: "Differential: HTTP, SOCKS, CLI, UDP with pinned pproxy",
62 required: true,
63 },
64 CertificationProfileConfig {
65 profile: CertificationProfile::Platform,
66 gate_var: PLATFORM_GATE,
67 description: "Platform-specific: root, OS-specific subsets",
68 required: false,
69 },
70 ]
71}
72
73pub fn generate_profile_summary(scenarios: &[OracleScenario]) -> String {
74 let profiles = assign_profiles(scenarios);
75 let mut summary = String::new();
76
77 let structural_count = profiles.iter().filter(|(p, _)| p.is_none()).count();
78 summary.push_str(&format!(
79 "Structural (ungated): {} scenarios\n",
80 structural_count
81 ));
82
83 for config in all_profile_configs() {
84 let count = profiles
85 .iter()
86 .filter(|(p, _)| *p == Some(config.profile))
87 .count();
88 summary.push_str(&format!(
89 "{}: {} scenarios (gate: {})\n",
90 config.description, count, config.gate_var
91 ));
92 }
93
94 summary
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn profile_classification_cli_defaults() {
103 let scenarios =
104 super::super::scenario::scenarios_for_category(ScenarioCategory::CliDefaults);
105 for s in &scenarios {
106 let profile = certification_profile(s);
107 assert_eq!(
108 profile, None,
109 "CLI scenario {} should be unprofiled (structural)",
110 s.id
111 );
112 }
113 }
114
115 #[test]
116 fn profile_classification_http_socks() {
117 let scenarios =
118 super::super::scenario::scenarios_for_category(ScenarioCategory::HttpSocksTcp);
119 for s in &scenarios {
120 let profile = certification_profile(s);
121 assert!(
122 profile == Some(CertificationProfile::Differential),
123 "HTTP/SOCKS scenario {} should be Differential, got {:?}",
124 s.id,
125 profile
126 );
127 }
128 }
129
130 #[test]
131 fn profile_classification_chains() {
132 let scenarios = super::super::scenario::scenarios_for_category(ScenarioCategory::Chains);
133 for s in &scenarios {
134 let profile = certification_profile(s);
135 assert!(
136 profile == Some(CertificationProfile::Differential),
137 "Chain scenario {} should be Differential, got {:?}",
138 s.id,
139 profile
140 );
141 }
142 }
143
144 #[test]
145 fn profile_classification_udp() {
146 let scenarios = super::super::scenario::scenarios_for_category(ScenarioCategory::Udp);
147 for s in &scenarios {
148 let profile = certification_profile(s);
149 assert!(
150 profile == Some(CertificationProfile::Differential),
151 "UDP scenario {} should be Differential, got {:?}",
152 s.id,
153 profile
154 );
155 }
156 }
157
158 #[test]
159 fn profile_gate_defaults() {
160 std::env::remove_var(DIFFERENTIAL_GATE);
161 std::env::remove_var(PLATFORM_GATE);
162 assert!(!profile_enabled(CertificationProfile::Differential));
163 assert!(!profile_enabled(CertificationProfile::Platform));
164 }
165
166 #[test]
167 fn all_profile_configs_complete() {
168 let configs = all_profile_configs();
169 assert_eq!(configs.len(), 2);
170 let mut profiles: Vec<_> = configs.iter().map(|c| c.profile).collect();
171 profiles.sort_by_key(|p| format!("{:?}", p));
172 profiles.dedup();
173 assert_eq!(profiles.len(), 2);
174 }
175
176 #[test]
177 fn scenarios_for_profile_filtering() {
178 let all = super::super::scenario::all_scenarios();
179 let differential = scenarios_for_profile(&all, CertificationProfile::Differential);
180 assert!(!differential.is_empty());
181 for s in &differential {
182 assert_eq!(
183 certification_profile(s),
184 Some(CertificationProfile::Differential)
185 );
186 }
187 }
188
189 #[test]
190 fn assign_profiles_returns_all_scenarios() {
191 let all = super::super::scenario::all_scenarios();
192 let profiles = assign_profiles(&all);
193 assert_eq!(profiles.len(), all.len());
194 }
195}