telemetry_rust/test/
mod.rs

1//! Testing utilities for OpenTelemetry integration testing and validation.
2//!
3//! This module provides utilities for testing OpenTelemetry instrumentation,
4//! including trace header manipulation, Jaeger trace data structures for
5//! validation, and HTTP response testing helpers.
6//!
7//! The module contains tools for:
8//! - Parsing and generating trace headers (traceparent, tracestate)
9//! - Deserializing Jaeger trace data for validation
10//! - Testing HTTP responses with trace context
11//! - Generating test trace IDs and span IDs
12
13pub mod jaegar;
14
15use bytes::Bytes;
16use http_body_util::BodyExt;
17use hyper::{
18    HeaderMap, Response,
19    body::{Body, Incoming},
20    header::HeaderValue,
21};
22
23pub use opentelemetry_api::trace::{SpanId, TraceId};
24use rand::Rng;
25
26/// HTTP response wrapper that includes OpenTelemetry trace information.
27///
28/// This struct wraps an HTTP response and provides easy access to the associated
29/// trace ID and span ID for testing and debugging purposes. It's particularly
30/// useful in integration tests where you need to verify trace propagation.
31///
32/// # Example
33///
34/// ```rust
35/// use telemetry_rust::test::{TracedResponse, Traceparent};
36///
37/// async fn send_traced_request() -> TracedResponse<&'static str> {
38///     let traceparent = Traceparent::generate();
39///
40///     // Send request and get response
41///     let resp = hyper::Response::new("Hello world!");
42///
43///     TracedResponse::new(resp, traceparent)
44/// }
45/// ```
46#[derive(Debug)]
47pub struct TracedResponse<T = Incoming> {
48    resp: Response<T>,
49    /// The OpenTelemetry trace ID associated with this response
50    pub trace_id: TraceId,
51    /// The OpenTelemetry span ID associated with this response
52    pub span_id: SpanId,
53}
54
55impl<T> TracedResponse<T> {
56    /// Creates a new traced response from an HTTP response and trace parent information.
57    ///
58    /// # Arguments
59    ///
60    /// - `resp`: The HTTP response to wrap
61    /// - `traceparent`: The trace parent containing trace and span IDs
62    ///
63    /// # Returns
64    ///
65    /// A new [`TracedResponse`] instance
66    pub fn new(resp: Response<T>, traceparent: Traceparent) -> Self {
67        Self {
68            resp,
69            trace_id: traceparent.trace_id,
70            span_id: traceparent.span_id,
71        }
72    }
73
74    /// Consumes the traced response and returns the inner HTTP response.
75    ///
76    /// # Returns
77    ///
78    /// The wrapped [`hyper::Response`] instance.
79    pub async fn into_inner(self) -> Response<T> {
80        self.resp
81    }
82}
83
84impl<E, T: Body<Data = Bytes, Error = E>> TracedResponse<T> {
85    /// Consumes the response and returns the body as bytes.
86    ///
87    /// # Returns
88    ///
89    /// A future that resolves to the response body as [`bytes::Bytes`]
90    ///
91    /// # Errors
92    ///
93    /// Returns an error if the response body cannot be read
94    pub async fn into_bytes(self) -> Result<Bytes, E> {
95        Ok(self.resp.into_body().collect().await?.to_bytes())
96    }
97}
98
99impl<T> std::ops::Deref for TracedResponse<T> {
100    type Target = Response<T>;
101
102    fn deref(&self) -> &Self::Target {
103        &self.resp
104    }
105}
106
107impl<T> std::ops::DerefMut for TracedResponse<T> {
108    fn deref_mut(&mut self) -> &mut Self::Target {
109        &mut self.resp
110    }
111}
112
113/// Enumeration of supported tracing header formats for testing.
114///
115/// This enum represents the different trace context propagation formats
116/// that can be used in HTTP headers for testing distributed tracing scenarios.
117pub enum TracingHeaderKind {
118    /// W3C Trace Context format using the `traceparent` header
119    Traceparent,
120    /// B3 single header format using the `b3` header
121    B3Single,
122    /// B3 multiple header format using separate `X-B3-*` headers
123    B3Multi,
124}
125
126/// A container for OpenTelemetry trace parent information used in testing.
127///
128/// This struct holds a trace ID and span ID pair that represents a trace context
129/// relationship. It's commonly used for generating test trace headers and
130/// validating trace propagation in integration tests.
131///
132/// # Example
133///
134/// ```rust
135/// use telemetry_rust::test::{Traceparent, TracingHeaderKind};
136///
137/// // Generate a new trace parent for testing
138/// let traceparent = Traceparent::generate();
139///
140/// // Create HTTP headers for trace propagation
141/// let headers = traceparent.get_headers(TracingHeaderKind::Traceparent);
142///
143/// // Use in HTTP request testing
144/// let mut req = hyper::Request::new(());
145/// for (key, value) in headers {
146///     if let Some(header_name) = key {
147///         req.headers_mut().insert(header_name, value);
148///     }
149/// }
150/// ```
151pub struct Traceparent {
152    /// The OpenTelemetry trace ID
153    pub trace_id: TraceId,
154    /// The OpenTelemetry span ID
155    pub span_id: SpanId,
156}
157
158impl Traceparent {
159    /// Generates a new random trace parent with random trace and span IDs.
160    ///
161    /// This method creates a new trace parent with randomly generated IDs,
162    /// useful for creating test scenarios with unique trace contexts.
163    ///
164    /// # Returns
165    ///
166    /// A new [`Traceparent`] with randomly generated trace and span IDs
167    ///
168    /// # Examples
169    ///
170    /// ```rust
171    /// use telemetry_rust::test::Traceparent;
172    ///
173    /// let traceparent = Traceparent::generate();
174    /// println!("Trace ID: {}", traceparent.trace_id);
175    /// ```
176    pub fn generate() -> Self {
177        let mut rng = rand::rng();
178        let trace_id = TraceId::from_u128(rng.random());
179        let span_id = SpanId::from_u64(rng.random());
180        Self { trace_id, span_id }
181    }
182
183    /// Generates HTTP headers containing trace context in the specified format.
184    ///
185    /// This method creates HTTP headers with trace context information formatted
186    /// according to the specified tracing header kind. This is useful for testing
187    /// trace propagation with different header formats.
188    ///
189    /// # Arguments
190    ///
191    /// - `kind`: The format to use for the trace headers
192    ///
193    /// # Returns
194    ///
195    /// A [`HeaderMap`] containing the appropriately formatted trace headers
196    ///
197    /// # Examples
198    ///
199    /// ```rust
200    /// use telemetry_rust::test::{Traceparent, TracingHeaderKind};
201    ///
202    /// let traceparent = Traceparent::generate();
203    /// let headers = traceparent.get_headers(TracingHeaderKind::Traceparent);
204    /// ```
205    ///
206    /// # Panics
207    ///
208    /// This function will panic if the trace ID or span ID cannot be converted
209    /// to a valid HTTP header value format.
210    pub fn get_headers(&self, kind: TracingHeaderKind) -> HeaderMap {
211        let mut map = HeaderMap::new();
212
213        match kind {
214            TracingHeaderKind::Traceparent => {
215                let value = format!("00-{}-{}-01", self.trace_id, self.span_id);
216                map.append("traceparent", HeaderValue::from_str(&value).unwrap());
217            }
218            TracingHeaderKind::B3Single => {
219                let value = format!("{}-{}-1", self.trace_id, self.span_id);
220                map.append("b3", HeaderValue::from_str(&value).unwrap());
221            }
222            TracingHeaderKind::B3Multi => {
223                map.append(
224                    "X-B3-TraceId",
225                    HeaderValue::from_str(&self.trace_id.to_string()).unwrap(),
226                );
227                map.append(
228                    "X-B3-SpanId",
229                    HeaderValue::from_str(&self.span_id.to_string()).unwrap(),
230                );
231                map.append("X-B3-Sampled", HeaderValue::from_str("1").unwrap());
232            }
233        }
234
235        map
236    }
237}