Skip to main content

lean_ctx/core/context_kernel/
provider_metrics_e2e.rs

1#[cfg(test)]
2mod tests {
3    use serde_json::{Value, json};
4
5    use super::super::envelope_bridge;
6    use super::super::provider_parity::{detect_provider, envelope_from_usage};
7    use super::super::token_envelope::ProviderKind;
8    use super::super::{evidence_wiring, kernel_config, usage_normalizer};
9
10    fn isolated() -> std::sync::MutexGuard<'static, ()> {
11        let guard = kernel_config::KERNEL_TEST_LOCK
12            .lock()
13            .unwrap_or_else(std::sync::PoisonError::into_inner);
14        kernel_config::reset_features();
15        evidence_wiring::reset();
16        usage_normalizer::reset_usage();
17        envelope_bridge::reset();
18        guard
19    }
20
21    #[allow(clippy::needless_pass_by_value)]
22    fn record(url: &str, expected: ProviderKind, model: &str, usage: Value) {
23        let provider = detect_provider(url);
24        assert_eq!(provider, expected);
25        let envelope = envelope_from_usage(provider, model, &usage);
26        envelope_bridge::record_proxy_envelope(&envelope);
27    }
28
29    macro_rules! provider_e2e {
30        ($name:ident, $url:expr, $kind:expr, $model:expr, $usage:expr, $totals:expr) => {
31            #[test]
32            fn $name() {
33                let _guard = isolated();
34                record($url, $kind, $model, $usage);
35                let stats = envelope_bridge::provider_stats();
36                assert_eq!(stats.len(), 1);
37                assert_eq!(stats[0].provider, $kind);
38                assert_eq!(stats[0].request_count, 1);
39                assert_eq!((stats[0].total_input, stats[0].total_output), $totals);
40            }
41        };
42    }
43
44    provider_e2e!(
45        openai_e2e,
46        "https://api.openai.com",
47        ProviderKind::OpenAi,
48        "gpt-5",
49        json!({"prompt_tokens": 100, "completion_tokens": 25}),
50        (100, 25)
51    );
52    provider_e2e!(
53        anthropic_e2e,
54        "https://api.anthropic.com",
55        ProviderKind::Anthropic,
56        "claude-sonnet",
57        json!({"input_tokens": 80, "output_tokens": 20}),
58        (80, 20)
59    );
60    provider_e2e!(
61        bedrock_e2e,
62        "https://bedrock-runtime.us-east-1.amazonaws.com",
63        ProviderKind::Bedrock,
64        "amazon.nova-pro",
65        json!({"inputTokens": 70, "outputTokens": 15}),
66        (70, 15)
67    );
68    provider_e2e!(
69        azure_e2e,
70        "https://tenant.openai.azure.com",
71        ProviderKind::Azure,
72        "gpt-4.1",
73        json!({"prompt_tokens": 60, "completion_tokens": 10}),
74        (60, 10)
75    );
76
77    #[test]
78    fn multi_provider_e2e() {
79        let _guard = isolated();
80        for _ in 0..3 {
81            record(
82                "https://api.openai.com",
83                ProviderKind::OpenAi,
84                "gpt-5",
85                json!({"prompt_tokens": 10, "completion_tokens": 2}),
86            );
87        }
88        for _ in 0..2 {
89            record(
90                "https://api.anthropic.com",
91                ProviderKind::Anthropic,
92                "claude-sonnet",
93                json!({"input_tokens": 20, "output_tokens": 4}),
94            );
95        }
96        let stats = envelope_bridge::provider_stats();
97        assert_eq!(stats.len(), 2);
98        assert_eq!(
99            (stats[0].provider, stats[0].request_count),
100            (ProviderKind::OpenAi, 3)
101        );
102        assert_eq!(
103            (stats[1].provider, stats[1].request_count),
104            (ProviderKind::Anthropic, 2)
105        );
106    }
107
108    #[test]
109    fn envelope_bridge_records_evidence() {
110        let _guard = isolated();
111        record(
112            "https://api.openai.com",
113            ProviderKind::OpenAi,
114            "gpt-5",
115            json!({
116                "prompt_tokens": 40,
117                "completion_tokens": 12,
118                "completion_tokens_details": {"reasoning_tokens": 7}
119            }),
120        );
121        let summary = evidence_wiring::dispatch_summary();
122        assert!(summary.proxy_dispatches >= 1);
123        assert!(summary.total_input_tokens >= 40);
124        assert!(summary.total_output_tokens >= 12);
125        // tokens_saved comes from envelope.tokens_saved, not reasoning
126    }
127
128    #[test]
129    fn envelope_bridge_records_usage() {
130        let _guard = isolated();
131        record(
132            "https://api.anthropic.com",
133            ProviderKind::Anthropic,
134            "claude-sonnet",
135            json!({
136                "input_tokens": 30,
137                "output_tokens": 9,
138                "cache_read_input_tokens": 6
139            }),
140        );
141        let usage = usage_normalizer::session_usage();
142        assert!(usage.total_requests >= 1);
143        assert!(usage.total_tokens >= 39);
144        let model = &usage.per_model["claude-sonnet"];
145        assert!(model.input_tokens >= 30);
146        assert!(model.output_tokens >= 9);
147        assert!(model.cache_read_tokens >= 6);
148    }
149
150    #[test]
151    fn reset_isolates() {
152        let _guard = isolated();
153        record(
154            "https://api.openai.com",
155            ProviderKind::OpenAi,
156            "gpt-5",
157            json!({"prompt_tokens": 10, "completion_tokens": 2}),
158        );
159        envelope_bridge::reset();
160        assert!(envelope_bridge::provider_stats().is_empty());
161    }
162}