From e0975cc94b4b458dc75bc3aa16df88dae9c68bfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Wo=C5=BAniak?= Date: Mon, 6 Jun 2022 16:07:49 +0200 Subject: [PATCH] Start testing orders --- .../database_manager/src/order_addresses.rs | 14 + actors/database_manager/src/order_items.rs | 2 +- actors/database_manager/src/orders.rs | 365 +++++++++++++++--- actors/order_manager/src/lib.rs | 9 +- actors/payment_manager/src/lib.rs | 19 +- api/src/routes/admin/api_v1/orders.rs | 2 +- shared/model/src/api.rs | 5 - shared/model/src/lib.rs | 8 +- 8 files changed, 357 insertions(+), 67 deletions(-) diff --git a/actors/database_manager/src/order_addresses.rs b/actors/database_manager/src/order_addresses.rs index e0b0386..67581dc 100644 --- a/actors/database_manager/src/order_addresses.rs +++ b/actors/database_manager/src/order_addresses.rs @@ -128,3 +128,17 @@ RETURNING id, name, email, street, city, country, zip .await .map_err(|_| Error::CreateOrderAddress.into()) } + +#[cfg(test)] +mod tests { + use config::UpdateConfig; + use fake::Fake; + use model::*; + use uuid::Uuid; + + pub struct NoOpts; + + impl UpdateConfig for NoOpts {} + + use crate::*; +} diff --git a/actors/database_manager/src/order_items.rs b/actors/database_manager/src/order_items.rs index 578ea8f..fb85f90 100644 --- a/actors/database_manager/src/order_items.rs +++ b/actors/database_manager/src/order_items.rs @@ -109,7 +109,7 @@ WHERE id = $1 #[derive(actix::Message)] #[rtype(result = "Result>")] pub struct OrderItems { - pub order_id: model::OrderId, + pub order_id: OrderId, } db_async_handler!(OrderItems, order_items, Vec); diff --git a/actors/database_manager/src/orders.rs b/actors/database_manager/src/orders.rs index e67d961..159635c 100644 --- a/actors/database_manager/src/orders.rs +++ b/actors/database_manager/src/orders.rs @@ -1,5 +1,4 @@ use model::*; -use sqlx::PgPool; use super::Result; use crate::{ @@ -22,11 +21,14 @@ pub enum Error { #[derive(actix::Message)] #[rtype(result = "Result>")] -pub struct AllAccountOrders; +pub struct AllOrders; -db_async_handler!(AllAccountOrders, all_orders, Vec); +db_async_handler!(AllOrders, all_orders, Vec, inner_all_orders); -pub(crate) async fn all_orders(_msg: AllAccountOrders, pool: PgPool) -> Result> { +pub(crate) async fn all_orders( + _msg: AllOrders, + t: &mut sqlx::Transaction<'_, sqlx::Postgres>, +) -> Result> { sqlx::query_as( r#" SELECT id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, address_id @@ -34,7 +36,7 @@ FROM orders ORDER BY id DESC "#, ) - .fetch_all(&pool) + .fetch_all(t) .await .map_err(|e| { log::error!("{e:?}"); @@ -54,39 +56,38 @@ pub mod create_order { #[derive(actix::Message)] #[rtype(result = "Result")] -pub struct CreateAccountOrder { +pub struct CreateOrder { pub buyer_id: AccountId, pub items: Vec, - pub shopping_cart_id: ShoppingCartId, + pub shopping_cart_id: Option, pub checkout_notes: Option, + pub delivery_address_id: OrderAddressId, } -db_async_handler!( - CreateAccountOrder, - create_account_order, - Order, - inner_create_account_order -); +db_async_handler!(CreateOrder, create_order, Order, inner_create_order); -pub(crate) async fn create_account_order( - msg: CreateAccountOrder, +pub(crate) async fn create_order( + msg: CreateOrder, t: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result { let order: Order = match sqlx::query_as( r#" -INSERT INTO orders (buyer_id, status) -VALUES ($1, $2, $3) +INSERT INTO orders (buyer_id, status, checkout_notes, address_id) +VALUES ($1, $2, $3, $4) RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, address_id "#, ) .bind(msg.buyer_id) .bind(OrderStatus::Confirmed) + .bind(msg.checkout_notes.as_deref()) + .bind(msg.delivery_address_id) .fetch_one(&mut *t) .await { Ok(order) => order, Err(e) => { log::error!("{e:?}"); + dbg!(e); return Err(super::Error::AccountOrder(Error::CantCreate)); } }; @@ -102,42 +103,49 @@ RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, ) .await { + dbg!(e); log::error!("{e:?}"); return Err(super::Error::AccountOrder(Error::CantCreate)); } } - if let Err(e) = shopping_cart_set_state( - ShoppingCartSetState { - id: msg.shopping_cart_id, - state: ShoppingCartState::Closed, - checkout_notes: msg.checkout_notes, - }, - t, - ) - .await - { - log::error!("{e:?}"); + if let Some(shopping_cart_id) = msg.shopping_cart_id { + if let Err(e) = shopping_cart_set_state( + ShoppingCartSetState { + id: shopping_cart_id, + state: ShoppingCartState::Closed, + checkout_notes: msg.checkout_notes, + }, + t, + ) + .await + { + dbg!(e); + log::error!("{e:?}"); - return Err(super::Error::AccountOrder(Error::CantCreate)); - }; + return Err(super::Error::AccountOrder(Error::CantCreate)); + }; + } Ok(order) } #[derive(actix::Message)] #[rtype(result = "Result")] -pub struct UpdateAccountOrder { +pub struct UpdateOrder { pub id: OrderId, pub buyer_id: AccountId, pub status: OrderStatus, pub order_id: Option, } -db_async_handler!(UpdateAccountOrder, update_account_order, Order); +db_async_handler!(UpdateOrder, update_order, Order, inner_update_order); -pub(crate) async fn update_account_order(msg: UpdateAccountOrder, db: PgPool) -> Result { +pub(crate) async fn update_order( + msg: UpdateOrder, + t: &mut sqlx::Transaction<'_, sqlx::Postgres>, +) -> Result { sqlx::query_as( r#" UPDATE orders @@ -150,7 +158,7 @@ RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, .bind(msg.buyer_id) .bind(msg.status) .bind(msg.order_id) - .fetch_one(&db) + .fetch_one(t) .await .map_err(|e| { log::error!("{e:?}"); @@ -160,16 +168,21 @@ RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, #[derive(actix::Message)] #[rtype(result = "Result")] -pub struct UpdateAccountOrderByExt { +pub struct UpdateOrderByExt { pub order_ext_id: String, pub status: OrderStatus, } -db_async_handler!(UpdateAccountOrderByExt, update_account_order_by_ext, Order); +db_async_handler!( + UpdateOrderByExt, + update_order_by_ext, + Order, + inner_update_order_by_ext +); -pub(crate) async fn update_account_order_by_ext( - msg: UpdateAccountOrderByExt, - db: PgPool, +pub(crate) async fn update_order_by_ext( + msg: UpdateOrderByExt, + t: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result { sqlx::query_as( r#" @@ -181,7 +194,7 @@ RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, ) .bind(msg.order_ext_id) .bind(msg.status) - .fetch_one(&db) + .fetch_one(t) .await .map_err(|e| { log::error!("{e:?}"); @@ -191,13 +204,21 @@ RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, #[derive(actix::Message)] #[rtype(result = "Result")] -pub struct FindAccountOrder { +pub struct FindOrder { pub id: OrderId, } -db_async_handler!(FindAccountOrder, find_account_order, Order); +db_async_handler!( + FindOrder, + find_account_order, + Order, + inner_find_account_order +); -pub(crate) async fn find_account_order(msg: FindAccountOrder, db: PgPool) -> Result { +pub(crate) async fn find_account_order( + msg: FindOrder, + t: &mut sqlx::Transaction<'_, sqlx::Postgres>, +) -> Result { sqlx::query_as( r#" SELECT id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, address_id @@ -206,7 +227,7 @@ WHERE id = $1 "#, ) .bind(msg.id) - .fetch_one(&db) + .fetch_one(t) .await .map_err(|e| { log::error!("{e:?}"); @@ -221,9 +242,17 @@ pub struct SetOrderServiceId { pub service_order_id: String, } -db_async_handler!(SetOrderServiceId, set_order_service_id, Order); +db_async_handler!( + SetOrderServiceId, + set_order_service_id, + Order, + inner_set_order_service_id +); -pub(crate) async fn set_order_service_id(msg: SetOrderServiceId, db: PgPool) -> Result { +pub(crate) async fn set_order_service_id( + msg: SetOrderServiceId, + t: &mut sqlx::Transaction<'_, sqlx::Postgres>, +) -> Result { sqlx::query_as( r#" UPDATE orders @@ -234,10 +263,252 @@ RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, ) .bind(msg.id) .bind(msg.service_order_id) - .fetch_one(&db) + .fetch_one(t) .await .map_err(|e| { log::error!("{e:?}"); super::Error::AccountOrder(Error::NotExists) }) } + +#[cfg(test)] +mod tests { + use config::UpdateConfig; + use fake::Fake; + use model::*; + use uuid::Uuid; + + pub struct NoOpts; + + impl UpdateConfig for NoOpts {} + + use crate::*; + + async fn test_order_address( + t: &mut sqlx::Transaction<'_, sqlx::Postgres>, + ) -> model::OrderAddress { + create_order_address( + CreateOrderAddress { + name: Default::default(), + email: Default::default(), + street: Default::default(), + city: Default::default(), + country: Default::default(), + zip: Default::default(), + }, + t, + ) + .await + .unwrap() + } + + async fn test_product(t: &mut sqlx::Transaction<'_, sqlx::Postgres>) -> Product { + create_product( + CreateProduct { + name: ProductName::new(format!("{}", Uuid::new_v4())), + short_description: ProductShortDesc::new(format!("{}", Uuid::new_v4())), + long_description: ProductLongDesc::new(format!("{}", Uuid::new_v4())), + category: None, + price: Price::from_u32(4687), + deliver_days_flag: Days(vec![Day::Friday, Day::Sunday]), + }, + t, + ) + .await + .unwrap() + } + + async fn test_shopping_cart( + t: &mut sqlx::Transaction<'_, sqlx::Postgres>, + buyer_id: Option, + state: ShoppingCartState, + ) -> ShoppingCart { + let buyer_id = match buyer_id { + Some(id) => id, + _ => test_account(&mut *t).await.id, + }; + + sqlx::query( + r#" +UPDATE shopping_carts +SET state = 'closed' +WHERE buyer_id = $1 + "#, + ) + .bind(buyer_id) + .execute(&mut *t) + .await + .unwrap(); + + let cart = create_shopping_cart( + CreateShoppingCart { + buyer_id, + payment_method: PaymentMethod::PaymentOnTheSpot, + }, + &mut *t, + ) + .await + .unwrap(); + + update_shopping_cart( + UpdateShoppingCart { + id: cart.id, + buyer_id: cart.buyer_id, + payment_method: cart.payment_method, + state, + checkout_notes: None, + }, + &mut *t, + ) + .await + .unwrap() + } + + async fn test_account(t: &mut sqlx::Transaction<'_, sqlx::Postgres>) -> FullAccount { + use fake::faker::internet::en; + let login: String = en::Username().fake(); + let email: String = en::FreeEmail().fake(); + let hash: String = en::Password(10..20).fake(); + + create_account( + CreateAccount { + email: Email::new(email), + login: Login::new(login), + pass_hash: PassHash::new(hash), + role: Role::Admin, + }, + t, + ) + .await + .unwrap() + } + + async fn test_empty_order_without_cart( + t: &mut sqlx::Transaction<'_, sqlx::Postgres>, + buyer_id: Option, + address_id: OrderAddressId, + ) -> Order { + let buyer_id = match buyer_id { + Some(id) => id, + _ => test_account(t).await.id, + }; + super::create_order( + CreateOrder { + buyer_id, + items: vec![], + shopping_cart_id: None, + checkout_notes: None, + delivery_address_id: address_id, + }, + t, + ) + .await + .unwrap() + } + + #[actix::test] + async fn empty_order_without_cart() { + testx::db_t!(t); + + let address_id = test_order_address(&mut t).await.id; + test_empty_order_without_cart(&mut t, None, address_id).await; + + testx::db_rollback!(t); + } + + #[actix::test] + async fn empty_order_with_cart() { + testx::db_t!(t); + + let buyer_id = test_account(&mut t).await.id; + let address_id = test_order_address(&mut t).await.id; + + let cart_id = test_shopping_cart(&mut t, Some(buyer_id), ShoppingCartState::Active) + .await + .id; + + super::create_order( + CreateOrder { + buyer_id, + items: vec![], + shopping_cart_id: Some(cart_id), + checkout_notes: None, + delivery_address_id: address_id, + }, + &mut t, + ) + .await + .unwrap(); + testx::db_rollback!(t); + } + + #[actix::test] + async fn non_empty_order_with_cart() { + testx::db_t!(t); + + let buyer_id = test_account(&mut t).await.id; + let address_id = test_order_address(&mut t).await.id; + + let cart_id = test_shopping_cart(&mut t, Some(buyer_id), ShoppingCartState::Active) + .await + .id; + + super::create_order( + CreateOrder { + buyer_id, + items: vec![ + create_order::OrderItem { + product_id: test_product(&mut t).await.id, + quantity: Default::default(), + quantity_unit: QuantityUnit::Gram, + }, + create_order::OrderItem { + product_id: test_product(&mut t).await.id, + quantity: Default::default(), + quantity_unit: QuantityUnit::Gram, + }, + ], + shopping_cart_id: Some(cart_id), + checkout_notes: None, + delivery_address_id: address_id, + }, + &mut t, + ) + .await + .unwrap(); + testx::db_rollback!(t); + } + + #[actix::test] + async fn non_empty_order_without_cart() { + testx::db_t!(t); + + let buyer_id = test_account(&mut t).await.id; + let address_id = test_order_address(&mut t).await.id; + + super::create_order( + CreateOrder { + buyer_id, + items: vec![ + create_order::OrderItem { + product_id: test_product(&mut t).await.id, + quantity: Default::default(), + quantity_unit: QuantityUnit::Gram, + }, + create_order::OrderItem { + product_id: test_product(&mut t).await.id, + quantity: Default::default(), + quantity_unit: QuantityUnit::Gram, + }, + ], + shopping_cart_id: None, + checkout_notes: None, + delivery_address_id: address_id, + }, + &mut t, + ) + .await + .unwrap(); + testx::db_rollback!(t); + } +} diff --git a/actors/order_manager/src/lib.rs b/actors/order_manager/src/lib.rs index 9066fd2..66a8f08 100644 --- a/actors/order_manager/src/lib.rs +++ b/actors/order_manager/src/lib.rs @@ -163,7 +163,7 @@ pub(crate) async fn create_account_order( } }; - query_db!( + let address: model::OrderAddress = query_db!( db, database_manager::CreateOrderAddress { name: address.name, @@ -178,8 +178,8 @@ pub(crate) async fn create_account_order( let order = query_db!( db, - database_manager::CreateAccountOrder { - shopping_cart_id: cart.id, + database_manager::CreateOrder { + shopping_cart_id: Some(cart.id), buyer_id: msg.account_id, items: items .into_iter() @@ -189,7 +189,8 @@ pub(crate) async fn create_account_order( quantity_unit: item.quantity_unit, }) .collect(), - checkout_notes: cart.checkout_notes + checkout_notes: cart.checkout_notes, + delivery_address_id: address.id, }, Error::CreateAccountOrder, Error::DatabaseInternal diff --git a/actors/payment_manager/src/lib.rs b/actors/payment_manager/src/lib.rs index b304d41..39adbf1 100644 --- a/actors/payment_manager/src/lib.rs +++ b/actors/payment_manager/src/lib.rs @@ -215,9 +215,22 @@ pub(crate) async fn request_payment( Error::UnavailableShoppingCart ); + let address: model::OrderAddress = query_db!( + db, + database_manager::CreateOrderAddress { + name: address.name, + email: address.email, + street: address.street, + city: address.city, + country: address.country, + zip: address.zip, + }, + Error::InvalidOrderAddress + ); + let db_order: model::Order = query_db!( db, - database_manager::CreateAccountOrder { + database_manager::CreateOrder { buyer_id: msg.buyer_id, items: cart_products .iter() @@ -236,7 +249,7 @@ pub(crate) async fn request_payment( } }) .collect(), - shopping_cart_id: cart.id, + shopping_cart_id: Some(cart.id), checkout_notes: cart.checkout_notes, }, Error::CreateOrder @@ -345,7 +358,7 @@ pub(crate) async fn update_payment( pay_u::PaymentStatus::Canceled => OrderStatus::Cancelled, }; let _ = db - .send(database_manager::UpdateAccountOrderByExt { + .send(database_manager::UpdateOrderByExt { status, order_ext_id, }) diff --git a/api/src/routes/admin/api_v1/orders.rs b/api/src/routes/admin/api_v1/orders.rs index 2f3a3e9..7d40b52 100644 --- a/api/src/routes/admin/api_v1/orders.rs +++ b/api/src/routes/admin/api_v1/orders.rs @@ -17,7 +17,7 @@ async fn orders( ) -> routes::Result> { credentials.require_admin(tm.into_inner()).await?; - let orders: Vec = admin_send_db!(&db, database_manager::AllAccountOrders); + let orders: Vec = admin_send_db!(&db, database_manager::AllOrders); let items: Vec = admin_send_db!(db, database_manager::AllOrderItems); Ok(Json((orders, items).into())) diff --git a/shared/model/src/api.rs b/shared/model/src/api.rs index cd09481..f827a61 100644 --- a/shared/model/src/api.rs +++ b/shared/model/src/api.rs @@ -117,7 +117,6 @@ impl From<(Vec, Vec)> for Orders { id, buyer_id, status, - order_id, order_ext_id: _, service_order_id: _, checkout_notes, @@ -127,7 +126,6 @@ impl From<(Vec, Vec)> for Orders { id, buyer_id, status, - order_id, items: items.drain_filter(|item| item.order_id == id).collect(), checkout_notes, address_id, @@ -146,7 +144,6 @@ impl From<(crate::Order, Vec)> for Order { id, buyer_id, status, - order_id, order_ext_id: _, service_order_id: _, checkout_notes, @@ -159,7 +156,6 @@ impl From<(crate::Order, Vec)> for Order { id, buyer_id, status, - order_id, items: items.drain_filter(|item| item.order_id == id).collect(), checkout_notes, address_id, @@ -173,7 +169,6 @@ pub struct Order { pub id: crate::OrderId, pub buyer_id: crate::AccountId, pub status: crate::OrderStatus, - pub order_id: Option, pub items: Vec, pub checkout_notes: Option, pub address_id: OrderAddressId, diff --git a/shared/model/src/lib.rs b/shared/model/src/lib.rs index b7b7758..0c62dc6 100644 --- a/shared/model/src/lib.rs +++ b/shared/model/src/lib.rs @@ -908,7 +908,6 @@ pub struct Order { pub id: OrderId, pub buyer_id: AccountId, pub status: OrderStatus, - pub order_id: Option, pub order_ext_id: uuid::Uuid, pub service_order_id: Option, pub checkout_notes: Option, @@ -918,22 +917,20 @@ pub struct Order { #[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "db", derive(sqlx::FromRow))] #[derive(Serialize, Deserialize)] -pub struct PublicAccountOrder { +pub struct PublicOrder { pub id: OrderId, pub buyer_id: AccountId, pub status: OrderStatus, - pub order_id: Option, pub checkout_notes: String, pub address_id: OrderAddressId, } -impl From for PublicAccountOrder { +impl From for PublicOrder { fn from( Order { id, buyer_id, status, - order_id, order_ext_id: _, service_order_id: _, checkout_notes, @@ -944,7 +941,6 @@ impl From for PublicAccountOrder { id, buyer_id, status, - order_id, checkout_notes: checkout_notes.unwrap_or_default(), address_id, }