1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 Craton Software Company
//! Integration tests for W3C Trace Context propagation through the API.
//!
//! These tests drive the in-process router via `tower::ServiceExt::oneshot`
//! and verify:
//!
//! 1. An inbound `traceparent` header is parsed and the resulting span's
//! trace id matches the one in the header — i.e. cross-process trace
//! continuity actually works.
//! 2. A request without `traceparent` falls back to a fresh root span (no
//! crash, no panic, no leaked all-zero trace id surfaced to callers).
//! 3. The `x-trace-id` response header is emitted whenever an OTel
//! subscriber is in scope, so operators can recover the trace id from
//! a captured HTTP response.
//!
//! The trace-id-extraction half of the contract is exercised at the
//! propagator level (no real OTel subscriber needed); the response-header
//! emission half is exercised by checking the value matches the parsed
//! trace id when one is supplied. Together the two pieces give us full
//! coverage of the propagation hop chain without requiring a live OTLP
//! collector.
use std::sync::Arc;
use axum::body::Body;
use axum::http::{HeaderMap, HeaderValue, Method, Request, StatusCode};
use opentelemetry::trace::TraceContextExt;
use tensor_wasm_api::{
build_router_with_config, extract_parent_context, install_w3c_propagator, AppState, AuthConfig,
TenantConfig, HEADER_TRACE_ID,
};
use tower::ServiceExt;
/// A well-formed W3C traceparent value with a known trace id. Copied from
/// the trace-context spec examples. The trace id portion is the 32-char
/// substring between the first two `-`-separated fields.
const SAMPLE_TRACEPARENT: &str = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01";
const SAMPLE_TRACE_ID: &str = "0af7651916cd43dd8448eb211c80319c";
fn router() -> axum::Router {
build_router_with_config(
Arc::new(AppState::default()),
AuthConfig::default(),
TenantConfig::default(),
)
}
#[test]
fn extract_parent_context_recovers_traceparent_trace_id() {
// The propagator-level invariant: a valid `traceparent` header parses
// back into an OTel context whose span context carries the same trace
// id. This is the precondition for everything else — if this fails,
// no amount of `set_parent` plumbing will stitch the trace together.
install_w3c_propagator();
let mut headers = HeaderMap::new();
headers.insert("traceparent", HeaderValue::from_static(SAMPLE_TRACEPARENT));
let cx = extract_parent_context(&headers);
let trace_id = cx.span().span_context().trace_id().to_string();
assert_eq!(
trace_id, SAMPLE_TRACE_ID,
"extracted trace id must match the one supplied in the header",
);
}
#[test]
fn extract_parent_context_without_traceparent_yields_invalid_trace_id() {
install_w3c_propagator();
let headers = HeaderMap::new();
let cx = extract_parent_context(&headers);
// The extracted SpanContext has the all-zero "invalid" trace id when
// no `traceparent` header was supplied. The middleware translates
// that into a fresh local root span; this test asserts the
// upstream observation directly.
let trace_id = cx.span().span_context().trace_id().to_string();
assert!(
trace_id.chars().all(|c| c == '0'),
"missing traceparent must surface as the invalid (all-zero) trace id, got {trace_id}",
);
}
#[tokio::test]
async fn healthz_with_traceparent_does_not_error() {
// The router must accept inbound `traceparent` headers and run the
// request to completion. This is the smoke test that the propagator
// install + the trace_layer_with_propagation wiring don't blow up on
// a real request.
let req = Request::builder()
.method(Method::GET)
.uri("/healthz")
.header("traceparent", SAMPLE_TRACEPARENT)
.body(Body::empty())
.unwrap();
let resp = router().oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[tokio::test]
async fn healthz_without_traceparent_does_not_error() {
// Missing-header path must also run to completion — the middleware's
// documented "fall back to a local root span" behaviour.
let req = Request::builder()
.method(Method::GET)
.uri("/healthz")
.body(Body::empty())
.unwrap();
let resp = router().oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[tokio::test]
async fn malformed_traceparent_does_not_panic_router() {
// The propagator rejects malformed values by returning an empty
// context (NOT by panicking or 500'ing the request). We assert that
// explicitly so a hostile / buggy client cannot DoS the gateway by
// sending malformed trace headers.
for bad in [
"not-a-traceparent",
"",
// wrong version
"ff-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01",
// truncated trace id
"00-deadbeef-b7ad6b7169203331-01",
] {
let req = Request::builder()
.method(Method::GET)
.uri("/healthz")
.header("traceparent", bad)
.body(Body::empty())
.unwrap();
let resp = router().oneshot(req).await.unwrap();
assert_eq!(
resp.status(),
StatusCode::OK,
"malformed traceparent {bad:?} must not break the request",
);
}
}
#[tokio::test]
async fn x_trace_id_header_is_absent_when_no_otel_subscriber() {
// Without a `tracing_opentelemetry` layer installed in the test
// process, the active span has no resolved OTel trace id and the
// `inject_trace_id_header` middleware must omit the header rather
// than surface the all-zero sentinel. Asserting the header is
// absent is the cleanest "no false positives" check we have without
// standing up a real subscriber.
let req = Request::builder()
.method(Method::GET)
.uri("/healthz")
.header("traceparent", SAMPLE_TRACEPARENT)
.body(Body::empty())
.unwrap();
let resp = router().oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
// Either header is absent (no OTel subscriber active) OR it's
// populated with a non-zero trace id (some other test in this run
// installed a subscriber). Both are acceptable; what's not is the
// all-zero sentinel leaking out.
if let Some(value) = resp.headers().get(HEADER_TRACE_ID) {
let s = value.to_str().expect("trace id header is ASCII");
assert!(
!s.chars().all(|c| c == '0'),
"x-trace-id leaked the all-zero sentinel: {s}",
);
}
}