use std::net::SocketAddr;
use axum::Router;
use tokio::net::TcpListener;
use tokio::sync::oneshot;
use tokio::task::JoinHandle;
use super::TestClient;
pub struct TestApp {
pub addr: SocketAddr,
shutdown_tx: Option<oneshot::Sender<()>>,
handle: Option<JoinHandle<()>>,
}
impl TestApp {
pub fn builder() -> TestAppBuilder {
TestAppBuilder::default()
}
pub fn client(&self) -> TestClient {
TestClient::new(format!("http://{}", self.addr))
}
pub async fn shutdown(mut self) {
if let Some(tx) = self.shutdown_tx.take() {
let _ = tx.send(());
}
if let Some(handle) = self.handle.take() {
let _ = handle.await;
}
}
}
impl Drop for TestApp {
fn drop(&mut self) {
if let Some(tx) = self.shutdown_tx.take() {
let _ = tx.send(());
}
if let Some(handle) = self.handle.take() {
handle.abort();
}
}
}
#[derive(Default)]
pub struct TestAppBuilder {
router: Option<Router>,
}
impl TestAppBuilder {
pub fn router(mut self, router: Router) -> Self {
self.router = Some(router);
self
}
pub async fn build(self) -> TestApp {
let listener = TcpListener::bind("127.0.0.1:0")
.await
.expect("failed to bind ephemeral port");
let addr = listener.local_addr().expect("no local addr");
let router = self.router.unwrap_or_default();
let (tx, rx) = oneshot::channel::<()>();
let handle = tokio::spawn(async move {
axum::serve(listener, router)
.with_graceful_shutdown(async {
let _ = rx.await;
})
.await
.expect("test server failed");
});
TestApp {
addr,
shutdown_tx: Some(tx),
handle: Some(handle),
}
}
}