Start testing orders

This commit is contained in:
Adrian Woźniak 2022-06-06 16:07:49 +02:00
parent 7c272d310f
commit e0975cc94b
No known key found for this signature in database
GPG Key ID: 0012845A89C7352B
8 changed files with 357 additions and 67 deletions

View File

@ -128,3 +128,17 @@ RETURNING id, name, email, street, city, country, zip
.await .await
.map_err(|_| Error::CreateOrderAddress.into()) .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::*;
}

View File

@ -109,7 +109,7 @@ WHERE id = $1
#[derive(actix::Message)] #[derive(actix::Message)]
#[rtype(result = "Result<Vec<OrderItem>>")] #[rtype(result = "Result<Vec<OrderItem>>")]
pub struct OrderItems { pub struct OrderItems {
pub order_id: model::OrderId, pub order_id: OrderId,
} }
db_async_handler!(OrderItems, order_items, Vec<OrderItem>); db_async_handler!(OrderItems, order_items, Vec<OrderItem>);

View File

@ -1,5 +1,4 @@
use model::*; use model::*;
use sqlx::PgPool;
use super::Result; use super::Result;
use crate::{ use crate::{
@ -22,11 +21,14 @@ pub enum Error {
#[derive(actix::Message)] #[derive(actix::Message)]
#[rtype(result = "Result<Vec<Order>>")] #[rtype(result = "Result<Vec<Order>>")]
pub struct AllAccountOrders; pub struct AllOrders;
db_async_handler!(AllAccountOrders, all_orders, Vec<Order>); db_async_handler!(AllOrders, all_orders, Vec<Order>, inner_all_orders);
pub(crate) async fn all_orders(_msg: AllAccountOrders, pool: PgPool) -> Result<Vec<Order>> { pub(crate) async fn all_orders(
_msg: AllOrders,
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Vec<Order>> {
sqlx::query_as( sqlx::query_as(
r#" r#"
SELECT id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, address_id 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 ORDER BY id DESC
"#, "#,
) )
.fetch_all(&pool) .fetch_all(t)
.await .await
.map_err(|e| { .map_err(|e| {
log::error!("{e:?}"); log::error!("{e:?}");
@ -54,39 +56,38 @@ pub mod create_order {
#[derive(actix::Message)] #[derive(actix::Message)]
#[rtype(result = "Result<Order>")] #[rtype(result = "Result<Order>")]
pub struct CreateAccountOrder { pub struct CreateOrder {
pub buyer_id: AccountId, pub buyer_id: AccountId,
pub items: Vec<create_order::OrderItem>, pub items: Vec<create_order::OrderItem>,
pub shopping_cart_id: ShoppingCartId, pub shopping_cart_id: Option<ShoppingCartId>,
pub checkout_notes: Option<String>, pub checkout_notes: Option<String>,
pub delivery_address_id: OrderAddressId,
} }
db_async_handler!( db_async_handler!(CreateOrder, create_order, Order, inner_create_order);
CreateAccountOrder,
create_account_order,
Order,
inner_create_account_order
);
pub(crate) async fn create_account_order( pub(crate) async fn create_order(
msg: CreateAccountOrder, msg: CreateOrder,
t: &mut sqlx::Transaction<'_, sqlx::Postgres>, t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Order> { ) -> Result<Order> {
let order: Order = match sqlx::query_as( let order: Order = match sqlx::query_as(
r#" r#"
INSERT INTO orders (buyer_id, status) INSERT INTO orders (buyer_id, status, checkout_notes, address_id)
VALUES ($1, $2, $3) VALUES ($1, $2, $3, $4)
RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, address_id RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, address_id
"#, "#,
) )
.bind(msg.buyer_id) .bind(msg.buyer_id)
.bind(OrderStatus::Confirmed) .bind(OrderStatus::Confirmed)
.bind(msg.checkout_notes.as_deref())
.bind(msg.delivery_address_id)
.fetch_one(&mut *t) .fetch_one(&mut *t)
.await .await
{ {
Ok(order) => order, Ok(order) => order,
Err(e) => { Err(e) => {
log::error!("{e:?}"); log::error!("{e:?}");
dbg!(e);
return Err(super::Error::AccountOrder(Error::CantCreate)); 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 .await
{ {
dbg!(e);
log::error!("{e:?}"); log::error!("{e:?}");
return Err(super::Error::AccountOrder(Error::CantCreate)); return Err(super::Error::AccountOrder(Error::CantCreate));
} }
} }
if let Err(e) = shopping_cart_set_state( if let Some(shopping_cart_id) = msg.shopping_cart_id {
ShoppingCartSetState { if let Err(e) = shopping_cart_set_state(
id: msg.shopping_cart_id, ShoppingCartSetState {
state: ShoppingCartState::Closed, id: shopping_cart_id,
checkout_notes: msg.checkout_notes, state: ShoppingCartState::Closed,
}, checkout_notes: msg.checkout_notes,
t, },
) t,
.await )
{ .await
log::error!("{e:?}"); {
dbg!(e);
log::error!("{e:?}");
return Err(super::Error::AccountOrder(Error::CantCreate)); return Err(super::Error::AccountOrder(Error::CantCreate));
}; };
}
Ok(order) Ok(order)
} }
#[derive(actix::Message)] #[derive(actix::Message)]
#[rtype(result = "Result<Order>")] #[rtype(result = "Result<Order>")]
pub struct UpdateAccountOrder { pub struct UpdateOrder {
pub id: OrderId, pub id: OrderId,
pub buyer_id: AccountId, pub buyer_id: AccountId,
pub status: OrderStatus, pub status: OrderStatus,
pub order_id: Option<ExtOrderId>, pub order_id: Option<ExtOrderId>,
} }
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<Order> { pub(crate) async fn update_order(
msg: UpdateOrder,
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Order> {
sqlx::query_as( sqlx::query_as(
r#" r#"
UPDATE orders 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.buyer_id)
.bind(msg.status) .bind(msg.status)
.bind(msg.order_id) .bind(msg.order_id)
.fetch_one(&db) .fetch_one(t)
.await .await
.map_err(|e| { .map_err(|e| {
log::error!("{e:?}"); log::error!("{e:?}");
@ -160,16 +168,21 @@ RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes,
#[derive(actix::Message)] #[derive(actix::Message)]
#[rtype(result = "Result<Order>")] #[rtype(result = "Result<Order>")]
pub struct UpdateAccountOrderByExt { pub struct UpdateOrderByExt {
pub order_ext_id: String, pub order_ext_id: String,
pub status: OrderStatus, 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( pub(crate) async fn update_order_by_ext(
msg: UpdateAccountOrderByExt, msg: UpdateOrderByExt,
db: PgPool, t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Order> { ) -> Result<Order> {
sqlx::query_as( sqlx::query_as(
r#" 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.order_ext_id)
.bind(msg.status) .bind(msg.status)
.fetch_one(&db) .fetch_one(t)
.await .await
.map_err(|e| { .map_err(|e| {
log::error!("{e:?}"); log::error!("{e:?}");
@ -191,13 +204,21 @@ RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes,
#[derive(actix::Message)] #[derive(actix::Message)]
#[rtype(result = "Result<Order>")] #[rtype(result = "Result<Order>")]
pub struct FindAccountOrder { pub struct FindOrder {
pub id: OrderId, 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<Order> { pub(crate) async fn find_account_order(
msg: FindOrder,
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Order> {
sqlx::query_as( sqlx::query_as(
r#" r#"
SELECT id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, address_id 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) .bind(msg.id)
.fetch_one(&db) .fetch_one(t)
.await .await
.map_err(|e| { .map_err(|e| {
log::error!("{e:?}"); log::error!("{e:?}");
@ -221,9 +242,17 @@ pub struct SetOrderServiceId {
pub service_order_id: String, 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<Order> { pub(crate) async fn set_order_service_id(
msg: SetOrderServiceId,
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Order> {
sqlx::query_as( sqlx::query_as(
r#" r#"
UPDATE orders UPDATE orders
@ -234,10 +263,252 @@ RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes,
) )
.bind(msg.id) .bind(msg.id)
.bind(msg.service_order_id) .bind(msg.service_order_id)
.fetch_one(&db) .fetch_one(t)
.await .await
.map_err(|e| { .map_err(|e| {
log::error!("{e:?}"); log::error!("{e:?}");
super::Error::AccountOrder(Error::NotExists) 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<AccountId>,
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<AccountId>,
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);
}
}

View File

@ -163,7 +163,7 @@ pub(crate) async fn create_account_order(
} }
}; };
query_db!( let address: model::OrderAddress = query_db!(
db, db,
database_manager::CreateOrderAddress { database_manager::CreateOrderAddress {
name: address.name, name: address.name,
@ -178,8 +178,8 @@ pub(crate) async fn create_account_order(
let order = query_db!( let order = query_db!(
db, db,
database_manager::CreateAccountOrder { database_manager::CreateOrder {
shopping_cart_id: cart.id, shopping_cart_id: Some(cart.id),
buyer_id: msg.account_id, buyer_id: msg.account_id,
items: items items: items
.into_iter() .into_iter()
@ -189,7 +189,8 @@ pub(crate) async fn create_account_order(
quantity_unit: item.quantity_unit, quantity_unit: item.quantity_unit,
}) })
.collect(), .collect(),
checkout_notes: cart.checkout_notes checkout_notes: cart.checkout_notes,
delivery_address_id: address.id,
}, },
Error::CreateAccountOrder, Error::CreateAccountOrder,
Error::DatabaseInternal Error::DatabaseInternal

View File

@ -215,9 +215,22 @@ pub(crate) async fn request_payment(
Error::UnavailableShoppingCart 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!( let db_order: model::Order = query_db!(
db, db,
database_manager::CreateAccountOrder { database_manager::CreateOrder {
buyer_id: msg.buyer_id, buyer_id: msg.buyer_id,
items: cart_products items: cart_products
.iter() .iter()
@ -236,7 +249,7 @@ pub(crate) async fn request_payment(
} }
}) })
.collect(), .collect(),
shopping_cart_id: cart.id, shopping_cart_id: Some(cart.id),
checkout_notes: cart.checkout_notes, checkout_notes: cart.checkout_notes,
}, },
Error::CreateOrder Error::CreateOrder
@ -345,7 +358,7 @@ pub(crate) async fn update_payment(
pay_u::PaymentStatus::Canceled => OrderStatus::Cancelled, pay_u::PaymentStatus::Canceled => OrderStatus::Cancelled,
}; };
let _ = db let _ = db
.send(database_manager::UpdateAccountOrderByExt { .send(database_manager::UpdateOrderByExt {
status, status,
order_ext_id, order_ext_id,
}) })

View File

@ -17,7 +17,7 @@ async fn orders(
) -> routes::Result<Json<Orders>> { ) -> routes::Result<Json<Orders>> {
credentials.require_admin(tm.into_inner()).await?; credentials.require_admin(tm.into_inner()).await?;
let orders: Vec<model::Order> = admin_send_db!(&db, database_manager::AllAccountOrders); let orders: Vec<model::Order> = admin_send_db!(&db, database_manager::AllOrders);
let items: Vec<model::OrderItem> = admin_send_db!(db, database_manager::AllOrderItems); let items: Vec<model::OrderItem> = admin_send_db!(db, database_manager::AllOrderItems);
Ok(Json((orders, items).into())) Ok(Json((orders, items).into()))

View File

@ -117,7 +117,6 @@ impl From<(Vec<crate::Order>, Vec<crate::OrderItem>)> for Orders {
id, id,
buyer_id, buyer_id,
status, status,
order_id,
order_ext_id: _, order_ext_id: _,
service_order_id: _, service_order_id: _,
checkout_notes, checkout_notes,
@ -127,7 +126,6 @@ impl From<(Vec<crate::Order>, Vec<crate::OrderItem>)> for Orders {
id, id,
buyer_id, buyer_id,
status, status,
order_id,
items: items.drain_filter(|item| item.order_id == id).collect(), items: items.drain_filter(|item| item.order_id == id).collect(),
checkout_notes, checkout_notes,
address_id, address_id,
@ -146,7 +144,6 @@ impl From<(crate::Order, Vec<crate::OrderItem>)> for Order {
id, id,
buyer_id, buyer_id,
status, status,
order_id,
order_ext_id: _, order_ext_id: _,
service_order_id: _, service_order_id: _,
checkout_notes, checkout_notes,
@ -159,7 +156,6 @@ impl From<(crate::Order, Vec<crate::OrderItem>)> for Order {
id, id,
buyer_id, buyer_id,
status, status,
order_id,
items: items.drain_filter(|item| item.order_id == id).collect(), items: items.drain_filter(|item| item.order_id == id).collect(),
checkout_notes, checkout_notes,
address_id, address_id,
@ -173,7 +169,6 @@ pub struct Order {
pub id: crate::OrderId, pub id: crate::OrderId,
pub buyer_id: crate::AccountId, pub buyer_id: crate::AccountId,
pub status: crate::OrderStatus, pub status: crate::OrderStatus,
pub order_id: Option<crate::ExtOrderId>,
pub items: Vec<crate::OrderItem>, pub items: Vec<crate::OrderItem>,
pub checkout_notes: Option<String>, pub checkout_notes: Option<String>,
pub address_id: OrderAddressId, pub address_id: OrderAddressId,

View File

@ -908,7 +908,6 @@ pub struct Order {
pub id: OrderId, pub id: OrderId,
pub buyer_id: AccountId, pub buyer_id: AccountId,
pub status: OrderStatus, pub status: OrderStatus,
pub order_id: Option<ExtOrderId>,
pub order_ext_id: uuid::Uuid, pub order_ext_id: uuid::Uuid,
pub service_order_id: Option<String>, pub service_order_id: Option<String>,
pub checkout_notes: Option<String>, pub checkout_notes: Option<String>,
@ -918,22 +917,20 @@ pub struct Order {
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::FromRow))] #[cfg_attr(feature = "db", derive(sqlx::FromRow))]
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct PublicAccountOrder { pub struct PublicOrder {
pub id: OrderId, pub id: OrderId,
pub buyer_id: AccountId, pub buyer_id: AccountId,
pub status: OrderStatus, pub status: OrderStatus,
pub order_id: Option<ExtOrderId>,
pub checkout_notes: String, pub checkout_notes: String,
pub address_id: OrderAddressId, pub address_id: OrderAddressId,
} }
impl From<Order> for PublicAccountOrder { impl From<Order> for PublicOrder {
fn from( fn from(
Order { Order {
id, id,
buyer_id, buyer_id,
status, status,
order_id,
order_ext_id: _, order_ext_id: _,
service_order_id: _, service_order_id: _,
checkout_notes, checkout_notes,
@ -944,7 +941,6 @@ impl From<Order> for PublicAccountOrder {
id, id,
buyer_id, buyer_id,
status, status,
order_id,
checkout_notes: checkout_notes.unwrap_or_default(), checkout_notes: checkout_notes.unwrap_or_default(),
address_id, address_id,
} }