use axum::{Router, routing::get};
use socle::{BootstrapCtx, ServiceBootstrap, testing::TestClient};
use tokio::sync::oneshot;
async fn spawn_service(
build: impl FnOnce(ServiceBootstrap) -> ServiceBootstrap,
) -> (TestClient, oneshot::Sender<()>) {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
.await
.expect("bind");
let addr = listener.local_addr().expect("local_addr");
let (tx, rx) = oneshot::channel::<()>();
let svc = build(ServiceBootstrap::new("test-service"));
tokio::spawn(async move {
svc.serve_with_shutdown(listener, async {
rx.await.ok();
})
.await
.ok();
});
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
(TestClient::new(format!("http://{addr}")), tx)
}
#[tokio::test]
async fn health_liveness_returns_200() {
let (client, _stop) = spawn_service(|s| s.with_router(|_: &BootstrapCtx| Router::new())).await;
let resp = client.get("/health/live").await;
assert_eq!(resp.status(), 200);
}
#[tokio::test]
async fn health_readiness_returns_200_when_no_checks_registered() {
let (client, _stop) = spawn_service(|s| s.with_router(|_: &BootstrapCtx| Router::new())).await;
let resp = client.get("/health/ready").await;
assert_eq!(resp.status(), 200);
}
#[tokio::test]
async fn user_route_is_reachable() {
let (client, _stop) = spawn_service(|s| {
s.with_router(|_: &BootstrapCtx| Router::new().route("/hello", get(|| async { "world" })))
})
.await;
let resp = client.get("/hello").await;
assert_eq!(resp.status(), 200);
assert_eq!(resp.text().await.unwrap(), "world");
}
#[tokio::test]
async fn unknown_route_returns_404_problem_json() {
let (client, _stop) = spawn_service(|s| s.with_router(|_: &BootstrapCtx| Router::new())).await;
let resp = client.get("/does-not-exist").await;
assert_eq!(resp.status(), 404);
let ct = resp
.headers()
.get("content-type")
.and_then(|v| v.to_str().ok())
.unwrap_or("");
assert!(
ct.contains("application/problem+json"),
"expected problem+json, got {ct}"
);
}
#[cfg(feature = "ratelimit-memory")]
#[tokio::test]
async fn rate_limit_blocks_after_limit_exceeded() {
use socle::RateLimitBackend;
let (client, _stop) = spawn_service(|s| {
s.with_rate_limit(RateLimitBackend {
limit: 2,
window_secs: 60,
})
.with_router(|_: &BootstrapCtx| Router::new().route("/", get(|| async { "ok" })))
})
.await;
assert_eq!(client.get("/").await.status(), 200);
assert_eq!(client.get("/").await.status(), 200);
assert_eq!(client.get("/").await.status(), 429);
}