use crate::config; use crate::database::{create_pool, create_pool_from_url, GetConnection, PgPool}; use crate::error::AppError; use axum::async_trait; use axum::body::{to_bytes, Body}; use axum::http::header::CONTENT_TYPE; use axum::http::Request; use axum::response::Response; use deadpool_diesel::postgres::BuildError; use deadpool_diesel::Status; use derive_more::Constructor; use diesel_async::pooled_connection::deadpool::{Object, PoolError}; use diesel_async::{AsyncConnection, AsyncPgConnection}; use mime::APPLICATION_JSON; use serde::{Deserialize, Serialize}; use serde_json::json; use testcontainers_modules::postgres::Postgres; use testcontainers_modules::testcontainers::runners::AsyncRunner; use testcontainers_modules::testcontainers::{ContainerAsync, TestcontainersError}; use thiserror::Error; #[derive(Debug, PartialEq, Error)] pub enum Error { #[error(transparent)] Connection(#[from] diesel::ConnectionError), #[error(transparent)] Database(#[from] diesel::result::Error), } pub async fn setup_test_transaction() -> Result { let mut conn = AsyncPgConnection::establish(config::DATABASE_URL).await?; conn.begin_test_transaction().await?; Ok(conn) } pub(crate) async fn create_test_pool() -> Result { let pool = create_pool()?; Ok(PoolStub(pool)) } #[derive(Debug, Error)] pub(crate) enum ContainerError { #[error(transparent)] TestContainers(#[from] TestcontainersError), #[error(transparent)] BuildError(#[from] BuildError), #[error(transparent)] PoolError(#[from] PoolError), #[error(transparent)] DieselError(#[from] diesel::result::Error), } /// When the TestContainer is dropped, the container will be removed. /// # Panics /// If destructed and the container field is dropped, the container will be removed, and using the pool will cause panic. #[derive(Constructor)] pub(crate) struct TestContainer { pub _container: ContainerAsync, pub pool: PgPool, } pub(crate) async fn create_test_containers_pool<'a>() -> Result { let container = create_postgres_container().await?; let connection_string = format!( "postgres://postgres:postgres@127.0.0.1:{}/postgres", container.get_host_port_ipv4(5432).await? ); let pool = create_pool_from_url(connection_string)?; run_migrations(pool.get().await?.as_mut()).await?; Ok(TestContainer::new(container, pool)) } pub(crate) async fn create_postgres_container( ) -> Result, TestcontainersError> { Postgres::default().start().await } pub(crate) async fn run_migrations( conn: &mut AsyncPgConnection, ) -> Result<(), diesel::result::Error> { config::MIGRATIONS.run_pending_migrations(conn).await } #[derive(Clone)] pub(crate) struct PoolStub(PgPool); #[async_trait] impl GetConnection for PoolStub { async fn get(&self) -> Result, AppError> { let mut conn = self.0.get().await?; conn.begin_test_transaction().await?; Ok(conn) } fn status(&self) -> Status { unimplemented!("PoolStub does not support status") } } pub trait BuildJson { fn json(self, body: T) -> Result, axum::http::Error>; } impl BuildJson for axum::http::request::Builder { fn json(self, body: T) -> Result, axum::http::Error> { self.header(CONTENT_TYPE, APPLICATION_JSON.as_ref()) .body(Body::new(json!(body).to_string())) } } #[derive(Debug, Error)] pub(crate) enum DeserializeError { #[error(transparent)] SerdeError(#[from] serde_json::Error), #[error(transparent)] AxumError(#[from] axum::Error), } #[async_trait] pub trait DeserializeInto { async fn deserialize_into Deserialize<'de>>(self) -> Result; } #[async_trait] impl DeserializeInto for Response { async fn deserialize_into Deserialize<'de>>(self) -> Result { let body = to_bytes(self.into_body(), usize::MAX).await?; serde_json::from_slice(&body).map_err(Into::into) } }