Tests
This commit is contained in:
parent
8e728cca58
commit
f7036a18a0
2
.idea/dataSources.xml
generated
2
.idea/dataSources.xml
generated
@ -5,7 +5,7 @@
|
||||
<driver-ref>postgresql</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:postgresql://localhost:32784/postgres</jdbc-url>
|
||||
<jdbc-url>jdbc:postgresql://localhost:32769/postgres</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
|
19
Cargo.lock
generated
19
Cargo.lock
generated
@ -441,18 +441,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bon"
|
||||
version = "2.0.0"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea8256e3cff531086cc3faf94c1649930ff64bceb2d0e8cc84fc0356d7ee9806"
|
||||
checksum = "811d7882589e047896e5974d039dd8823a67973a63d559e6ad1e87ff5c42ed4f"
|
||||
dependencies = [
|
||||
"bon-macros",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bon-macros"
|
||||
version = "2.0.0"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b99838f77c5073bc7846ecce92b64e7e5a5bd152a8ec392facf90ee4d90b4b35"
|
||||
checksum = "d8e745a763e579a5ce70130e66f9dd35abf77cfeb9f418f305aeab8d1ae54c43"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"ident_case",
|
||||
@ -1509,7 +1510,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
name = "lib"
|
||||
version = "1.4.3"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
"bon",
|
||||
"chrono",
|
||||
"deadpool-diesel",
|
||||
"derive_more",
|
||||
@ -1517,9 +1520,12 @@ dependencies = [
|
||||
"diesel-async",
|
||||
"diesel-crud-derive",
|
||||
"diesel-crud-trait",
|
||||
"diesel_async_migrations",
|
||||
"into-response-derive",
|
||||
"mime",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"testcontainers-modules",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tower 0.5.0",
|
||||
@ -2841,15 +2847,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.5.2"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
|
||||
checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bytes",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"pin-project-lite",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
|
@ -45,6 +45,7 @@ lib = { path = "../lib", features = ["axum", "serde", "derive", "diesel", "time"
|
||||
#lib = { git = "https://github.com/emberal/rust-lib", tag = "1.4.3", features = ["axum", "serde", "derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
lib = { path = "../lib", features = ["test"] }
|
||||
rstest = "0.22.0"
|
||||
testcontainers-modules = { version = "0.10.0", features = ["postgres"] }
|
||||
async-std = { version = "1.12.0", features = ["attributes"] }
|
||||
|
@ -1,35 +1,10 @@
|
||||
use crate::config;
|
||||
use crate::error::AppError;
|
||||
use axum::async_trait;
|
||||
use deadpool_diesel::postgres::BuildError;
|
||||
use deadpool_diesel::Status;
|
||||
use diesel_async::pooled_connection::deadpool::{Object, Pool};
|
||||
use diesel_async::pooled_connection::AsyncDieselConnectionManager;
|
||||
use diesel_async::AsyncPgConnection;
|
||||
|
||||
pub type PgPool = Pool<AsyncPgConnection>;
|
||||
|
||||
#[async_trait]
|
||||
pub trait GetConnection: Clone + Send + Sync {
|
||||
async fn get(&self) -> Result<Object<AsyncPgConnection>, AppError>;
|
||||
fn status(&self) -> Status;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl GetConnection for PgPool {
|
||||
async fn get(&self) -> Result<Object<AsyncPgConnection>, AppError> {
|
||||
self.get().await.map_err(Into::into)
|
||||
}
|
||||
fn status(&self) -> Status {
|
||||
self.status()
|
||||
}
|
||||
}
|
||||
use lib::diesel::pool::PgPool;
|
||||
|
||||
pub(crate) fn create_pool() -> Result<PgPool, BuildError> {
|
||||
create_pool_from_url(config::DATABASE_URL)
|
||||
}
|
||||
|
||||
pub(crate) fn create_pool_from_url(url: impl Into<String>) -> Result<PgPool, BuildError> {
|
||||
let config = AsyncDieselConnectionManager::<AsyncPgConnection>::new(url);
|
||||
Pool::builder(config).max_size(config::POOL_SIZE).build()
|
||||
lib::diesel::pool::create_pool()
|
||||
.url(config::DATABASE_URL)
|
||||
.size(config::POOL_SIZE)
|
||||
.call()
|
||||
}
|
||||
|
22
src/error.rs
22
src/error.rs
@ -1,11 +1,11 @@
|
||||
use crate::services::reservation_service::ReservationError;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum_login::AuthnBackend;
|
||||
use derive_more::Constructor;
|
||||
use diesel::result::DatabaseErrorKind;
|
||||
use diesel_async::pooled_connection::deadpool;
|
||||
|
||||
use crate::services::reservation_service::ReservationError;
|
||||
use lib::diesel::get_connection::GetConnectionError;
|
||||
use lib::diesel_crud_trait::CrudError;
|
||||
use lib::into_response_derive::IntoResponse;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -96,6 +96,15 @@ impl IntoResponse for ResponseError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GetConnectionError> for ResponseError {
|
||||
fn from(value: GetConnectionError) -> Self {
|
||||
match value {
|
||||
GetConnectionError::PoolError(error) => Self::InternalServerError(error.to_string()),
|
||||
GetConnectionError::DieselError(error) => Self::InternalServerError(error.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CrudError> for ResponseError {
|
||||
fn from(value: CrudError) -> Self {
|
||||
match &value {
|
||||
@ -156,6 +165,15 @@ impl IntoResponse for AppError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GetConnectionError> for AppError {
|
||||
fn from(value: GetConnectionError) -> Self {
|
||||
match value {
|
||||
GetConnectionError::PoolError(error) => Self::PoolError(error),
|
||||
GetConnectionError::DieselError(error) => Self::DatabaseError(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<base64ct::Error> for AppError {
|
||||
fn from(value: base64ct::Error) -> Self {
|
||||
Self::Base64Error(value.to_string())
|
||||
|
11
src/main.rs
11
src/main.rs
@ -2,13 +2,16 @@
|
||||
#[macro_use]
|
||||
extern crate rstest;
|
||||
|
||||
use crate::database::{create_pool, GetConnection};
|
||||
use crate::database::create_pool;
|
||||
use crate::services::session_service::SessionService;
|
||||
use crate::services::user_service::UserService;
|
||||
use axum::middleware::{from_fn, Next};
|
||||
use axum::response::IntoResponse;
|
||||
use axum::Router;
|
||||
use axum_login::tower_sessions::SessionManagerLayer;
|
||||
use axum_login::AuthManagerLayerBuilder;
|
||||
use lib::axum::app::AppBuilder;
|
||||
use lib::diesel::get_connection::GetConnection;
|
||||
use tower_sessions::cookie::time::Duration;
|
||||
use tower_sessions::Expiry;
|
||||
|
||||
@ -73,7 +76,7 @@ async fn main() {
|
||||
trait LoginRequired {
|
||||
fn login_required<Pool>(self) -> Self
|
||||
where
|
||||
Pool: GetConnection + Send + Sync + 'static;
|
||||
Pool: GetConnection + 'static;
|
||||
}
|
||||
|
||||
impl<S> LoginRequired for Router<S>
|
||||
@ -84,10 +87,6 @@ where
|
||||
where
|
||||
Pool: GetConnection + Send + Sync + 'static,
|
||||
{
|
||||
use axum_login::axum::{
|
||||
middleware::{from_fn, Next},
|
||||
response::IntoResponse,
|
||||
};
|
||||
self.route_layer(from_fn(
|
||||
|auth_session: axum_login::AuthSession<UserService<Pool>>, req, next: Next| async move {
|
||||
if auth_session.user.is_some() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::auth::AuthSession;
|
||||
use crate::database::GetConnection;
|
||||
use crate::error::ResponseError;
|
||||
use crate::models::user::{CreateUser, User, UserCredentials};
|
||||
use crate::result::{ResponseResult, Success};
|
||||
use crate::services::user_service::UserService;
|
||||
@ -7,17 +7,17 @@ use crate::{bad_request, internal_server_error, ok};
|
||||
use axum::extract::State;
|
||||
use axum::Json;
|
||||
use axum_valid::Valid;
|
||||
use lib::diesel::get_connection::GetConnection;
|
||||
// router!(
|
||||
// "/auth",
|
||||
// routes!(
|
||||
// post "/login" => login::<Pool>,
|
||||
// post "/register" => register::<Pool>
|
||||
// ),
|
||||
// Pool: Clone, Send, Sync, GetConnection -> UserService
|
||||
// Pool: GetConnection -> UserService
|
||||
// );
|
||||
|
||||
pub fn router<Pool: Clone + Send + Sync + GetConnection + 'static>(
|
||||
) -> axum::Router<UserService<Pool>> {
|
||||
pub fn router<Pool: GetConnection + 'static>() -> axum::Router<UserService<Pool>> {
|
||||
axum::Router::new().nest(
|
||||
"/auth",
|
||||
axum::Router::new()
|
||||
@ -53,7 +53,7 @@ where
|
||||
.insert(create_user)
|
||||
.await
|
||||
.map(Success::Created)
|
||||
.map_err(Into::into)
|
||||
.map_err(ResponseError::from)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -61,26 +61,51 @@ mod tests {
|
||||
use crate::create_app;
|
||||
use crate::error::ErrorResponseBody;
|
||||
use crate::models::user::{CreateUser, User, UserRole};
|
||||
use crate::test::{create_test_containers_pool, create_test_pool, BuildJson, DeserializeInto};
|
||||
use crate::test::create_test_containers_pool;
|
||||
use axum::http::request::Builder;
|
||||
use axum::http::{Request, StatusCode};
|
||||
use axum::Router;
|
||||
use futures::executor::block_on;
|
||||
use lib::axum::traits::BuildJson;
|
||||
use lib::serde::traits::DeserializeInto;
|
||||
use secrecy::ExposeSecret;
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::{Serialize, Serializer};
|
||||
use testcontainers_modules::postgres::Postgres;
|
||||
use testcontainers_modules::testcontainers::ContainerAsync;
|
||||
use tower::ServiceExt;
|
||||
|
||||
fn register() -> Builder {
|
||||
Request::builder().uri("/auth/register").method("POST")
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_register_created() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
let app = create_app(pool);
|
||||
let create_user = CreateUser::new("test@email.com", "password");
|
||||
#[fixture]
|
||||
#[once]
|
||||
fn setup() -> Setup {
|
||||
block_on(async {
|
||||
let test_container = create_test_containers_pool().await.unwrap();
|
||||
let app = create_app(test_container.pool);
|
||||
Setup {
|
||||
_container: test_container.container,
|
||||
app,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
struct Setup {
|
||||
_container: ContainerAsync<Postgres>,
|
||||
app: Router,
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_register_created(setup: &Setup) {
|
||||
let create_user = CreateUser::new("test_register_created@email.com", "password");
|
||||
let create_email = create_user.email.clone();
|
||||
|
||||
let response = app
|
||||
let response = setup
|
||||
.app
|
||||
.clone()
|
||||
.oneshot(register().json(create_user).unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
@ -95,15 +120,18 @@ mod tests {
|
||||
assert!(!user.salt.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_register_email_already_registered() {
|
||||
let test_container = create_test_containers_pool().await.unwrap();
|
||||
let app = create_app(test_container.pool);
|
||||
|
||||
let create_user = CreateUser::new("test@email.com", "password");
|
||||
#[rstest]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_register_email_already_registered(setup: &Setup) {
|
||||
let create_user = CreateUser::new(
|
||||
"test_register_email_already_registered@email.com",
|
||||
"password",
|
||||
);
|
||||
|
||||
let call = || async {
|
||||
app.clone()
|
||||
setup
|
||||
.app
|
||||
.clone()
|
||||
.oneshot(register().json(create_user.clone()).unwrap())
|
||||
.await
|
||||
.unwrap()
|
||||
@ -129,7 +157,7 @@ mod tests {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut s = serializer.serialize_struct("CreateUser", 2)?;
|
||||
let mut s = serializer.serialize_struct("CreateUser", 3)?;
|
||||
s.serialize_field("email", &self.email)?;
|
||||
s.serialize_field("password", &self.password.expose_secret())?;
|
||||
s.serialize_field("role", &self.role)?;
|
||||
|
@ -115,28 +115,27 @@ mod tests {
|
||||
use crate::models::room::Room;
|
||||
use crate::models::user::{CreateUser, User};
|
||||
use crate::schema::{hotel, room, user};
|
||||
use crate::test::setup_test_transaction;
|
||||
use crate::test::create_test_containers_pool;
|
||||
use chrono::{Duration, Utc};
|
||||
use diesel::dsl::insert_into;
|
||||
use diesel_async::AsyncPgConnection;
|
||||
use futures::executor::block_on;
|
||||
use lib::diesel::pool::PgPool;
|
||||
use lib::diesel_crud_trait::{DieselCrudCreate, DieselCrudRead};
|
||||
use secrecy::SecretString;
|
||||
use testcontainers_modules::postgres::Postgres;
|
||||
use testcontainers_modules::testcontainers::ContainerAsync;
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_check_in(#[future] setup: Setup) {
|
||||
let Setup {
|
||||
mut conn,
|
||||
reservation,
|
||||
..
|
||||
} = setup.await;
|
||||
|
||||
Reservation::check_in(reservation.id, &mut conn)
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_check_in(setup: &Setup) {
|
||||
let mut conn = setup.pool.get().await.unwrap();
|
||||
Reservation::check_in(setup.reservation.id, &mut conn)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
Reservation::read(reservation.id, &mut conn)
|
||||
Reservation::read(setup.reservation.id, &mut conn)
|
||||
.await
|
||||
.unwrap()
|
||||
.checked_in
|
||||
@ -144,20 +143,15 @@ mod tests {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_check_out(#[future] setup: Setup) {
|
||||
let Setup {
|
||||
mut conn,
|
||||
reservation,
|
||||
..
|
||||
} = setup.await;
|
||||
|
||||
Reservation::check_out(reservation.id, &mut conn)
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_check_out(setup: &Setup) {
|
||||
let mut conn = setup.pool.get().await.unwrap();
|
||||
Reservation::check_out(setup.reservation.id, &mut conn)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
!Reservation::read(reservation.id, &mut conn)
|
||||
!Reservation::read(setup.reservation.id, &mut conn)
|
||||
.await
|
||||
.unwrap()
|
||||
.checked_in
|
||||
@ -190,40 +184,34 @@ mod tests {
|
||||
Duration::days(11),
|
||||
Err(ReservationError::RoomNotAvailable)
|
||||
)]
|
||||
#[tokio::test]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_room_available(
|
||||
#[future] setup: Setup,
|
||||
setup: &Setup,
|
||||
#[case] start: Duration,
|
||||
#[case] end: Duration,
|
||||
#[case] expected: Result<(), ReservationError>,
|
||||
) {
|
||||
let Setup {
|
||||
mut conn,
|
||||
reservation,
|
||||
..
|
||||
} = setup.await;
|
||||
let mut conn = setup.pool.get().await.unwrap();
|
||||
|
||||
let now = Utc::now().naive_utc();
|
||||
let start = now + start;
|
||||
let end = now + end;
|
||||
|
||||
assert_eq!(
|
||||
Reservation::room_available(reservation.room_id, (start, end).into(), &mut conn).await,
|
||||
Reservation::room_available(setup.reservation.room_id, (start, end).into(), &mut conn)
|
||||
.await,
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_room_available_no_reservations(#[future] setup: Setup) {
|
||||
let Setup {
|
||||
mut conn, hotel, ..
|
||||
} = setup.await;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_room_available_no_reservations(setup: &Setup) {
|
||||
let mut conn = setup.pool.get().await.unwrap();
|
||||
let room = Room::insert(
|
||||
Room {
|
||||
id: 2,
|
||||
hotel_id: hotel.id,
|
||||
hotel_id: setup.hotel.id,
|
||||
beds: 1,
|
||||
size: 1,
|
||||
},
|
||||
@ -243,21 +231,30 @@ mod tests {
|
||||
}
|
||||
|
||||
#[fixture]
|
||||
async fn setup() -> Setup {
|
||||
let mut conn = setup_test_transaction().await.unwrap();
|
||||
let hotel = insert_hotel(&mut conn).await;
|
||||
let user = insert_user(&mut conn).await;
|
||||
let room = insert_room(&mut conn, hotel.id).await;
|
||||
let reservation = insert_reservation(&mut conn, room.id, user.email).await;
|
||||
Setup {
|
||||
conn,
|
||||
hotel,
|
||||
reservation,
|
||||
}
|
||||
#[once]
|
||||
fn setup() -> Setup {
|
||||
block_on(async {
|
||||
let test_container = create_test_containers_pool().await.unwrap();
|
||||
let pool = test_container.pool;
|
||||
let mut conn = pool.get().await.unwrap();
|
||||
|
||||
let hotel = insert_hotel(&mut conn).await;
|
||||
let user = insert_user(&mut conn).await;
|
||||
let room = insert_room(&mut conn, hotel.id).await;
|
||||
let reservation = insert_reservation(&mut conn, room.id, user.email).await;
|
||||
|
||||
Setup {
|
||||
_container: test_container.container,
|
||||
pool,
|
||||
hotel,
|
||||
reservation,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
struct Setup {
|
||||
conn: AsyncPgConnection,
|
||||
_container: ContainerAsync<Postgres>,
|
||||
pool: PgPool,
|
||||
hotel: Hotel,
|
||||
reservation: Reservation,
|
||||
}
|
||||
|
@ -55,13 +55,14 @@ impl Room {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config;
|
||||
use crate::models::hotel::{CreateHotel, Hotel};
|
||||
use crate::models::reservation::{CreateReservation, Reservation};
|
||||
use crate::models::room::Room;
|
||||
use crate::models::user::{CreateUser, User};
|
||||
use crate::test::setup_test_transaction;
|
||||
use diesel_async::AsyncPgConnection;
|
||||
use lib::diesel_crud_trait::DieselCrudCreate;
|
||||
use lib::test::diesel_pool::setup_test_transaction;
|
||||
use serde_json::json;
|
||||
|
||||
#[rstest]
|
||||
@ -148,7 +149,7 @@ mod tests {
|
||||
|
||||
#[fixture]
|
||||
async fn setup() -> Setup {
|
||||
let mut conn = setup_test_transaction().await.unwrap();
|
||||
let mut conn = setup_test_transaction(config::DATABASE_URL).await.unwrap();
|
||||
let hotels = insert_hotels(&mut conn).await;
|
||||
let rooms = insert_rooms(&mut conn, &hotels).await;
|
||||
let _reservation = insert_reservation(&mut conn, rooms[0].id).await;
|
||||
|
124
src/test.rs
124
src/test.rs
@ -1,78 +1,11 @@
|
||||
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,
|
||||
}
|
||||
use diesel_async::AsyncPgConnection;
|
||||
use lib::test::test_containers::{ContainerError, TestContainer};
|
||||
|
||||
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
|
||||
let test_container = lib::test::test_containers::create_test_containers_pool().await?;
|
||||
run_migrations(test_container.pool.get().await?.as_mut()).await?;
|
||||
Ok(test_container)
|
||||
}
|
||||
|
||||
pub(crate) async fn run_migrations(
|
||||
@ -80,50 +13,3 @@ pub(crate) async fn run_migrations(
|
||||
) -> 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)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user