#![cfg(test)]
use crate::observability::w3c_trace_context::*;
use std::collections::HashMap;
use std::str::FromStr;
struct HeaderFixtureRequest {
headers: HashMap<String, String>,
}
impl HeaderFixtureRequest {
fn new() -> Self {
Self {
headers: HashMap::new(),
}
}
fn with_traceparent(mut self, traceparent: &str) -> Self {
self.headers
.insert("traceparent".to_string(), traceparent.to_string());
self
}
fn with_tracestate(mut self, tracestate: &str) -> Self {
self.headers
.insert("tracestate".to_string(), tracestate.to_string());
self
}
}
struct GrpcMetadataFixture {
metadata: HashMap<String, String>,
}
impl GrpcMetadataFixture {
fn new() -> Self {
Self {
metadata: HashMap::new(),
}
}
fn metadata_mut(&mut self) -> &mut HashMap<String, String> {
&mut self.metadata
}
}
#[test]
fn audit_http_to_grpc_span_context_propagation() {
let original_trace_id = "4bf92f3577b34da6a3ce929d0e0e4736";
let original_span_id = "00f067aa0ba902b7";
let original_traceparent = format!("00-{}-{}-01", original_trace_id, original_span_id);
let http_request = HeaderFixtureRequest::new()
.with_traceparent(&original_traceparent)
.with_tracestate("vendor1=value1,vendor2=value2");
let extracted_context = extract_from_http(&http_request.headers)
.expect("failed to extract trace context")
.expect("trace context should be present");
assert_eq!(extracted_context.trace_id.to_hex(), original_trace_id);
assert_eq!(extracted_context.span_id.to_hex(), original_span_id);
assert!(extracted_context.flags.is_sampled());
assert_eq!(
extracted_context.tracestate.as_deref(),
Some("vendor1=value1,vendor2=value2")
);
let child_context = extracted_context.create_child();
assert_eq!(
child_context.trace_id.to_hex(),
original_trace_id,
"child must preserve trace ID"
);
assert_ne!(
child_context.span_id.to_hex(),
original_span_id,
"child must have new span ID"
);
assert_eq!(
child_context.parent_span_id.to_hex(),
original_span_id,
"child's parent must be original span"
);
let mut grpc_request = GrpcMetadataFixture::new();
inject_to_grpc(&child_context, grpc_request.metadata_mut());
let grpc_traceparent = grpc_request
.metadata
.get("traceparent")
.expect("grpc request must contain traceparent");
let injected_context =
W3CTraceContext::from_str(grpc_traceparent).expect("injected traceparent must be valid");
assert_eq!(
injected_context.trace_id.to_hex(),
original_trace_id,
"gRPC call must preserve trace ID"
);
assert_eq!(
injected_context.span_id, child_context.span_id,
"gRPC call must use child span ID"
);
assert_ne!(
injected_context.span_id.to_hex(),
original_span_id,
"spans must be unique for stitching"
);
println!("✅ AUDIT PASSED: Span context flows correctly from HTTP → gRPC");
println!(" Trace ID: {} (preserved)", original_trace_id);
println!(
" HTTP span: {} → gRPC span: {}",
original_span_id,
injected_context.span_id.to_hex()
);
}
#[test]
fn audit_graceful_handling_of_missing_trace_context() {
let http_request = HeaderFixtureRequest::new();
let extracted_context = extract_from_http(&http_request.headers)
.expect("extraction must not fail on missing headers");
assert!(
extracted_context.is_none(),
"missing context should return None"
);
let root_context = W3CTraceContext::new_root();
let mut grpc_request = GrpcMetadataFixture::new();
inject_to_grpc(&root_context, grpc_request.metadata_mut());
assert!(
grpc_request.metadata.contains_key("traceparent"),
"root context must propagate"
);
println!("✅ AUDIT PASSED: Graceful degradation on missing trace context");
}
#[test]
fn audit_security_bounds_prevent_amplification() {
let malicious_traceparent = "00-".to_string() + &"a".repeat(200);
let http_request = HeaderFixtureRequest::new().with_traceparent(&malicious_traceparent);
let result = extract_from_http(&http_request.headers);
assert!(result.is_err(), "oversized context must be rejected");
if let Err(TraceContextError::ValueTooLong(len)) = result {
assert!(len > 128, "error should report actual length");
} else {
panic!("expected ValueTooLong error");
}
println!("✅ AUDIT PASSED: Security bounds prevent amplification attacks");
}
#[test]
fn audit_malformed_trace_context_handling() {
let test_cases = vec![
("invalid-format", "malformed traceparent"),
(
"00-00000000000000000000000000000000-00f067aa0ba902b7-01",
"zero trace ID",
),
(
"00-4bf92f3577b34da6a3ce929d0e0e4736-0000000000000000-01",
"zero span ID",
),
(
"01-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
"unsupported version",
),
(
"00-invalid-hex-chars-here-00f067aa0ba902b7-01",
"invalid hex",
),
];
for (invalid_traceparent, description) in test_cases {
let http_request = HeaderFixtureRequest::new().with_traceparent(invalid_traceparent);
let result = extract_from_http(&http_request.headers);
assert!(
result.is_err(),
"malformed context must be rejected: {}",
description
);
println!("✅ Rejected {}: {}", description, invalid_traceparent);
}
println!("✅ AUDIT PASSED: Malformed trace contexts handled gracefully");
}
#[test]
fn integration_test_full_trace_round_trip() {
let client_trace_id = "1234567890abcdef1234567890abcdef";
let client_span_id = "abcdef1234567890";
let client_traceparent = format!("00-{}-{}-01", client_trace_id, client_span_id);
let gateway_request = HeaderFixtureRequest::new().with_traceparent(&client_traceparent);
let gateway_context = extract_from_http(&gateway_request.headers)
.unwrap()
.unwrap();
let service_a_context = gateway_context.create_child();
let mut service_a_request = GrpcMetadataFixture::new();
inject_to_grpc(&service_a_context, service_a_request.metadata_mut());
let service_a_extracted = extract_from_http(&service_a_request.metadata)
.unwrap()
.unwrap();
let service_b_context = service_a_extracted.create_child();
let mut service_b_request = GrpcMetadataFixture::new();
inject_to_grpc(&service_b_context, service_b_request.metadata_mut());
assert_eq!(
gateway_context.trace_id.to_hex(),
client_trace_id,
"trace ID preserved through gateway"
);
assert_eq!(
service_a_context.trace_id.to_hex(),
client_trace_id,
"trace ID preserved in service A"
);
assert_eq!(
service_b_context.trace_id.to_hex(),
client_trace_id,
"trace ID preserved in service B"
);
assert_eq!(
service_a_context.parent_span_id, gateway_context.span_id,
"service A parent is gateway span"
);
assert_eq!(
service_b_context.parent_span_id, service_a_context.span_id,
"service B parent is service A span"
);
println!("✅ INTEGRATION PASSED: Full trace chain preserved");
println!(" Client → Gateway → Service A → Service B");
println!(" Trace: {}", client_trace_id);
println!(
" Spans: {} → {} → {} → {}",
client_span_id,
gateway_context.span_id.to_hex(),
service_a_context.span_id.to_hex(),
service_b_context.span_id.to_hex()
);
}