use std::net::SocketAddr; use aws_credential_types::Credentials; use aws_sdk_s3::Client; use post3::PostgresBackend; use sqlx::PgPool; use tokio::net::TcpListener; use tokio_util::sync::CancellationToken; static TRACING: std::sync::Once = std::sync::Once::new(); fn init_tracing() { TRACING.call_once(|| { tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::from_default_env() .add_directive("post3_server=debug".parse().unwrap()) .add_directive("tower_http=debug".parse().unwrap()), ) .with_test_writer() .init(); }); } pub struct TestServer { pub addr: SocketAddr, pub client: Client, cancel: CancellationToken, pool: PgPool, } impl TestServer { pub async fn start() -> Self { init_tracing(); let db_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| { "postgresql://devuser:devpassword@localhost:5435/post3_dev".into() }); let pool = sqlx::pool::PoolOptions::new() .max_connections(5) .connect(&db_url) .await .unwrap(); // Run migrations sqlx::migrate!("./migrations").run(&pool).await.unwrap(); // Clean slate sqlx::query("DELETE FROM upload_parts").execute(&pool).await.unwrap(); sqlx::query("DELETE FROM multipart_upload_metadata").execute(&pool).await.unwrap(); sqlx::query("DELETE FROM multipart_uploads").execute(&pool).await.unwrap(); sqlx::query("DELETE FROM blocks").execute(&pool).await.unwrap(); sqlx::query("DELETE FROM object_metadata").execute(&pool).await.unwrap(); sqlx::query("DELETE FROM objects").execute(&pool).await.unwrap(); sqlx::query("DELETE FROM buckets").execute(&pool).await.unwrap(); let backend = PostgresBackend::new(pool.clone()); let state = post3_server::state::State { store: backend }; let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); let cancel = CancellationToken::new(); let cancel_clone = cancel.clone(); let router = post3_server::s3::router::build_router(state); tokio::spawn(async move { axum::serve(listener, router.into_make_service()) .with_graceful_shutdown(async move { cancel_clone.cancelled().await; }) .await .unwrap(); }); let creds = Credentials::new("test", "test", None, None, "test"); let config = aws_sdk_s3::Config::builder() .behavior_version_latest() .region(aws_types::region::Region::new("us-east-1")) .endpoint_url(format!("http://{}", addr)) .credentials_provider(creds) .force_path_style(true) .build(); let client = Client::from_conf(config); Self { addr, client, cancel, pool, } } pub async fn shutdown(self) { self.cancel.cancel(); // Give the server task a moment to wind down tokio::time::sleep(std::time::Duration::from_millis(50)).await; self.pool.close().await; } }