clnrm_core/telemetry/
testing.rs

1//! Testing utilities for OpenTelemetry integration
2//!
3//! Provides in-memory span exporters and test helpers for validating
4//! OpenTelemetry functionality without external dependencies.
5
6use opentelemetry::{
7    trace::{Span, Status, Tracer, TracerProvider},
8    KeyValue,
9};
10
11use std::sync::{Arc, Mutex};
12
13use crate::validation::SpanData;
14
15use opentelemetry_sdk::trace::{InMemorySpanExporter, SdkTracerProvider};
16
17/// Use the built-in OpenTelemetry SDK InMemorySpanExporter
18pub type TestSpanExporter = InMemorySpanExporter;
19
20/// Test tracer provider with in-memory exporter
21pub struct TestTracerProvider {
22    provider: SdkTracerProvider,
23    exporter: TestSpanExporter,
24}
25
26impl Default for TestTracerProvider {
27    fn default() -> Self {
28        Self::new()
29    }
30}
31
32impl TestTracerProvider {
33    /// Create a new test tracer provider
34    pub fn new() -> Self {
35        let exporter = TestSpanExporter::default();
36        let processor =
37            opentelemetry_sdk::trace::BatchSpanProcessor::builder(exporter.clone()).build();
38
39        let provider = SdkTracerProvider::builder()
40            .with_span_processor(processor)
41            .build();
42
43        Self { provider, exporter }
44    }
45
46    /// Get a tracer from the provider
47    pub fn tracer(&self) -> opentelemetry_sdk::trace::Tracer {
48        self.provider.tracer("clnrm-test")
49    }
50
51    /// Get the span exporter for validation
52    pub fn exporter(&self) -> &TestSpanExporter {
53        &self.exporter
54    }
55
56    /// Get all captured spans
57    pub fn get_spans(&self) -> Vec<SpanData> {
58        // For now, return empty vector - real implementation would convert
59        // from OpenTelemetry SDK SpanData to our SpanData
60        Vec::new()
61    }
62
63    /// Find spans by name
64    pub fn find_spans_by_name(&self, _name: &str) -> Vec<SpanData> {
65        // For now, return empty vector - real implementation would convert
66        // from OpenTelemetry SDK SpanData to our SpanData
67        Vec::new()
68    }
69
70    /// Find spans by trace ID
71    pub fn find_spans_by_trace_id(&self, _trace_id: &str) -> Vec<SpanData> {
72        // For now, return empty vector - real implementation would convert
73        // from OpenTelemetry SDK SpanData to our SpanData
74        Vec::new()
75    }
76
77    /// Find spans by attribute
78    pub fn find_spans_by_attribute(&self, _key: &str, _value: &str) -> Vec<SpanData> {
79        // For now, return empty vector - real implementation would convert
80        // from OpenTelemetry SDK SpanData to our SpanData
81        Vec::new()
82    }
83
84    /// Clear all captured spans
85    pub fn clear(&self) {
86        self.exporter.reset();
87    }
88
89    /// Check if any spans have been captured
90    pub fn has_spans(&self) -> bool {
91        !self
92            .exporter
93            .get_finished_spans()
94            .unwrap_or_default()
95            .is_empty()
96    }
97}
98
99/// Helper functions for creating test spans
100pub struct TestSpanHelper;
101
102impl TestSpanHelper {
103    /// Create a test span with the given name
104    pub fn create_span(tracer: &opentelemetry_sdk::trace::Tracer, name: &'static str) -> impl Span {
105        tracer.start(name)
106    }
107
108    /// Create a test span with attributes
109    pub fn create_span_with_attributes(
110        tracer: &opentelemetry_sdk::trace::Tracer,
111        name: &'static str,
112        attributes: Vec<KeyValue>,
113    ) -> impl Span {
114        let mut span = tracer.start(name);
115        for attr in attributes {
116            span.set_attribute(attr);
117        }
118        span
119    }
120
121    /// Create a test span with duration
122    pub fn create_span_with_duration(
123        tracer: &opentelemetry_sdk::trace::Tracer,
124        name: &'static str,
125        duration_ms: u64,
126    ) -> impl Span {
127        let mut span = tracer.start(name);
128        span.set_attribute(KeyValue::new("duration_ms", duration_ms as f64));
129        span
130    }
131
132    /// Create a test span with status
133    pub fn create_span_with_status(
134        tracer: &opentelemetry_sdk::trace::Tracer,
135        name: &'static str,
136        status: Status,
137    ) -> impl Span {
138        let mut span = tracer.start(name);
139        span.set_status(status);
140        span
141    }
142
143    /// Create a parent-child span relationship
144    pub fn create_parent_child_spans(
145        tracer: &opentelemetry_sdk::trace::Tracer,
146        parent_name: &'static str,
147        child_name: &'static str,
148    ) -> (impl Span, impl Span) {
149        let parent_span = tracer.start(parent_name);
150        let child_span = tracer.start(child_name);
151        (parent_span, child_span)
152    }
153}
154
155/// Mock OTLP collector for testing export functionality
156pub struct MockOtlpCollector {
157    endpoint: String,
158    received_spans: Arc<Mutex<Vec<crate::validation::SpanData>>>,
159}
160
161impl MockOtlpCollector {
162    /// Create a new mock OTLP collector
163    pub fn new(endpoint: String) -> Self {
164        Self {
165            endpoint,
166            received_spans: Arc::new(Mutex::new(Vec::new())),
167        }
168    }
169
170    /// Get the endpoint URL
171    pub fn endpoint(&self) -> &str {
172        &self.endpoint
173    }
174
175    /// Get all received spans
176    pub fn get_received_spans(&self) -> Vec<crate::validation::SpanData> {
177        self.received_spans
178            .lock()
179            .map(|guard| guard.clone())
180            .unwrap_or_default()
181    }
182
183    /// Clear all received spans
184    pub fn clear(&self) {
185        if let Ok(mut guard) = self.received_spans.lock() {
186            guard.clear();
187        }
188    }
189
190    /// Check if any spans have been received
191    pub fn has_spans(&self) -> bool {
192        self.received_spans
193            .lock()
194            .map(|guard| !guard.is_empty())
195            .unwrap_or(false)
196    }
197}