#![allow(clippy::expect_used)]
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Once};
use axum::body::Body;
use axum::http::{Method, Request, StatusCode};
use tensor_wasm_api::{
build_router_with_config, AppState, AuthConfig, TenantConfig, ENV_TRUSTED_HOSTS,
};
use tower::ServiceExt;
use tracing::span;
use tracing::Subscriber;
use tracing_subscriber::layer::{Context, SubscriberExt};
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::Layer;
const HTTP_REQUEST_SPAN: &str = "http.request";
struct CountingLayer {
http_request_spans: Arc<AtomicUsize>,
}
impl<S> Layer<S> for CountingLayer
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
fn on_new_span(&self, attrs: &span::Attributes<'_>, _id: &span::Id, _ctx: Context<'_, S>) {
if attrs.metadata().name() == HTTP_REQUEST_SPAN {
self.http_request_spans.fetch_add(1, Ordering::Relaxed);
}
}
}
fn install_counting_subscriber() -> Arc<AtomicUsize> {
static INIT: Once = Once::new();
static COUNTER: std::sync::OnceLock<Arc<AtomicUsize>> = std::sync::OnceLock::new();
let counter = COUNTER
.get_or_init(|| Arc::new(AtomicUsize::new(0)))
.clone();
INIT.call_once(|| {
let layer = CountingLayer {
http_request_spans: Arc::clone(&counter),
};
let _ = tracing_subscriber::registry().with(layer).try_init();
});
counter
}
#[tokio::test]
async fn host_validate_rejects_before_trace_layer_allocates_span() {
let counter = install_counting_subscriber();
temp_env::async_with_vars([(ENV_TRUSTED_HOSTS, Some("api.example.com"))], async {
let router = build_router_with_config(
Arc::new(AppState::default()),
AuthConfig::default(),
TenantConfig::default(),
);
let baseline = counter.load(Ordering::Relaxed);
let req = Request::builder()
.method(Method::GET)
.uri("/healthz")
.header("host", "evil.example.com")
.body(Body::empty())
.expect("well-formed request");
let resp = router.clone().oneshot(req).await.expect("router oneshot");
assert_eq!(
resp.status(),
StatusCode::BAD_REQUEST,
"hostile Host must be rejected with 400 (host_validate envelope)",
);
let after_reject = counter.load(Ordering::Relaxed);
let positive_baseline = after_reject;
let allowed = Request::builder()
.method(Method::GET)
.uri("/healthz")
.header("host", "api.example.com")
.body(Body::empty())
.expect("well-formed request");
let resp = router.oneshot(allowed).await.expect("router oneshot");
assert_eq!(
resp.status(),
StatusCode::OK,
"allowlisted Host must reach the handler",
);
let after_allow = counter.load(Ordering::Relaxed);
if after_allow == positive_baseline {
eprintln!(
"host_validate_runs_before_trace_layer: counting \
subscriber not active (another test owns the \
global); skipping span-count assertion",
);
return;
}
assert_eq!(
after_reject - baseline,
0,
"host_validate rejection opened an http.request span \
(delta={}) — the T19 layer re-order regressed: \
host_validate must sit OUTSIDE trace_layer_with_propagation \
so hostile probes never burn trace budget",
after_reject - baseline,
);
assert_eq!(
after_allow - after_reject,
1,
"allowed request must open exactly one http.request span \
(saw delta={})",
after_allow - after_reject,
);
})
.await;
}