use axum::body::Body;
use axum::http::{Request, StatusCode};
use tower::ServiceExt;
#[tokio::test]
async fn health_and_fallback() {
let app = jammi_server::build_router();
let resp = app
.clone()
.oneshot(
Request::builder()
.uri("/healthz")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let body: serde_json::Value = serde_json::from_slice(
&axum::body::to_bytes(resp.into_body(), usize::MAX)
.await
.unwrap(),
)
.unwrap();
assert_eq!(body["status"], "ok");
let resp = app
.oneshot(
Request::builder()
.uri("/nonexistent")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let body: serde_json::Value = serde_json::from_slice(
&axum::body::to_bytes(resp.into_body(), usize::MAX)
.await
.unwrap(),
)
.unwrap();
assert!(body["error"].is_string());
assert!(body["code"].is_string());
}
#[test]
fn server_config_validation() {
use jammi_db::config::ServerConfig;
assert!(ServerConfig::default().validate().is_ok());
let bad = ServerConfig {
health_listen: "not-an-address".into(),
..Default::default()
};
let err = bad.validate().unwrap_err().to_string();
assert!(
err.contains("health_listen"),
"Error should mention 'health_listen': {err}"
);
let same = ServerConfig {
health_listen: "0.0.0.0:8080".into(),
flight_listen: "0.0.0.0:8080".into(),
..Default::default()
};
assert!(
same.validate().is_err(),
"Same health_listen and flight_listen should fail"
);
let ephemeral = ServerConfig {
health_listen: "127.0.0.1:0".into(),
flight_listen: "127.0.0.1:0".into(),
..Default::default()
};
assert!(
ephemeral.validate().is_ok(),
"identical ephemeral :0 addresses must be allowed (distinct ports at bind)"
);
}
#[tokio::test]
async fn concurrent_health_checks() {
let mut handles = Vec::new();
for _ in 0..20 {
let app = jammi_server::build_router();
handles.push(tokio::spawn(async move {
let resp = app
.oneshot(
Request::builder()
.uri("/healthz")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
resp.status()
}));
}
for h in handles {
assert_eq!(h.await.unwrap(), StatusCode::OK);
}
}
#[tokio::test]
async fn graceful_shutdown_completes_cleanly() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let (tx, rx) = tokio::sync::oneshot::channel::<()>();
let shutdown = async move {
let _ = rx.await;
};
let app = jammi_server::build_router();
let server_handle = tokio::spawn(async move {
axum::serve(listener, app)
.with_graceful_shutdown(shutdown)
.await
});
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
let client = reqwest::Client::new();
let resp = client
.get(format!("http://{addr}/healthz"))
.send()
.await
.expect("Health request should succeed while server is running");
assert_eq!(resp.status(), 200);
tx.send(()).expect("Shutdown signal should send");
let result = tokio::time::timeout(std::time::Duration::from_secs(5), server_handle)
.await
.expect("Server should shut down within 5 seconds")
.expect("Server task should not panic");
assert!(result.is_ok(), "Server should exit with Ok(())");
}