130 lines
4.1 KiB
Rust
130 lines
4.1 KiB
Rust
![]() |
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<AsyncPgConnection, Error> {
|
||
|
let mut conn = AsyncPgConnection::establish(config::DATABASE_URL).await?;
|
||
|
conn.begin_test_transaction().await?;
|
||
|
Ok(conn)
|
||
|
}
|
||
|
|
||
|
pub(crate) async fn create_test_pool() -> Result<PoolStub, BuildError> {
|
||
|
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<Postgres>,
|
||
|
pub pool: PgPool,
|
||
|
}
|
||
|
|
||
|
pub(crate) async fn create_test_containers_pool<'a>() -> Result<TestContainer, ContainerError> {
|
||
|
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<ContainerAsync<Postgres>, 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<Object<AsyncPgConnection>, 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<T: Serialize>(self, body: T) -> Result<Request<Body>, axum::http::Error>;
|
||
|
}
|
||
|
|
||
|
impl BuildJson for axum::http::request::Builder {
|
||
|
fn json<T: Serialize>(self, body: T) -> Result<Request<Body>, 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<T: for<'de> Deserialize<'de>>(self) -> Result<T, DeserializeError>;
|
||
|
}
|
||
|
|
||
|
#[async_trait]
|
||
|
impl DeserializeInto for Response {
|
||
|
async fn deserialize_into<T: for<'de> Deserialize<'de>>(self) -> Result<T, DeserializeError> {
|
||
|
let body = to_bytes(self.into_body(), usize::MAX).await?;
|
||
|
serde_json::from_slice(&body).map_err(Into::into)
|
||
|
}
|
||
|
}
|