use std::sync::Arc;
use axum::body::Body;
use axum::http::{Method, Request, StatusCode};
use tensor_wasm_api::{
build_router_with_full_config, AppState, AuthConfig, PerTenantRateLimitConfig, RateLimitConfig,
RateLimiter, TenantConfig,
};
use tower::ServiceExt;
const PROTECTED_PATH: &str = "/jobs/00000000-0000-0000-0000-000000000000";
const ALLOWED_STATUS: StatusCode = StatusCode::NOT_FOUND;
fn router_with_limit(tokens: &[&str], qps: u32, burst: u32) -> axum::Router {
let auth = AuthConfig::from_tokens(tokens.iter().copied());
let limiter = RateLimiter::new(RateLimitConfig {
qps,
burst,
per_tenant_default: PerTenantRateLimitConfig::disabled(),
});
build_router_with_full_config(
Arc::new(AppState::default()),
auth,
TenantConfig::default(),
limiter,
)
}
#[tokio::test]
async fn burst_window_admits_then_429s_with_retry_after() {
let router = router_with_limit(&["A", "B"], 1, 3);
let mut statuses = Vec::new();
let mut retry_after = None;
for _ in 0..4 {
let req = Request::builder()
.method(Method::GET)
.uri(PROTECTED_PATH)
.header("Authorization", "Bearer A")
.body(Body::empty())
.unwrap();
let resp = router.clone().oneshot(req).await.expect("oneshot");
let status = resp.status();
if status == StatusCode::TOO_MANY_REQUESTS {
retry_after = resp
.headers()
.get(axum::http::header::RETRY_AFTER)
.and_then(|v| v.to_str().ok())
.map(str::to_owned);
}
statuses.push(status);
}
assert_eq!(statuses[0], ALLOWED_STATUS);
assert_eq!(statuses[1], ALLOWED_STATUS);
assert_eq!(statuses[2], ALLOWED_STATUS);
assert_eq!(statuses[3], StatusCode::TOO_MANY_REQUESTS);
assert!(
retry_after.is_some(),
"Retry-After header missing on 429: {statuses:?}",
);
}
#[tokio::test]
async fn separate_tokens_get_separate_buckets_through_router() {
let router = router_with_limit(&["A", "B"], 1, 2);
for _ in 0..2 {
let req = Request::builder()
.method(Method::GET)
.uri(PROTECTED_PATH)
.header("Authorization", "Bearer A")
.body(Body::empty())
.unwrap();
let resp = router.clone().oneshot(req).await.unwrap();
assert_eq!(resp.status(), ALLOWED_STATUS);
}
let drained = Request::builder()
.method(Method::GET)
.uri(PROTECTED_PATH)
.header("Authorization", "Bearer A")
.body(Body::empty())
.unwrap();
assert_eq!(
router.clone().oneshot(drained).await.unwrap().status(),
StatusCode::TOO_MANY_REQUESTS,
);
for _ in 0..2 {
let req = Request::builder()
.method(Method::GET)
.uri(PROTECTED_PATH)
.header("Authorization", "Bearer B")
.body(Body::empty())
.unwrap();
let resp = router.clone().oneshot(req).await.unwrap();
assert_eq!(resp.status(), ALLOWED_STATUS);
}
}
#[tokio::test]
async fn disabled_limiter_does_not_throttle() {
let router = router_with_limit(&["A"], 0, 0);
for _ in 0..50 {
let req = Request::builder()
.method(Method::GET)
.uri(PROTECTED_PATH)
.header("Authorization", "Bearer A")
.body(Body::empty())
.unwrap();
let resp = router.clone().oneshot(req).await.unwrap();
assert_eq!(resp.status(), ALLOWED_STATUS);
}
}
#[tokio::test]
async fn rate_limit_runs_after_bearer_auth() {
let router = router_with_limit(&["A"], 1, 1);
let req = Request::builder()
.method(Method::GET)
.uri(PROTECTED_PATH)
.body(Body::empty())
.unwrap();
let resp = router.clone().oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
let ok = Request::builder()
.method(Method::GET)
.uri(PROTECTED_PATH)
.header("Authorization", "Bearer A")
.body(Body::empty())
.unwrap();
assert_eq!(router.oneshot(ok).await.unwrap().status(), ALLOWED_STATUS);
}
#[tokio::test]
async fn dev_mode_shares_a_single_bucket() {
let auth = AuthConfig::default(); let limiter = RateLimiter::new(RateLimitConfig {
qps: 1,
burst: 2,
per_tenant_default: PerTenantRateLimitConfig::disabled(),
});
let router = build_router_with_full_config(
Arc::new(AppState::default()),
auth,
TenantConfig::default(),
limiter,
);
for i in 0..2 {
let req = Request::builder()
.method(Method::GET)
.uri(PROTECTED_PATH)
.body(Body::empty())
.unwrap();
let resp = router.clone().oneshot(req).await.unwrap();
assert_eq!(resp.status(), ALLOWED_STATUS, "request {i}");
}
let req = Request::builder()
.method(Method::GET)
.uri(PROTECTED_PATH)
.body(Body::empty())
.unwrap();
assert_eq!(
router.oneshot(req).await.unwrap().status(),
StatusCode::TOO_MANY_REQUESTS,
);
}