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