use a2a_protocol_types::trace_context::{TRACEPARENT_HEADER, TRACESTATE_HEADER, TraceContext};
use super::{CurrentTrace, TracePropagationInterceptor};
use crate::interceptor::{CallInterceptor, ClientRequest};
const PARENT: &str = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01";
fn request() -> ClientRequest {
ClientRequest::new("SendMessage", serde_json::json!({}))
}
async fn intercepted(mut req: ClientRequest) -> ClientRequest {
TracePropagationInterceptor::new()
.before(&mut req)
.await
.expect("the interceptor never fails");
req
}
#[tokio::test]
async fn no_scope_means_no_header() {
let req = intercepted(request()).await;
assert!(
req.extra_headers.is_empty(),
"a client used outside a traced call must be unaffected"
);
}
#[tokio::test]
async fn an_open_scope_is_written_onto_the_request() {
let trace = TraceContext::parse(PARENT).expect("valid");
let req = CurrentTrace::scope(trace, intercepted(request())).await;
assert_eq!(
req.extra_headers
.get(TRACEPARENT_HEADER)
.map(String::as_str),
Some(PARENT)
);
assert!(!req.extra_headers.contains_key(TRACESTATE_HEADER));
}
#[tokio::test]
async fn tracestate_rides_along_when_there_is_one() {
let trace = TraceContext::parse(PARENT)
.expect("valid")
.with_tracestate("vendor=value")
.expect("valid");
let req = CurrentTrace::scope(trace, intercepted(request())).await;
assert_eq!(
req.extra_headers.get(TRACESTATE_HEADER).map(String::as_str),
Some("vendor=value")
);
}
#[tokio::test]
async fn an_explicit_traceparent_is_not_overwritten() {
let explicit = "00-11111111111111111111111111111111-2222222222222222-00";
let mut req = request();
req.extra_headers
.insert(TRACEPARENT_HEADER.to_owned(), explicit.to_owned());
let trace = TraceContext::parse(PARENT).expect("valid");
let req = CurrentTrace::scope(trace, intercepted(req)).await;
assert_eq!(
req.extra_headers
.get(TRACEPARENT_HEADER)
.map(String::as_str),
Some(explicit)
);
}
#[tokio::test]
async fn an_explicit_traceparent_is_not_overwritten_whatever_its_case() {
let explicit = "00-11111111111111111111111111111111-2222222222222222-00";
for spelling in ["Traceparent", "TRACEPARENT", "TraceParent"] {
let mut req = request();
req.extra_headers
.insert(spelling.to_owned(), explicit.to_owned());
let trace = TraceContext::parse(PARENT)
.expect("valid")
.with_tracestate("vendor=value")
.expect("valid");
let req = CurrentTrace::scope(trace, intercepted(req)).await;
assert_eq!(
req.extra_headers.len(),
1,
"{spelling} must be recognised, not duplicated: {:?}",
req.extra_headers
);
assert_eq!(
req.extra_headers.get(spelling).map(String::as_str),
Some(explicit)
);
}
let mut req = request();
req.extra_headers
.insert("Tracestate".to_owned(), "caller=chose".to_owned());
let trace = TraceContext::parse(PARENT)
.expect("valid")
.with_tracestate("vendor=value")
.expect("valid");
let req = CurrentTrace::scope(trace, intercepted(req)).await;
assert!(
!req.extra_headers.contains_key(TRACESTATE_HEADER),
"a second tracestate would go on the wire alongside the caller's: {:?}",
req.extra_headers
);
}
#[test]
fn header_builder_appends_rather_than_replacing() {
let req = hyper::Request::builder()
.method(hyper::Method::POST)
.uri("http://example.invalid/")
.header(
"traceparent",
"00-11111111111111111111111111111111-2222222222222222-00",
)
.header(
"Traceparent",
"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
)
.body(())
.expect("a well-formed request");
assert_eq!(
req.headers().get_all("traceparent").iter().count(),
2,
"`header()` appends, so a duplicate key is two fields, not one"
);
}
#[tokio::test]
async fn a_started_root_is_sampled_and_well_formed() {
let root = CurrentTrace::start_root();
assert!(
root.is_sampled(),
"an unsampled root would be dropped downstream"
);
let reparsed = TraceContext::parse(&root.traceparent()).expect("a root must re-parse");
assert_eq!(reparsed.trace_id(), root.trace_id());
assert_eq!(reparsed.span_id(), root.span_id());
}
#[tokio::test]
async fn started_roots_are_distinct() {
let a = CurrentTrace::start_root();
let b = CurrentTrace::start_root();
assert_ne!(a.trace_id(), b.trace_id());
assert_ne!(a.span_id(), b.span_id());
}
#[tokio::test]
async fn current_reports_the_innermost_scope() {
let outer = TraceContext::parse(PARENT).expect("valid");
let inner = outer.child("b7ad6b7169203331").expect("valid");
let observed = CurrentTrace::scope(outer.clone(), async {
let outer_seen = CurrentTrace::current().expect("in scope");
let inner_seen =
CurrentTrace::scope(inner, async { CurrentTrace::current().expect("in scope") }).await;
(outer_seen, inner_seen)
})
.await;
assert_eq!(observed.0.span_id(), "00f067aa0ba902b7");
assert_eq!(observed.1.span_id(), "b7ad6b7169203331");
assert!(
CurrentTrace::current().is_none(),
"the scope must not leak out"
);
}