Skip to main content

eggress_testkit/oracle/
profile.rs

1use 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}