use std::net::TcpListener;
use std::time::Duration;
use crate::server::{Host, ServerHandle};
pub fn free_port() -> u16 {
TcpListener::bind("127.0.0.1:0")
.expect("bind ephemeral port")
.local_addr()
.expect("local_addr")
.port()
}
pub async fn wait_until_ready(base_url: &str, health_path: &str) {
let url = format!(
"{}/{}",
base_url.trim_end_matches('/'),
health_path.trim_start_matches('/')
);
for _ in 0..60 {
if let Ok(resp) = reqwest::get(&url).await {
if resp.status().is_success() {
return;
}
}
tokio::time::sleep(Duration::from_millis(200)).await;
}
panic!("server failed to become ready at {url}");
}
pub struct TestServer {
pub base_url: String,
pub handle: ServerHandle,
pub task: tokio::task::JoinHandle<()>,
}
impl TestServer {
pub async fn teardown(self) {
self.handle.shutdown();
let _ = tokio::time::timeout(Duration::from_secs(5), self.task).await;
}
}
pub async fn spawn(host: Host, port: u16) -> TestServer {
spawn_with_health(host, port, "/health/live").await
}
pub async fn spawn_with_health(host: Host, port: u16, health_path: &str) -> TestServer {
let addr = format!("127.0.0.1:{port}");
let base_url = format!("http://{addr}");
let handle = host.server_handle();
let task = tokio::spawn(async move {
let _ = host.run_at(&addr).await;
});
wait_until_ready(&base_url, health_path).await;
TestServer {
base_url,
handle,
task,
}
}