From c7e9b25b9760c666bb0c32d40070b045b4c5976e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Wo=C5=BAniak?= Date: Fri, 11 Nov 2022 16:32:07 +0100 Subject: [PATCH] Working tests, test detailed products --- Cargo.lock | 80 +- crates/account_manager/Cargo.toml | 4 +- .../migrations/202204131841_init.sql | 2 +- .../migrations/202204131842_addresses.sql | 2 +- crates/account_manager/src/db/accounts.rs | 19 +- crates/account_manager/src/db/addresses.rs | 13 +- crates/api/Cargo.toml | 2 +- crates/cart_manager/Cargo.toml | 12 +- .../migrations/202204131841_init.sql | 4 +- .../src/db/shopping_cart_items.rs | 110 +- crates/cart_manager/src/db/shopping_carts.rs | 138 +-- crates/cart_manager/src/main.rs | 2 +- crates/channels/Cargo.toml | 2 +- crates/channels/src/stocks/load.rs | 9 +- crates/channels/src/stocks/mod.rs | 15 +- crates/db-utils/Cargo.toml | 2 +- crates/db-utils/src/lib.rs | 40 +- crates/email_manager/Cargo.toml | 6 +- crates/model/src/lib.rs | 106 +- crates/order_manager/Cargo.toml | 6 +- crates/payment_manager/Cargo.toml | 3 +- crates/payment_manager/src/lib.rs | 2 +- crates/search_manager/Cargo.toml | 8 +- crates/stock_manager/Cargo.toml | 7 +- .../migrations/202204131841_init.sql | 10 +- crates/stock_manager/src/actions/load.rs | 329 +++++- crates/stock_manager/src/actions/product.rs | 24 +- .../src/actions/product_photo.rs | 16 +- .../src/actions/product_stock.rs | 16 +- .../src/actions/product_variant.rs | 24 +- ...r__actions__load__tests__load_details.snap | 939 +++++++++++++++++ ...ctions__load__tests__load_details.snap.new | 940 ++++++++++++++++++ crates/stock_manager/src/db/mod.rs | 6 + crates/stock_manager/src/db/photos.rs | 8 +- crates/stock_manager/src/db/product_photos.rs | 7 +- .../stock_manager/src/db/product_variants.rs | 110 +- crates/stock_manager/src/db/products.rs | 49 +- crates/stock_manager/src/db/stocks.rs | 44 +- crates/token_manager/Cargo.toml | 1 + scripts/migrate.sh | 7 + 40 files changed, 2793 insertions(+), 331 deletions(-) create mode 100644 crates/stock_manager/src/actions/snapshots/stock_manager__actions__load__tests__load_details.snap create mode 100644 crates/stock_manager/src/actions/snapshots/stock_manager__actions__load__tests__load_details.snap.new diff --git a/Cargo.lock b/Cargo.lock index 6e541ec..e86e109 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -830,12 +830,11 @@ dependencies = [ name = "cart_manager" version = "0.1.0" dependencies = [ - "actix 0.13.0", - "actix-rt", "channels", "chrono", "config", "dotenv", + "fake", "futures 0.3.25", "model", "opentelemetry 0.17.0", @@ -846,6 +845,7 @@ dependencies = [ "sqlx", "sqlx-core", "tarpc", + "testx", "thiserror", "tokio", "tracing", @@ -967,6 +967,19 @@ dependencies = [ "tracing", ] +[[package]] +name = "console" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "terminal_size", + "winapi 0.3.9", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -1385,6 +1398,7 @@ dependencies = [ "chrono", "config", "dotenv", + "fake", "handlebars", "model", "opentelemetry 0.17.0", @@ -1395,6 +1409,7 @@ dependencies = [ "serde", "serde_json", "tarpc", + "testx", "thiserror", "tokio", "tracing", @@ -1409,6 +1424,12 @@ version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1056f553da426e9c025a662efa48b52e62e0a3a7648aa2d15aeaaf7f0d329357" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.31" @@ -2239,6 +2260,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "insta" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581d4e3314cae4536e5d22ffd23189d4a374696c5ef733eadafae0ed273fd303" +dependencies = [ + "console", + "lazy_static", + "linked-hash-map", + "similar", + "yaml-rust", +] + [[package]] name = "instant" version = "0.1.12" @@ -2446,6 +2480,12 @@ dependencies = [ "cc", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "local-channel" version = "0.1.3" @@ -2962,10 +3002,12 @@ dependencies = [ "chrono", "config", "database_manager", + "fake", "model", "pretty_env_logger", "rumqttc", "serde", + "testx", "thiserror", "tracing", "uuid 1.2.1", @@ -3094,6 +3136,7 @@ dependencies = [ "config", "database_manager", "derive_more", + "fake", "model", "parking_lot 0.12.1", "pay_u", @@ -3678,6 +3721,7 @@ dependencies = [ "config", "derive_more", "dotenv", + "fake", "futures 0.3.25", "model", "opentelemetry 0.17.0", @@ -3688,6 +3732,7 @@ dependencies = [ "serde", "sonic-channel", "tarpc", + "testx", "thiserror", "tokio", "tracing", @@ -3897,6 +3942,12 @@ dependencies = [ "libc", ] +[[package]] +name = "similar" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ac7f900db32bf3fd12e0117dd3dc4da74bc52ebaac97f39668446d89694803" + [[package]] name = "siphasher" version = "0.3.10" @@ -4108,6 +4159,7 @@ dependencies = [ "dotenv", "fake", "futures 0.3.25", + "insta", "model", "opentelemetry 0.17.0", "opentelemetry-jaeger", @@ -4161,9 +4213,9 @@ dependencies = [ [[package]] name = "tarpc" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd84a0fdd485d04b67be6009a04603489c8cb00ade830e4dd2e3660bef855b1" +checksum = "71a98cc1a0a9013e8df3900d09c597dd65cfc6ea4d42968629b1b9ea949acf8f" dependencies = [ "anyhow", "fnv", @@ -4249,6 +4301,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "testx" version = "0.1.0" @@ -4379,6 +4441,7 @@ dependencies = [ "config", "database_manager", "derive_more", + "fake", "futures 0.3.25", "futures-util", "hmac", @@ -5244,6 +5307,15 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "zstd" version = "0.11.2+zstd.1.5.2" diff --git a/crates/account_manager/Cargo.toml b/crates/account_manager/Cargo.toml index 87fe392..6a7c490 100644 --- a/crates/account_manager/Cargo.toml +++ b/crates/account_manager/Cargo.toml @@ -16,7 +16,7 @@ dotenv = { version = "0.15.0" } futures = { version = "0.3.25" } gumdrop = { version = "0.8.1" } json = { version = "0.12.4" } -model = { path = "../model" } +model = { path = "../model", features = ['db'] } opentelemetry = { version = "0.17.0" } opentelemetry-jaeger = { version = "0.17.0" } pretty_env_logger = { version = "0.4", features = [] } @@ -24,7 +24,7 @@ rumqttc = { version = "*" } serde = { version = "1.0.137", features = ["derive"] } sqlx = { version = "0.6.2", features = ["migrate", "runtime-actix-rustls", "all-types", "postgres"] } sqlx-core = { version = "0.6.2", features = [] } -tarpc = { version = "0.30.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] } +tarpc = { version = "0.31.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] } thiserror = { version = "1.0.31" } tokio = { version = "1.21.2", features = ['full'] } tracing = { version = "0.1.6" } diff --git a/crates/account_manager/migrations/202204131841_init.sql b/crates/account_manager/migrations/202204131841_init.sql index 464e86e..1dd5311 100644 --- a/crates/account_manager/migrations/202204131841_init.sql +++ b/crates/account_manager/migrations/202204131841_init.sql @@ -12,7 +12,7 @@ CREATE TYPE "Role" AS ENUM ( ); CREATE TABLE public.accounts ( - id integer NOT NULL, + id serial NOT NULL, email character varying NOT NULL, login character varying NOT NULL, pass_hash character varying NOT NULL, diff --git a/crates/account_manager/migrations/202204131842_addresses.sql b/crates/account_manager/migrations/202204131842_addresses.sql index 4a301f6..c4cabac 100644 --- a/crates/account_manager/migrations/202204131842_addresses.sql +++ b/crates/account_manager/migrations/202204131842_addresses.sql @@ -1,5 +1,5 @@ CREATE TABLE public.account_addresses ( - id integer NOT NULL, + id serial NOT NULL, name text NOT NULL, email text NOT NULL, street text NOT NULL, diff --git a/crates/account_manager/src/db/accounts.rs b/crates/account_manager/src/db/accounts.rs index ae3bece..6b098b6 100644 --- a/crates/account_manager/src/db/accounts.rs +++ b/crates/account_manager/src/db/accounts.rs @@ -17,19 +17,25 @@ pub enum Error { } #[derive(Debug)] -pub struct AllAccounts; +pub struct AllAccounts { + pub limit: i32, + pub offset: i32, +} impl AllAccounts { pub async fn run( - _msg: AllAccounts, + self, pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result> { sqlx::query_as( r#" SELECT id, email, login, pass_hash, role, customer_id, state FROM accounts +LIMIT $1 OFFSET $2 "#, ) + .bind(self.limit) + .bind(self.offset) .fetch_all(pool) .await .map_err(|e| { @@ -207,6 +213,7 @@ mod tests { use model::*; use super::*; + use crate::db::Database; pub struct NoOpts; @@ -274,7 +281,13 @@ mod tests { test_create_account(&mut t, None, None, None).await; test_create_account(&mut t, None, None, None).await; - let v: Vec = AllAccounts.run(&mut t).await.unwrap(); + let v: Vec = AllAccounts { + limit: 200, + offset: 0, + } + .run(&mut t) + .await + .unwrap(); testx::db_rollback!(t); assert!(v.len() >= 3); diff --git a/crates/account_manager/src/db/addresses.rs b/crates/account_manager/src/db/addresses.rs index 8a7ca37..7d8aab8 100644 --- a/crates/account_manager/src/db/addresses.rs +++ b/crates/account_manager/src/db/addresses.rs @@ -194,6 +194,7 @@ mod test { use super::super::accounts::CreateAccount; use super::*; + use crate::db::Database; pub struct NoOpts; @@ -267,13 +268,11 @@ mod test { address }; - let found = super::find_account_address( - FindAccountAddress { - account_id: account.id, - address_id: address.id, - }, - &mut t, - ) + let found = FindAccountAddress { + account_id: account.id, + address_id: address.id, + } + .run(&mut t) .await .unwrap(); diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml index dbac3e8..e4af703 100644 --- a/crates/api/Cargo.toml +++ b/crates/api/Cargo.toml @@ -43,7 +43,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = [] } sqlx = { version = "0.6.2", features = ["migrate", "runtime-actix-rustls", "all-types", "postgres"] } sqlx-core = { version = "0.6.2", features = [] } -tarpc = { version = "0.30.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] } +tarpc = { version = "0.31.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] } tera = { version = "1.15", features = [] } thiserror = { version = "1.0", features = [] } token_manager = { path = "../token_manager" } diff --git a/crates/cart_manager/Cargo.toml b/crates/cart_manager/Cargo.toml index 109a8ee..6a257ae 100644 --- a/crates/cart_manager/Cargo.toml +++ b/crates/cart_manager/Cargo.toml @@ -8,14 +8,12 @@ name = "cart-manager" path = "src/main.rs" [dependencies] -actix = { version = "0.13", features = [] } -actix-rt = { version = "2.7", features = [] } channels = { path = "../channels" } chrono = { version = "0.4", features = ["serde"] } config = { path = "../config" } dotenv = { version = "0.15.0" } futures = { version = "0.3.25" } -model = { path = "../model" } +model = { path = "../model", features = ["db"] } opentelemetry = { version = "0.17.0" } opentelemetry-jaeger = { version = "0.17.0" } pretty_env_logger = { version = "0.4", features = [] } @@ -23,10 +21,14 @@ rumqttc = { version = "*" } serde = { version = "1.0.137", features = ["derive"] } sqlx = { version = "0.6.2", features = ["migrate", "runtime-actix-rustls", "all-types", "postgres"] } sqlx-core = { version = "0.6.2", features = [] } -tarpc = { version = "0.30.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] } +tarpc = { version = "0.31.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] } thiserror = { version = "1.0.31" } tokio = { version = "1.21.2", features = ['full'] } tracing = { version = "0.1.37" } tracing-opentelemetry = { version = "0.17.4" } tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } -uuid = { version = "0.8", features = ["serde"] } +uuid = { version = "0.8", features = ["serde", "v4"] } + +[dev-dependencies] +fake = { version = "2.5.0" } +testx = { path = "../testx" } diff --git a/crates/cart_manager/migrations/202204131841_init.sql b/crates/cart_manager/migrations/202204131841_init.sql index 78eaa90..75261bf 100644 --- a/crates/cart_manager/migrations/202204131841_init.sql +++ b/crates/cart_manager/migrations/202204131841_init.sql @@ -16,7 +16,7 @@ CREATE TYPE "QuantityUnit" AS ENUM ( ); CREATE TABLE shopping_carts ( - id integer NOT NULL, + id serial NOT NULL, buyer_id integer NOT NULL, payment_method "PaymentMethod" DEFAULT 'payment_on_the_spot'::"PaymentMethod" NOT NULL, state "ShoppingCartState" DEFAULT 'active'::"ShoppingCartState" NOT NULL, @@ -24,7 +24,7 @@ CREATE TABLE shopping_carts ( ); CREATE TABLE shopping_cart_items ( - id integer NOT NULL, + id serial NOT NULL, product_id integer NOT NULL, shopping_cart_id integer, quantity integer DEFAULT 0 NOT NULL, diff --git a/crates/cart_manager/src/db/shopping_cart_items.rs b/crates/cart_manager/src/db/shopping_cart_items.rs index 89e1dab..7b991c3 100644 --- a/crates/cart_manager/src/db/shopping_cart_items.rs +++ b/crates/cart_manager/src/db/shopping_cart_items.rs @@ -366,9 +366,9 @@ RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit #[cfg(test)] mod tests { use config::UpdateConfig; - use fake::Fake; - use model::*; - use uuid::Uuid; + + use crate::db::shopping_carts::*; + use crate::db::Database; pub struct NoOpts; @@ -376,42 +376,6 @@ mod tests { use super::*; - async fn test_product(t: &mut sqlx::Transaction<'_, sqlx::Postgres>) -> 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]), - } - .run(t) - .await - .unwrap() - } - - async fn test_account( - t: &mut sqlx::Transaction<'_, sqlx::Postgres>, - login: Option, - email: Option, - hash: Option, - ) -> FullAccount { - use fake::faker::internet::en; - let login: String = login.unwrap_or_else(|| en::Username().fake()); - let email: String = email.unwrap_or_else(|| en::FreeEmail().fake()); - let hash: String = hash.unwrap_or_else(|| en::Password(10..20).fake()); - - CreateAccount { - email: Email::new(email), - login: Login::new(login), - pass_hash: PassHash::new(hash), - role: Role::Admin, - } - .run(t) - .await - .unwrap() - } - async fn test_shopping_cart( t: &mut sqlx::Transaction<'_, sqlx::Postgres>, buyer_id: Option, @@ -419,7 +383,7 @@ mod tests { ) -> ShoppingCart { let buyer_id = match buyer_id { Some(id) => id, - _ => test_account(&mut *t, None, None, None).await.id, + _ => 1.into(), }; sqlx::query( @@ -469,7 +433,7 @@ WHERE buyer_id = $1 }; let product_id = match product_id { Some(id) => id, - _ => test_product(&mut *t).await.id, + _ => 1.into(), }; CreateShoppingCartItem { product_id, @@ -482,7 +446,7 @@ WHERE buyer_id = $1 .unwrap() } - #[actix::test] + #[tokio::test] async fn create() { testx::db_t_ref!(t); @@ -491,11 +455,11 @@ WHERE buyer_id = $1 testx::db_rollback!(t); } - #[actix::test] + #[tokio::test] async fn all() { testx::db_t_ref!(t); - let account_id = test_account(&mut t, None, None, None).await.id; + let account_id = 1.into(); let mut items = Vec::with_capacity(9); @@ -514,19 +478,17 @@ WHERE buyer_id = $1 items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None).await); items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None).await); - let all = all_shopping_cart_items(AllShoppingCartItems, &mut t) - .await - .unwrap(); + let all = AllShoppingCartItems.run(&mut t).await.unwrap(); testx::db_rollback!(t); assert_eq!(all, items) } - #[actix::test] + #[tokio::test] async fn account_cart_with_cart_id() { testx::db_t_ref!(t); - let account_id = test_account(&mut t, None, None, None).await.id; + let account_id = 1.into(); let mut items = Vec::with_capacity(9); @@ -545,13 +507,11 @@ WHERE buyer_id = $1 test_shopping_cart_item(&mut t, Some(cart3.id), None).await; test_shopping_cart_item(&mut t, Some(cart3.id), None).await; - let all = account_shopping_cart_items( - AccountShoppingCartItems { - account_id, - shopping_cart_id: Some(cart2.id), - }, - &mut t, - ) + let all = AccountShoppingCartItems { + account_id, + shopping_cart_id: Some(cart2.id), + } + .run(&mut t) .await .unwrap(); @@ -559,11 +519,11 @@ WHERE buyer_id = $1 assert_eq!(all, items) } - #[actix::test] + #[tokio::test] async fn account_cart_without_cart_id() { testx::db_t_ref!(t); - let account_id = test_account(&mut t, None, None, None).await.id; + let account_id = 1.into(); let mut items = Vec::with_capacity(9); @@ -582,13 +542,11 @@ WHERE buyer_id = $1 items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None).await); items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None).await); - let all = account_shopping_cart_items( - AccountShoppingCartItems { - account_id, - shopping_cart_id: None, - }, - &mut t, - ) + let all = AccountShoppingCartItems { + account_id, + shopping_cart_id: None, + } + .run(&mut t) .await .unwrap(); @@ -596,23 +554,21 @@ WHERE buyer_id = $1 assert_eq!(all, items) } - #[actix::test] + #[tokio::test] async fn update() { testx::db_t_ref!(t); - let account_id = test_account(&mut t, None, None, None).await.id; + let account_id = 1.into(); let cart1 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Closed).await; let item = test_shopping_cart_item(&mut t, Some(cart1.id), None).await; - let updated = update_shopping_cart_item( - UpdateShoppingCartItem { - id: item.id, - product_id: item.product_id, - shopping_cart_id: item.shopping_cart_id, - quantity: Quantity::from_u32(987979879), - quantity_unit: QuantityUnit::Kilogram, - }, - &mut t, - ) + let updated = UpdateShoppingCartItem { + id: item.id, + product_id: item.product_id, + shopping_cart_id: item.shopping_cart_id, + quantity: Quantity::from_u32(987979879), + quantity_unit: QuantityUnit::Kilogram, + } + .run(&mut t) .await .unwrap(); diff --git a/crates/cart_manager/src/db/shopping_carts.rs b/crates/cart_manager/src/db/shopping_carts.rs index 6fc15c9..cecec13 100644 --- a/crates/cart_manager/src/db/shopping_carts.rs +++ b/crates/cart_manager/src/db/shopping_carts.rs @@ -253,8 +253,8 @@ WHERE buyer_id = $1 AND state = 'active' #[cfg(test)] mod tests { use config::UpdateConfig; - use fake::Fake; - use model::*; + + use crate::db::Database; pub struct NoOpts; @@ -262,85 +262,53 @@ mod tests { use super::*; - async fn test_account( - t: &mut sqlx::Transaction<'_, sqlx::Postgres>, - login: Option, - email: Option, - hash: Option, - ) -> FullAccount { - use fake::faker::internet::en; - let login: String = login.unwrap_or_else(|| en::Username().fake()); - let email: String = email.unwrap_or_else(|| en::FreeEmail().fake()); - let hash: String = hash.unwrap_or_else(|| en::Password(10..20).fake()); - - CreateAccount { - email: Email::new(email), - login: Login::new(login), - pass_hash: PassHash::new(hash), - role: Role::Admin, - } - .run(t) - .await - .unwrap() - } - async fn test_shopping_cart( t: &mut sqlx::Transaction<'_, sqlx::Postgres>, buyer_id: Option, ) -> ShoppingCart { let buyer_id = match buyer_id { Some(id) => id, - _ => test_account(&mut *t, None, None, None).await.id, + _ => 1.into(), }; - super::create_shopping_cart( - CreateShoppingCart { - buyer_id, - payment_method: PaymentMethod::PaymentOnTheSpot, - }, - t, - ) + CreateShoppingCart { + buyer_id, + payment_method: PaymentMethod::PaymentOnTheSpot, + } + .run(t) .await .unwrap() } - #[actix::test] + #[tokio::test] async fn create_shopping_cart() { testx::db_t_ref!(t); - let account = test_account(&mut t, None, None, None).await; - - let cart = super::create_shopping_cart( - CreateShoppingCart { - buyer_id: account.id, - payment_method: PaymentMethod::PaymentOnTheSpot, - }, - &mut t, - ) + let cart = CreateShoppingCart { + buyer_id: 1.into(), + payment_method: PaymentMethod::PaymentOnTheSpot, + } + .run(&mut t) .await; testx::db_rollback!(t); assert!(cart.is_ok()); } - #[actix::test] + #[tokio::test] async fn update_shopping_cart() { testx::db_t_ref!(t); - let account = test_account(&mut t, None, None, None).await; + let original = test_shopping_cart(&mut t, Some(1.into())).await; - let original = test_shopping_cart(&mut t, Some(account.id)).await; - - let cart = super::update_shopping_cart( - UpdateShoppingCart { - id: original.id, - buyer_id: account.id, - payment_method: PaymentMethod::PayU, - state: ShoppingCartState::Closed, - checkout_notes: Some("Foo bar".into()), - }, - &mut t, - ) + let cart = UpdateShoppingCart { + id: original.id, + buyer_id: 1.into(), + payment_method: PaymentMethod::PayU, + state: ShoppingCartState::Closed, + checkout_notes: Some("Foo bar".into()), + } + .run(&mut t) .await .unwrap(); @@ -350,7 +318,7 @@ mod tests { cart, ShoppingCart { id: original.id, - buyer_id: account.id, + buyer_id: 1.into(), payment_method: PaymentMethod::PayU, state: ShoppingCartState::Closed, checkout_notes: Some("Foo bar".into()) @@ -358,29 +326,23 @@ mod tests { ); } - #[actix::test] + #[tokio::test] async fn without_cart_ensure_shopping_cart() { testx::db_t_ref!(t); - let account = test_account(&mut t, None, None, None).await; - - let cart = super::ensure_active_shopping_cart( - EnsureActiveShoppingCart { - buyer_id: account.id, - }, - &mut t, - ) - .await - .unwrap(); + let cart = EnsureActiveShoppingCart { buyer_id: 1.into() } + .run(&mut t) + .await + .unwrap(); let id = cart.id; testx::db_rollback!(t); assert_eq!( cart, - model::ShoppingCart { + ShoppingCart { id, - buyer_id: account.id, + buyer_id: 1.into(), payment_method: Default::default(), state: ShoppingCartState::Active, checkout_notes: None @@ -388,34 +350,26 @@ mod tests { ); } - #[actix::test] + #[tokio::test] async fn with_inactive_cart_ensure_shopping_cart() { testx::db_t_ref!(t); - let account = test_account(&mut t, None, None, None).await; - - let original = test_shopping_cart(&mut t, Some(account.id)).await; - let _ = super::update_shopping_cart( - UpdateShoppingCart { - id: original.id, - buyer_id: account.id, - payment_method: Default::default(), - state: ShoppingCartState::Closed, - checkout_notes: None, - }, - &mut t, - ) + let original = test_shopping_cart(&mut t, Some(1.into())).await; + let _ = UpdateShoppingCart { + id: original.id, + buyer_id: 1.into(), + payment_method: Default::default(), + state: ShoppingCartState::Closed, + checkout_notes: None, + } + .run(&mut t) .await .unwrap(); - let cart = super::ensure_active_shopping_cart( - EnsureActiveShoppingCart { - buyer_id: account.id, - }, - &mut t, - ) - .await - .unwrap(); + let cart = EnsureActiveShoppingCart { buyer_id: 1.into() } + .run(&mut t) + .await + .unwrap(); testx::db_rollback!(t); assert_ne!(original, cart); diff --git a/crates/cart_manager/src/main.rs b/crates/cart_manager/src/main.rs index 72cbdc8..86c85e7 100644 --- a/crates/cart_manager/src/main.rs +++ b/crates/cart_manager/src/main.rs @@ -14,7 +14,7 @@ pub struct Opts {} impl UpdateConfig for Opts {} -#[actix::main] +#[tokio::main] async fn main() { dotenv::dotenv().ok(); init_tracing("account-manager"); diff --git a/crates/channels/Cargo.toml b/crates/channels/Cargo.toml index ba12861..bdaad95 100644 --- a/crates/channels/Cargo.toml +++ b/crates/channels/Cargo.toml @@ -19,7 +19,7 @@ futures = { version = "0.3.25" } model = { path = "../model" } rumqttc = { version = "0.17.0" } serde = { version = "*", features = ['derive'] } -tarpc = { version = "0.30.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] } +tarpc = { version = "0.31.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] } thiserror = { version = "1.0.37" } tokio = { version = "1.21.2", features = ['full'] } tracing = { version = "0.1.37" } diff --git a/crates/channels/src/stocks/load.rs b/crates/channels/src/stocks/load.rs index f163f13..270141c 100644 --- a/crates/channels/src/stocks/load.rs +++ b/crates/channels/src/stocks/load.rs @@ -1,7 +1,7 @@ pub mod detailed_product { use model::v2::*; - use crate::stocks::Error; + pub use crate::stocks::Error; #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct Input { @@ -21,10 +21,13 @@ pub mod detailed_product { pub mod detailed_products { use model::v2::*; - use crate::stocks::Error; + pub use crate::stocks::Error; #[derive(Debug, serde::Serialize, serde::Deserialize)] - pub struct Input {} + pub struct Input { + pub limit: Limit, + pub offset: Offset, + } #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct Details { diff --git a/crates/channels/src/stocks/mod.rs b/crates/channels/src/stocks/mod.rs index 7210c60..d8b6ef5 100644 --- a/crates/channels/src/stocks/mod.rs +++ b/crates/channels/src/stocks/mod.rs @@ -5,6 +5,8 @@ pub mod product_stock; pub mod product_variant; pub use load::*; +use model::v2::ProductVariantId; +use model::ProductId; pub use product::*; pub use product_photo::*; pub use product_stock::*; @@ -13,7 +15,18 @@ pub use product_variant::*; pub static CLIENT_NAME: &str = "stocks"; #[derive(Debug, thiserror::Error, serde::Serialize, serde::Deserialize)] -pub enum Error {} +pub enum Error { + #[error("Something went wrong")] + InternalServerError, + #[error("Failed to load products")] + Products, + #[error("Failed to load products for products {0:?}")] + ProductVariants(Vec), + #[error("Failed to load photos for products variants {0:?}")] + VariantStocks(Vec), + #[error("Failed to load stocks for products variants {0:?}")] + VariantPhotos(Vec), +} pub mod rpc { use config::SharedAppConfig; diff --git a/crates/db-utils/Cargo.toml b/crates/db-utils/Cargo.toml index f57b9c4..c8d654e 100644 --- a/crates/db-utils/Cargo.toml +++ b/crates/db-utils/Cargo.toml @@ -4,6 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] -model = { path = "../model" } +model = { path = "../model", features = ["db"] } sqlx = { version = "0.6.2", features = ["migrate", "runtime-actix-rustls", "all-types", "postgres"] } sqlx-core = { version = "0.6.2", features = [] } diff --git a/crates/db-utils/src/lib.rs b/crates/db-utils/src/lib.rs index 05e4841..cd2223a 100644 --- a/crates/db-utils/src/lib.rs +++ b/crates/db-utils/src/lib.rs @@ -2,10 +2,16 @@ use sqlx::Arguments; pub type PgT<'l> = sqlx::Transaction<'l, sqlx::Postgres>; +pub struct Padding { + pub limit: i32, + pub offset: i32, +} + pub struct MultiLoad<'transaction, 'transaction2, 'header, 'condition, T> { pool: &'transaction mut sqlx::Transaction<'transaction2, sqlx::Postgres>, header: &'header str, condition: &'condition str, + padding: Option, sort: Option, size: Option, allow_over_max: bool, @@ -26,6 +32,7 @@ where pool, header, condition, + padding: None, sort: None, size: Some(200), allow_over_max: false, @@ -48,6 +55,11 @@ where self } + pub fn with_padding(mut self, limit: i32, offset: i32) -> Self { + self.padding = Some(Padding { limit, offset }); + self + } + pub async fn load<'query, Error, ErrorFn, Ids>( &mut self, len: usize, @@ -77,11 +89,13 @@ where }, ) { let query: String = self.header.into(); + let mut arg_cursor = 0; let mut query = ids.iter().enumerate().fold(query, |mut q, (idx, _id)| { if idx != 0 { q.push_str(" OR"); } q.push_str(&format!(" {} ${}", self.condition, idx + 1)); + arg_cursor = idx; q }); if let Some(s) = self.sort.as_deref() { @@ -89,14 +103,32 @@ where query.push_str(s); query.push(' '); } - let q = sqlx::query_as_with( - query.as_str(), + if self.padding.is_some() { + arg_cursor += 1; + query.push_str(&format!( + "LIMIT ${} OFFSET ${}", + arg_cursor + 1, + arg_cursor + 2 + )); + // arg_cursor += 2; + } + + eprintln!("{}", query); + + let mut args = ids.into_iter() .fold(sqlx::postgres::PgArguments::default(), |mut args, id| { + eprintln!("id = {:?}", id); args.add(id); args - }), - ); + }); + + if let Some(Padding { limit, offset }) = self.padding { + args.add(limit); + args.add(offset); + } + + let q = sqlx::query_as_with(query.as_str(), args); let records: Vec = match q.fetch_all(&mut *self.pool).await { Ok(rec) => rec, diff --git a/crates/email_manager/Cargo.toml b/crates/email_manager/Cargo.toml index 0f1442e..999fd0b 100644 --- a/crates/email_manager/Cargo.toml +++ b/crates/email_manager/Cargo.toml @@ -23,10 +23,14 @@ rumqttc = { version = "*" } sendgrid = { version = "0.18.1", features = ["async"] } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = [] } -tarpc = { version = "0.30.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] } +tarpc = { version = "0.31.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] } thiserror = { version = "1.0.31" } tokio = { version = "1.21.2", features = ['full'] } tracing = { version = "0.1.37" } tracing-opentelemetry = { version = "0.17.4" } tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } uuid = { version = "0.8", features = ["serde"] } + +[dev-dependencies] +fake = { version = "2.5.0" } +testx = { path = "../testx" } diff --git a/crates/model/src/lib.rs b/crates/model/src/lib.rs index ee2317c..f3e14a4 100644 --- a/crates/model/src/lib.rs +++ b/crates/model/src/lib.rs @@ -8,7 +8,7 @@ pub mod encrypt; use std::fmt::{Display, Formatter}; use std::ops; -use std::ops::BitOr; +use std::ops::{BitOr, Range}; use std::str::FromStr; use derive_more::{Deref, DerefMut, Display, From}; @@ -311,6 +311,89 @@ impl ops::Mul for Price { } } +pub trait Ranged: Sized + From + Copy { + fn in_range(self, range: Range) -> Self { + let v = self.into_raw().try_into().unwrap_or(range.start); + v.max(range.start).min(range.end).into() + } + + fn into_raw(self) -> i32; +} + +#[cfg_attr(feature = "db", derive(sqlx::Type))] +#[cfg_attr(feature = "db", sqlx(transparent))] +#[derive( + Default, + Debug, + Copy, + Clone, + Hash, + PartialOrd, + PartialEq, + Eq, + Serialize, + Deserialize, + Deref, + From, +)] +#[serde(transparent)] +pub struct Limit(NonNegative); + +impl From for Limit { + fn from(value: u32) -> Self { + Self::from_u32(value) + } +} + +impl Ranged for Limit { + fn into_raw(self) -> i32 { + self.0 .0 + } +} + +impl Limit { + pub fn from_u32(price: u32) -> Self { + Self(NonNegative(price as i32)) + } +} + +#[cfg_attr(feature = "db", derive(sqlx::Type))] +#[cfg_attr(feature = "db", sqlx(transparent))] +#[derive( + Default, + Debug, + Copy, + Clone, + Hash, + PartialOrd, + PartialEq, + Eq, + Serialize, + Deserialize, + Deref, + From, +)] +#[serde(transparent)] +pub struct Offset(NonNegative); + +impl From for Offset { + fn from(value: u32) -> Self { + Self::from_u32(value) + } +} + +impl Ranged for Offset { + fn into_raw(self) -> i32 { + self.0 .0 + } +} + +impl Offset { + pub fn from_u32(price: u32) -> Self { + Self(NonNegative(price as i32)) + } +} + #[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", sqlx(transparent))] @@ -905,7 +988,7 @@ pub mod v2 { use serde::{Deserialize, Serialize}; pub use crate::{ - Day, Days, FileName, LocalPath, PhotoId, Price, ProductCategory, ProductId, + Day, Days, FileName, Limit, LocalPath, Offset, PhotoId, Price, ProductCategory, ProductId, ProductLongDesc, ProductName, ProductPhotoId, ProductShortDesc, Quantity, QuantityUnit, RecordId, StockId, UniqueName, }; @@ -937,19 +1020,26 @@ pub mod v2 { #[serde(transparent)] pub struct ProductVariantId(RecordId); - #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] - pub struct DetailedProduct { - pub id: ProductId, + #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] + pub struct DetailedProductVariant { + pub id: ProductVariantId, pub name: ProductName, pub short_description: ProductShortDesc, pub long_description: ProductLongDesc, - pub category: Option, pub price: Price, - pub deliver_days_flag: Days, pub stocks: Vec, - pub photos: Vec, + pub photos: Vec, + } + + #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] + pub struct DetailedProduct { + pub id: ProductId, + pub name: ProductName, + pub category: Option, + pub deliver_days_flag: Days, + pub variants: Vec, } #[cfg_attr(feature = "dummy", derive(fake::Dummy))] diff --git a/crates/order_manager/Cargo.toml b/crates/order_manager/Cargo.toml index 9071ce4..3b3ce8d 100644 --- a/crates/order_manager/Cargo.toml +++ b/crates/order_manager/Cargo.toml @@ -9,10 +9,14 @@ actix-rt = { version = "2.7", features = [] } chrono = { version = "0.4", features = ["serde"] } config = { path = "../config" } database_manager = { path = "../database_manager" } -model = { path = "../model" } +model = { path = "../model", features = ["db"] } pretty_env_logger = { version = "0.4", features = [] } rumqttc = { version = "*" } serde = { version = "1.0.137", features = ["derive"] } thiserror = { version = "1.0.31" } tracing = { version = "0.1.34" } uuid = { version = "1.2.1", features = ["serde"] } + +[dev-dependencies] +fake = { version = "2.5.0" } +testx = { path = "../testx" } diff --git a/crates/payment_manager/Cargo.toml b/crates/payment_manager/Cargo.toml index 5c6ae94..5429ca0 100644 --- a/crates/payment_manager/Cargo.toml +++ b/crates/payment_manager/Cargo.toml @@ -10,7 +10,7 @@ chrono = { version = "0.4", features = ["serde"] } config = { path = "../config" } database_manager = { path = "../database_manager" } derive_more = { version = "0.99", features = [] } -model = { path = "../model" } +model = { path = "../model", features = ["db"] } parking_lot = { version = "0.12", features = [] } pay_u = { version = '0.1', features = ["single-client"] } pretty_env_logger = { version = "0.4", features = [] } @@ -21,4 +21,5 @@ tracing = { version = "0.1.34" } uuid = { version = "0.8", features = ["serde"] } [dev-dependencies] +fake = { version = "2.5.0" } testx = { path = "../testx" } diff --git a/crates/payment_manager/src/lib.rs b/crates/payment_manager/src/lib.rs index 2e18de9..1ebd438 100644 --- a/crates/payment_manager/src/lib.rs +++ b/crates/payment_manager/src/lib.rs @@ -332,6 +332,6 @@ mod tests { testx::db!(config, db); let db = db.start(); - let manager = PaymentManager::build(config, db).await; + let _manager = PaymentManager::build(config, db).await; } } diff --git a/crates/search_manager/Cargo.toml b/crates/search_manager/Cargo.toml index 298bc05..09e86fe 100644 --- a/crates/search_manager/Cargo.toml +++ b/crates/search_manager/Cargo.toml @@ -16,7 +16,7 @@ config = { path = "../config" } derive_more = { version = "0.99", features = [] } dotenv = { version = "0.15.0" } futures = { version = "0.3.25" } -model = { path = "../model" } +model = { path = "../model", features = ["db"] } opentelemetry = { version = "0.17.0" } opentelemetry-jaeger = { version = "0.17.0" } parking_lot = { version = "0.12", features = [] } @@ -24,7 +24,7 @@ pretty_env_logger = { version = "0.4", features = [] } rumqttc = { version = "*" } serde = { version = "1.0", features = ["derive"] } sonic-channel = { version = "1.1.0", features = ["ingest"] } -tarpc = { version = "0.30.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] } +tarpc = { version = "0.31.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] } thiserror = { version = "1.0.31" } tokio = { version = "1.21.2", features = ['full'] } tracing = { version = "0.1.6" } @@ -32,3 +32,7 @@ tracing-opentelemetry = { version = "0.17.4" } tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } uuid = { version = "1.2.1", features = ["serde"] } whatlang = { version = "0.16.2" } + +[dev-dependencies] +fake = { version = "2.5.0" } +testx = { path = "../testx" } diff --git a/crates/stock_manager/Cargo.toml b/crates/stock_manager/Cargo.toml index bd34108..9b7299e 100644 --- a/crates/stock_manager/Cargo.toml +++ b/crates/stock_manager/Cargo.toml @@ -15,7 +15,7 @@ db-utils = { path = "../db-utils" } derive_more = { version = "0.99", features = [] } dotenv = { version = "0.15.0" } futures = { version = "0.3.25" } -model = { path = "../model" } +model = { path = "../model", features = ["db"] } opentelemetry = { version = "0.17.0" } opentelemetry-jaeger = { version = "0.17.0" } pretty_env_logger = { version = "0.4", features = [] } @@ -23,14 +23,15 @@ rumqttc = { version = "*" } serde = { version = "1.0", features = ["derive"] } sqlx = { version = "0.6.2", features = ["migrate", "runtime-actix-rustls", "all-types", "postgres"] } sqlx-core = { version = "0.6.2", features = [] } -tarpc = { version = "0.30.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] } +tarpc = { version = "0.31.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] } thiserror = { version = "1.0.31" } tokio = { version = "1.21.2", features = ['full'] } tracing = { version = "0.1.6" } tracing-opentelemetry = { version = "0.17.4" } tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } -uuid = { version = "1.2.1" } +uuid = { version = "1.2.1", features = ['v4'] } [dev-dependencies] fake = { version = "2.5.0" } testx = { path = "../testx" } +insta = { version = "1.21.0" } diff --git a/crates/stock_manager/migrations/202204131841_init.sql b/crates/stock_manager/migrations/202204131841_init.sql index f352411..f0c0858 100644 --- a/crates/stock_manager/migrations/202204131841_init.sql +++ b/crates/stock_manager/migrations/202204131841_init.sql @@ -8,21 +8,21 @@ CREATE TYPE "QuantityUnit" AS ENUM ( ); CREATE TABLE photos ( - id integer NOT NULL PRIMARY KEY, + id serial NOT NULL PRIMARY KEY, local_path character varying NOT NULL, file_name character varying NOT NULL, unique_name text DEFAULT (gen_random_uuid())::text NOT NULL ); CREATE TABLE products ( - id integer NOT NULL PRIMARY KEY, + id serial NOT NULL PRIMARY KEY, "name" character varying NOT NULL, category character varying, deliver_days_flag integer DEFAULT 127 NOT NULL ); CREATE TABLE product_variants ( - id integer NOT NULL PRIMARY KEY, + id serial NOT NULL PRIMARY KEY, product_id integer REFERENCES products (id) NOT NULL, "name" character varying NOT NULL, short_description character varying NOT NULL, @@ -32,7 +32,7 @@ CREATE TABLE product_variants ( ); CREATE TABLE stocks ( - id integer NOT NULL PRIMARY KEY, + id serial NOT NULL PRIMARY KEY, product_variant_id integer REFERENCES product_variants(id) NOT NULL, quantity integer DEFAULT 0 NOT NULL, quantity_unit "QuantityUnit" NOT NULL, @@ -40,7 +40,7 @@ CREATE TABLE stocks ( ); CREATE TABLE product_photos ( - id integer NOT NULL PRIMARY KEY, + id serial NOT NULL PRIMARY KEY, product_variant_id integer REFERENCES product_variants(id) NOT NULL, photo_id integer REFERENCES photos(id) NOT NULL ); diff --git a/crates/stock_manager/src/actions/load.rs b/crates/stock_manager/src/actions/load.rs index a2494ad..bab701c 100644 --- a/crates/stock_manager/src/actions/load.rs +++ b/crates/stock_manager/src/actions/load.rs @@ -1,14 +1,19 @@ +use std::collections::HashMap; + use channels::stocks::{detailed_product, detailed_products}; use channels::AsyncClient; use config::SharedAppConfig; +use db_utils::PgT; +use model::v2::{DetailedProduct, DetailedProductVariant, Product, ProductVariant}; +use model::Limit; -use crate::db::Database; +use crate::db::{Database, PhotosForProductVariants, ProductVariantsStock, ProductsVariants}; pub async fn detailed_product( - input: detailed_product::Input, - db: Database, - mqtt: AsyncClient, - config: SharedAppConfig, + _input: detailed_product::Input, + _db: Database, + _mqtt: AsyncClient, + _config: SharedAppConfig, ) -> detailed_product::Output { todo!() } @@ -16,8 +21,316 @@ pub async fn detailed_product( pub async fn detailed_products( input: detailed_products::Input, db: Database, - mqtt: AsyncClient, - config: SharedAppConfig, + _mqtt: AsyncClient, + _config: SharedAppConfig, ) -> detailed_products::Output { - todo!() + let mut t = match db.pool().begin().await { + Err(e) => { + tracing::error!("{}", e); + return Err(detailed_products::Error::InternalServerError); + } + Ok(t) => t, + }; + + let res = inner_detailed_products(input, &mut t, Some(_mqtt), Some(_config)).await; + + t.commit().await.ok(); + + res +} + +async fn inner_detailed_products( + input: detailed_products::Input, + t: &mut PgT<'_>, + _mqtt: Option, + _config: Option, +) -> detailed_products::Output { + let dbm = crate::db::AllProducts { + limit: input.limit, + offset: input.offset, + }; + let products = match dbm.run(&mut *t).await { + Ok(products) => products, + Err(e) => { + tracing::error!("{}", e); + return Err(detailed_products::Error::Products); + } + }; + let dbm = ProductsVariants { + product_ids: products.iter().map(|p| p.id).collect(), + limit: Some(Limit::from_u32(products.len() as u32 * 10)), + offset: Some(0.into()), + }; + let variants = match dbm.run(&mut *t).await { + Ok(variants) => variants, + Err(e) => { + tracing::error!("{}", e); + return Err(detailed_products::Error::ProductVariants( + products.into_iter().map(|p| p.id).collect(), + )); + } + }; + let dbm = ProductVariantsStock { + product_variant_ids: variants.iter().map(|p| p.id).collect(), + }; + let stocks = match dbm.run(&mut *t).await { + Ok(stocks) => stocks, + Err(e) => { + tracing::error!("{}", e); + return Err(detailed_products::Error::VariantStocks( + variants.into_iter().map(|p| p.id).collect(), + )); + } + }; + let dbm = PhotosForProductVariants { + product_variant_ids: variants.iter().map(|p| p.id).collect(), + }; + let photos = match dbm.run(t).await { + Ok(photos) => photos, + Err(e) => { + tracing::error!("{}", e); + return Err(detailed_products::Error::VariantPhotos( + variants.into_iter().map(|p| p.id).collect(), + )); + } + }; + + let mut variants = { + let len = variants.len(); + variants + .into_iter() + .fold(HashMap::with_capacity(len), |mut h, variant| { + h.entry(variant.product_id) + .or_insert_with(|| Vec::with_capacity(10)) + .push(variant); + h + }) + }; + + let mut stocks = { + let len = stocks.len(); + stocks + .into_iter() + .fold(HashMap::with_capacity(len), |mut h, stock| { + h.entry(stock.product_variant_id) + .or_insert_with(|| Vec::with_capacity(10)) + .push(stock); + h + }) + }; + + let mut photos = + photos + .into_iter() + .fold(HashMap::with_capacity(stocks.len()), |mut h, photo| { + h.entry(photo.product_variant_id) + .or_insert_with(|| Vec::with_capacity(10)) + .push(photo); + h + }); + + let products = products + .into_iter() + .map( + |Product { + id, + name, + category, + deliver_days_flag, + }| DetailedProduct { + id, + name, + category, + deliver_days_flag, + variants: variants + .remove(&id) + .unwrap_or(vec![]) + .into_iter() + .map( + |ProductVariant { + id, + product_id: _, + name, + short_description, + long_description, + price, + }| DetailedProductVariant { + id, + name, + short_description, + long_description, + price, + stocks: stocks.remove(&id).unwrap_or_default(), + photos: photos.remove(&id).unwrap_or_default(), + }, + ) + .collect(), + }, + ) + .collect(); + + Ok(detailed_products::Details { products }) +} + +#[cfg(test)] +mod tests { + use channels::stocks::detailed_products; + use config::UpdateConfig; + use db_utils::PgT; + use model::v2::*; + use uuid::Uuid; + + use crate::actions::load::inner_detailed_products; + use crate::db::*; + + pub struct NoOpts; + impl UpdateConfig for NoOpts {} + + async fn test_product(t: &mut PgT<'_>) -> Product { + CreateProduct { + name: ProductName::new(format!("{}", Uuid::new_v4())), + category: None, + deliver_days_flag: Days(vec![Day::Friday, Day::Sunday]), + } + .run(t) + .await + .unwrap() + } + + async fn test_product_variant(product_id: ProductId, t: &mut PgT<'_>) -> ProductVariant { + CreateProductVariant { + product_id, + name: ProductName::new(format!("{}", Uuid::new_v4())), + short_description: ProductShortDesc::new(format!("{}", Uuid::new_v4())), + long_description: ProductLongDesc::new(format!("{}", Uuid::new_v4())), + price: Default::default(), + } + .run(t) + .await + .unwrap() + } + + async fn test_photo(t: &mut PgT<'_>) -> Photo { + CreatePhoto { + local_path: LocalPath::new(format!("{}", Uuid::new_v4())), + file_name: FileName::new(format!("{}", Uuid::new_v4())), + unique_name: UniqueName::new(format!("{}", Uuid::new_v4())), + } + .run(t) + .await + .unwrap() + } + + async fn test_product_photo( + product_variant_id: ProductVariantId, + photo_id: PhotoId, + t: &mut PgT<'_>, + ) -> ProductPhoto { + CreateProductPhoto { + product_variant_id, + photo_id, + } + .run(t) + .await + .unwrap() + } + + async fn n_test_photo( + n: usize, + product_variant_id: ProductVariantId, + t: &mut PgT<'_>, + ) -> Vec<(Photo, ProductPhoto)> { + let mut res = Vec::with_capacity(n); + + for _ in 0..n { + let photo = test_photo(t).await; + let product_photo = test_product_photo(product_variant_id, photo.id, t).await; + + res.push((photo, product_photo)); + } + + res + } + + async fn test_stock(product_variant_id: ProductVariantId, pool: &mut PgT<'_>) -> Stock { + let quantity = Quantity::from_u32(345); + let quantity_unit = QuantityUnit::Piece; + + CreateStock { + product_variant_id, + quantity_unit, + quantity, + } + .run(&mut *pool) + .await + .unwrap() + } + + async fn n_test_variant( + variant_count: usize, + product_id: ProductId, + t: &mut PgT<'_>, + ) -> Vec<(ProductVariant, Stock, Vec<(Photo, ProductPhoto)>)> { + let mut variants = Vec::with_capacity(variant_count); + + for _ in 0..variant_count { + let variant = test_product_variant(product_id, t).await; + let stock = test_stock(variant.id, t).await; + let photos = n_test_photo(3, variant.id, t).await; + + variants.push((variant, stock, photos)); + } + variants + } + + #[tokio::test] + async fn load_details() { + testx::db_t_ref!(t); + + let product_1 = test_product(&mut t).await; + let _variants_1 = n_test_variant(3, product_1.id, &mut t).await; + + let product_2 = test_product(&mut t).await; + let _variants_2 = n_test_variant(5, product_2.id, &mut t).await; + + let product_3 = test_product(&mut t).await; + let _variants_2 = n_test_variant(2, product_3.id, &mut t).await; + + let res = inner_detailed_products( + detailed_products::Input { + limit: Limit::from_u32(2000), + offset: Offset::from_u32(0), + }, + &mut t, + None, + None, + ) + .await; + + testx::db_rollback!(t); + + let mut res = res.unwrap(); + + assert_eq!(res.products.len(), 3); + let product = res.products.remove(0); + assert_eq!(product.variants.len(), 3); + for variant in product.variants { + assert_eq!(variant.photos.len(), 3); + assert_eq!(variant.stocks.len(), 1); + } + + let product = res.products.remove(0); + assert_eq!(product.variants.len(), 5); + for variant in product.variants { + assert_eq!(variant.photos.len(), 3); + assert_eq!(variant.stocks.len(), 1); + } + + let product = res.products.remove(0); + assert_eq!(product.variants.len(), 2); + for variant in product.variants { + assert_eq!(variant.photos.len(), 3); + assert_eq!(variant.stocks.len(), 1); + } + } } diff --git a/crates/stock_manager/src/actions/product.rs b/crates/stock_manager/src/actions/product.rs index a250285..bd808a8 100644 --- a/crates/stock_manager/src/actions/product.rs +++ b/crates/stock_manager/src/actions/product.rs @@ -5,28 +5,28 @@ use config::SharedAppConfig; use crate::db::Database; pub async fn create_product( - input: create_product::Input, - db: Database, - mqtt: AsyncClient, - config: SharedAppConfig, + _input: create_product::Input, + _db: Database, + _mqtt: AsyncClient, + _config: SharedAppConfig, ) -> create_product::Output { todo!() } pub async fn update_product( - input: update_product::Input, - db: Database, - mqtt: AsyncClient, - config: SharedAppConfig, + _input: update_product::Input, + _db: Database, + _mqtt: AsyncClient, + _config: SharedAppConfig, ) -> update_product::Output { todo!() } pub async fn delete_product( - input: delete_product::Input, - db: Database, - mqtt: AsyncClient, - config: SharedAppConfig, + _input: delete_product::Input, + _db: Database, + _mqtt: AsyncClient, + _config: SharedAppConfig, ) -> delete_product::Output { todo!() } diff --git a/crates/stock_manager/src/actions/product_photo.rs b/crates/stock_manager/src/actions/product_photo.rs index d689070..f805025 100644 --- a/crates/stock_manager/src/actions/product_photo.rs +++ b/crates/stock_manager/src/actions/product_photo.rs @@ -5,19 +5,19 @@ use config::SharedAppConfig; use crate::db::Database; pub async fn add_product_photo( - input: add_product_photo::Input, - db: Database, - mqtt: AsyncClient, - config: SharedAppConfig, + _input: add_product_photo::Input, + _db: Database, + _mqtt: AsyncClient, + _config: SharedAppConfig, ) -> add_product_photo::Output { todo!() } pub async fn delete_product_photo( - input: delete_product_photo::Input, - db: Database, - mqtt: AsyncClient, - config: SharedAppConfig, + _input: delete_product_photo::Input, + _db: Database, + _mqtt: AsyncClient, + _config: SharedAppConfig, ) -> delete_product_photo::Output { todo!() } diff --git a/crates/stock_manager/src/actions/product_stock.rs b/crates/stock_manager/src/actions/product_stock.rs index 37cec3e..0792894 100644 --- a/crates/stock_manager/src/actions/product_stock.rs +++ b/crates/stock_manager/src/actions/product_stock.rs @@ -5,19 +5,19 @@ use config::SharedAppConfig; use crate::db::Database; pub async fn create_product_stock( - input: create_product_stock::Input, - db: Database, - mqtt: AsyncClient, - config: SharedAppConfig, + _input: create_product_stock::Input, + _db: Database, + _mqtt: AsyncClient, + _config: SharedAppConfig, ) -> create_product_stock::Output { todo!() } pub async fn update_product_stock( - input: update_product_stock::Input, - db: Database, - mqtt: AsyncClient, - config: SharedAppConfig, + _input: update_product_stock::Input, + _db: Database, + _mqtt: AsyncClient, + _config: SharedAppConfig, ) -> update_product_stock::Output { todo!() } diff --git a/crates/stock_manager/src/actions/product_variant.rs b/crates/stock_manager/src/actions/product_variant.rs index f0e7f19..c0caf6b 100644 --- a/crates/stock_manager/src/actions/product_variant.rs +++ b/crates/stock_manager/src/actions/product_variant.rs @@ -5,28 +5,28 @@ use config::SharedAppConfig; use crate::db::Database; pub async fn create_product_variant( - input: create_product_variant::Input, - db: Database, - mqtt: AsyncClient, - config: SharedAppConfig, + _input: create_product_variant::Input, + _db: Database, + _mqtt: AsyncClient, + _config: SharedAppConfig, ) -> create_product_variant::Output { todo!() } pub async fn update_product_variant( - input: update_product_variant::Input, - db: Database, - mqtt: AsyncClient, - config: SharedAppConfig, + _input: update_product_variant::Input, + _db: Database, + _mqtt: AsyncClient, + _config: SharedAppConfig, ) -> update_product_variant::Output { todo!() } pub async fn delete_product_variant( - input: delete_product_variant::Input, - db: Database, - mqtt: AsyncClient, - config: SharedAppConfig, + _input: delete_product_variant::Input, + _db: Database, + _mqtt: AsyncClient, + _config: SharedAppConfig, ) -> delete_product_variant::Output { todo!() } diff --git a/crates/stock_manager/src/actions/snapshots/stock_manager__actions__load__tests__load_details.snap b/crates/stock_manager/src/actions/snapshots/stock_manager__actions__load__tests__load_details.snap new file mode 100644 index 0000000..02467fe --- /dev/null +++ b/crates/stock_manager/src/actions/snapshots/stock_manager__actions__load__tests__load_details.snap @@ -0,0 +1,939 @@ +--- +source: crates/stock_manager/./src/actions/load.rs +expression: res +--- +Details { + products: [ + DetailedProduct { + id: ProductId( + 136, + ), + name: ProductName( + "cfc46f2d-f56a-4192-86ac-669eee544e2c", + ), + category: None, + deliver_days_flag: Days( + [ + Friday, + Sunday, + ], + ), + variants: [ + DetailedProductVariant { + id: ProductVariantId( + 125, + ), + name: ProductName( + "d2caf306-3e74-40a7-ae8d-67dffa1fe89c", + ), + short_description: ProductShortDesc( + "2ff98474-8036-4593-b375-1b847f5feeb5", + ), + long_description: ProductLongDesc( + "1edbf80a-c7f8-4289-b245-eb9342300876", + ), + price: Price( + NonNegative( + 0, + ), + ), + stocks: [ + Stock { + id: StockId( + 67, + ), + product_variant_id: ProductVariantId( + 125, + ), + quantity: Quantity( + NonNegative( + 345, + ), + ), + quantity_unit: Piece, + }, + ], + photos: [ + ProductLinkedPhoto { + photo_id: PhotoId( + 163, + ), + local_path: LocalPath( + "5d04424a-6800-41df-914a-2e9a526e12e7", + ), + file_name: FileName( + "7e835666-264b-40c6-a346-5eeddb061acd", + ), + unique_name: UniqueName( + "9ddd3f1f-8ec4-4810-8db2-58ebe3418a23", + ), + product_variant_id: ProductVariantId( + 125, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 164, + ), + local_path: LocalPath( + "1057d8d9-5ba4-4eca-8ca6-97574a784bbc", + ), + file_name: FileName( + "ac6508fd-99f5-4674-a5b2-f349dd955fbf", + ), + unique_name: UniqueName( + "cc2147d8-b349-4c41-9a49-fafc93c9e884", + ), + product_variant_id: ProductVariantId( + 125, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 165, + ), + local_path: LocalPath( + "b03eb5a2-c26a-47c8-822b-7619d00ebbed", + ), + file_name: FileName( + "3902391c-c47c-465d-a2ec-623f53cce814", + ), + unique_name: UniqueName( + "bbafe518-73ee-4685-8cc0-0cd2e814d32c", + ), + product_variant_id: ProductVariantId( + 125, + ), + }, + ], + }, + DetailedProductVariant { + id: ProductVariantId( + 126, + ), + name: ProductName( + "1323b9bc-9c97-487d-afc8-2003c70f440f", + ), + short_description: ProductShortDesc( + "3740f189-9464-4e41-8883-106598bb6e7e", + ), + long_description: ProductLongDesc( + "e37d71aa-3bd1-42ec-915a-1fa1c761ec7b", + ), + price: Price( + NonNegative( + 0, + ), + ), + stocks: [ + Stock { + id: StockId( + 68, + ), + product_variant_id: ProductVariantId( + 126, + ), + quantity: Quantity( + NonNegative( + 345, + ), + ), + quantity_unit: Piece, + }, + ], + photos: [ + ProductLinkedPhoto { + photo_id: PhotoId( + 166, + ), + local_path: LocalPath( + "1ced075e-4ab0-4c52-8e1f-cb0e0d07b95c", + ), + file_name: FileName( + "478de82f-b366-421a-abce-19779845b659", + ), + unique_name: UniqueName( + "ffc7d82e-4401-4975-8469-bee5dc3345e7", + ), + product_variant_id: ProductVariantId( + 126, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 167, + ), + local_path: LocalPath( + "2d46cc8c-8c8e-4100-ae18-ca935cbeb1d9", + ), + file_name: FileName( + "1ba9314e-d928-4b4c-b75c-4287c0644b75", + ), + unique_name: UniqueName( + "b45017fd-a804-48cc-8f4d-c21c00e95f10", + ), + product_variant_id: ProductVariantId( + 126, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 168, + ), + local_path: LocalPath( + "dda34f45-1686-46c9-bdcb-259e147718b2", + ), + file_name: FileName( + "1fdba406-e2fe-444b-ae78-1ded4d985b55", + ), + unique_name: UniqueName( + "99243264-9098-4fa0-a3d0-4cd03248d66a", + ), + product_variant_id: ProductVariantId( + 126, + ), + }, + ], + }, + DetailedProductVariant { + id: ProductVariantId( + 127, + ), + name: ProductName( + "0ff0a1e6-c458-4525-b431-c7c5bfa13515", + ), + short_description: ProductShortDesc( + "ee7f70da-e46f-488b-9bc2-743ebc3066a3", + ), + long_description: ProductLongDesc( + "d529826e-e647-4191-9fcf-8bc1ada01e9d", + ), + price: Price( + NonNegative( + 0, + ), + ), + stocks: [ + Stock { + id: StockId( + 69, + ), + product_variant_id: ProductVariantId( + 127, + ), + quantity: Quantity( + NonNegative( + 345, + ), + ), + quantity_unit: Piece, + }, + ], + photos: [ + ProductLinkedPhoto { + photo_id: PhotoId( + 169, + ), + local_path: LocalPath( + "06bc914f-ff75-4eb2-8520-f509fdba1fd6", + ), + file_name: FileName( + "8db810d6-cc66-494e-989f-c604ae02bb61", + ), + unique_name: UniqueName( + "4facf461-b86d-4ae6-b3d6-a0fd4b2e94c9", + ), + product_variant_id: ProductVariantId( + 127, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 170, + ), + local_path: LocalPath( + "8de3b87c-b1f9-4f31-9c07-c4efdc078a5e", + ), + file_name: FileName( + "26b5b728-917d-4750-8fc1-c31990824ed3", + ), + unique_name: UniqueName( + "0656ea1a-4976-46fd-a7d2-550ea15b18ad", + ), + product_variant_id: ProductVariantId( + 127, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 171, + ), + local_path: LocalPath( + "66da98b9-fa8a-4fa6-84e6-5c6911f1de2d", + ), + file_name: FileName( + "37033640-df1c-4f6a-a2ab-e100e1c5589f", + ), + unique_name: UniqueName( + "717cf8ee-01fd-416a-a114-da0bbd6a3911", + ), + product_variant_id: ProductVariantId( + 127, + ), + }, + ], + }, + ], + }, + DetailedProduct { + id: ProductId( + 137, + ), + name: ProductName( + "7e774acc-e2da-4738-9314-fe9fe261e3b0", + ), + category: None, + deliver_days_flag: Days( + [ + Friday, + Sunday, + ], + ), + variants: [ + DetailedProductVariant { + id: ProductVariantId( + 128, + ), + name: ProductName( + "9ea07670-3bdf-4590-a1d6-a3ca9a1b5f97", + ), + short_description: ProductShortDesc( + "d64ca158-d322-40e6-909b-e61cf85174bc", + ), + long_description: ProductLongDesc( + "dce8c61d-c9d6-4fda-9835-314fdce460da", + ), + price: Price( + NonNegative( + 0, + ), + ), + stocks: [ + Stock { + id: StockId( + 70, + ), + product_variant_id: ProductVariantId( + 128, + ), + quantity: Quantity( + NonNegative( + 345, + ), + ), + quantity_unit: Piece, + }, + ], + photos: [ + ProductLinkedPhoto { + photo_id: PhotoId( + 172, + ), + local_path: LocalPath( + "73c82632-17c4-43c8-83ad-d48bc07aff88", + ), + file_name: FileName( + "648beb23-ffae-42db-b03a-36a872ae93f6", + ), + unique_name: UniqueName( + "7b44021b-c2e7-4023-a07f-203aecbd0d4e", + ), + product_variant_id: ProductVariantId( + 128, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 173, + ), + local_path: LocalPath( + "3332438c-ac16-4620-b82e-65f971b37a8c", + ), + file_name: FileName( + "6946453d-aa6b-4f99-8c9b-43d1c6a8ae2f", + ), + unique_name: UniqueName( + "6d8c6222-1b0f-4b32-9376-5593fefd4d76", + ), + product_variant_id: ProductVariantId( + 128, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 174, + ), + local_path: LocalPath( + "a6af6492-147d-4ec2-9240-d1d9c520c37e", + ), + file_name: FileName( + "e55dac90-3aa2-499f-9551-45f6454b98bd", + ), + unique_name: UniqueName( + "f8683c9c-5688-4490-82bc-24d1948ae46a", + ), + product_variant_id: ProductVariantId( + 128, + ), + }, + ], + }, + DetailedProductVariant { + id: ProductVariantId( + 129, + ), + name: ProductName( + "6c85837d-f0e1-4e91-a8a7-f1953c0901a6", + ), + short_description: ProductShortDesc( + "20e15392-208c-42f2-9de4-88d317168378", + ), + long_description: ProductLongDesc( + "951e5740-4f36-46bd-a5fa-4978c5909afa", + ), + price: Price( + NonNegative( + 0, + ), + ), + stocks: [ + Stock { + id: StockId( + 71, + ), + product_variant_id: ProductVariantId( + 129, + ), + quantity: Quantity( + NonNegative( + 345, + ), + ), + quantity_unit: Piece, + }, + ], + photos: [ + ProductLinkedPhoto { + photo_id: PhotoId( + 175, + ), + local_path: LocalPath( + "02760202-1c91-4122-b2c7-4b0a7dad3459", + ), + file_name: FileName( + "20e48ad2-e265-4349-bcb7-2c7c0bc577b2", + ), + unique_name: UniqueName( + "ffe76596-3789-48a9-a22e-af4213a0c380", + ), + product_variant_id: ProductVariantId( + 129, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 176, + ), + local_path: LocalPath( + "1c0a21a2-9825-4d2f-bb7d-a7cefab2db02", + ), + file_name: FileName( + "ebf4b435-e30b-4a08-a748-e04da7f86b66", + ), + unique_name: UniqueName( + "bad53d6f-5dff-4c81-88df-836e839d60be", + ), + product_variant_id: ProductVariantId( + 129, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 177, + ), + local_path: LocalPath( + "b7f35012-f084-4390-8b30-aa1d66ce1737", + ), + file_name: FileName( + "277f595e-6285-4a1c-9ac2-a5efc7105062", + ), + unique_name: UniqueName( + "3055730c-0a36-414a-abc9-1b84ded85769", + ), + product_variant_id: ProductVariantId( + 129, + ), + }, + ], + }, + DetailedProductVariant { + id: ProductVariantId( + 130, + ), + name: ProductName( + "9ce07ea7-aa6d-4b4e-9af6-e86f3ce6b697", + ), + short_description: ProductShortDesc( + "f5a72d7f-5252-4f75-a88d-e0ab9b334bf6", + ), + long_description: ProductLongDesc( + "986fa45f-23f8-4a70-bf17-541f295baec0", + ), + price: Price( + NonNegative( + 0, + ), + ), + stocks: [ + Stock { + id: StockId( + 72, + ), + product_variant_id: ProductVariantId( + 130, + ), + quantity: Quantity( + NonNegative( + 345, + ), + ), + quantity_unit: Piece, + }, + ], + photos: [ + ProductLinkedPhoto { + photo_id: PhotoId( + 178, + ), + local_path: LocalPath( + "6cda381b-df63-432f-a7c5-3d4496624d75", + ), + file_name: FileName( + "b541c928-5f93-4902-8ce7-2ed45b366b91", + ), + unique_name: UniqueName( + "34f326f6-fefa-4ad3-8112-4ef942e98c54", + ), + product_variant_id: ProductVariantId( + 130, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 179, + ), + local_path: LocalPath( + "3830cff1-a923-4116-a946-8e7637645f7b", + ), + file_name: FileName( + "ef46ff42-cd3f-40a5-bec9-ab14734e3b1b", + ), + unique_name: UniqueName( + "d89b8959-b9b8-4f7b-a7b4-d7548b01d6fa", + ), + product_variant_id: ProductVariantId( + 130, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 180, + ), + local_path: LocalPath( + "0ac50443-0027-4151-8a0c-80e942fe25d1", + ), + file_name: FileName( + "c3650bf4-42e7-4376-bda8-af134bf1a4fa", + ), + unique_name: UniqueName( + "84687975-eb4c-43a3-854d-6b4112d2e4a5", + ), + product_variant_id: ProductVariantId( + 130, + ), + }, + ], + }, + DetailedProductVariant { + id: ProductVariantId( + 131, + ), + name: ProductName( + "b972dd90-5466-42f2-90f3-bae6615922dc", + ), + short_description: ProductShortDesc( + "ee2b0694-34d1-4c05-a885-2e434df5cef7", + ), + long_description: ProductLongDesc( + "20ea6a02-d76c-4a35-b1b2-3a97a440e6db", + ), + price: Price( + NonNegative( + 0, + ), + ), + stocks: [ + Stock { + id: StockId( + 73, + ), + product_variant_id: ProductVariantId( + 131, + ), + quantity: Quantity( + NonNegative( + 345, + ), + ), + quantity_unit: Piece, + }, + ], + photos: [ + ProductLinkedPhoto { + photo_id: PhotoId( + 181, + ), + local_path: LocalPath( + "b8077234-e7bc-40fd-ac84-907bbe9285ee", + ), + file_name: FileName( + "3bf8ce7b-2639-4809-8b33-3b16200e7687", + ), + unique_name: UniqueName( + "477c3e9e-d9a2-4858-98d9-c61dcec7b629", + ), + product_variant_id: ProductVariantId( + 131, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 182, + ), + local_path: LocalPath( + "1e369270-54e1-4b19-9d9f-2b9204717cc3", + ), + file_name: FileName( + "7b168038-d1b7-43c1-b8a0-d3d7ce00edd3", + ), + unique_name: UniqueName( + "1b0defdc-6220-4647-8403-1e990cf8370a", + ), + product_variant_id: ProductVariantId( + 131, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 183, + ), + local_path: LocalPath( + "8f3cbfa7-2653-491d-9a44-59f436de4d9d", + ), + file_name: FileName( + "72c9483b-1366-4a3e-8baf-92fe550c4190", + ), + unique_name: UniqueName( + "b2820458-14ca-4b97-bf0e-ff6d434a8250", + ), + product_variant_id: ProductVariantId( + 131, + ), + }, + ], + }, + DetailedProductVariant { + id: ProductVariantId( + 132, + ), + name: ProductName( + "7650e262-fc4d-4fcf-9068-6767c9398a11", + ), + short_description: ProductShortDesc( + "0901ae8b-ab73-4332-9179-c6722b72b3b3", + ), + long_description: ProductLongDesc( + "ec5b29ff-b961-4840-a2b9-ebdb0c189bc7", + ), + price: Price( + NonNegative( + 0, + ), + ), + stocks: [ + Stock { + id: StockId( + 74, + ), + product_variant_id: ProductVariantId( + 132, + ), + quantity: Quantity( + NonNegative( + 345, + ), + ), + quantity_unit: Piece, + }, + ], + photos: [ + ProductLinkedPhoto { + photo_id: PhotoId( + 184, + ), + local_path: LocalPath( + "2851d6a1-e9af-4afd-9c03-99b1d38343e7", + ), + file_name: FileName( + "3b41801e-fc52-4e4f-b377-dde05a234051", + ), + unique_name: UniqueName( + "996e03cf-3951-4bfe-aba0-5f594b78ad4f", + ), + product_variant_id: ProductVariantId( + 132, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 185, + ), + local_path: LocalPath( + "3d777b4e-db95-4bec-b37e-5424f05c7aec", + ), + file_name: FileName( + "d5b5b067-09c8-4e77-8a43-08ad39d06858", + ), + unique_name: UniqueName( + "bf7a3949-8caf-43cd-ac38-446b9b8327fa", + ), + product_variant_id: ProductVariantId( + 132, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 186, + ), + local_path: LocalPath( + "4c61f99b-9e9a-4288-9a2c-3fe81ff4da11", + ), + file_name: FileName( + "5aa49987-04e0-4ce3-b34c-68cdbd5ef408", + ), + unique_name: UniqueName( + "6a850bc2-286b-4e93-9c3b-1d2d2db2f363", + ), + product_variant_id: ProductVariantId( + 132, + ), + }, + ], + }, + ], + }, + DetailedProduct { + id: ProductId( + 138, + ), + name: ProductName( + "dd0ba76a-d598-41a5-9ebf-5094a5c1b386", + ), + category: None, + deliver_days_flag: Days( + [ + Friday, + Sunday, + ], + ), + variants: [ + DetailedProductVariant { + id: ProductVariantId( + 133, + ), + name: ProductName( + "4d697353-4a3b-48f4-8877-430ac2f3404e", + ), + short_description: ProductShortDesc( + "25fea571-497d-44a2-ba7f-a793b8a51ca0", + ), + long_description: ProductLongDesc( + "7fb3d365-67df-4314-a8ba-3f9f3bf5ed14", + ), + price: Price( + NonNegative( + 0, + ), + ), + stocks: [ + Stock { + id: StockId( + 75, + ), + product_variant_id: ProductVariantId( + 133, + ), + quantity: Quantity( + NonNegative( + 345, + ), + ), + quantity_unit: Piece, + }, + ], + photos: [ + ProductLinkedPhoto { + photo_id: PhotoId( + 187, + ), + local_path: LocalPath( + "1bcc22a0-ce12-40e6-8566-97bded0c8f39", + ), + file_name: FileName( + "91f8e0a9-b9f9-4538-a1d4-e4f3d2c8c923", + ), + unique_name: UniqueName( + "8b879759-0276-4076-afbf-b3e0b07d5ba9", + ), + product_variant_id: ProductVariantId( + 133, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 188, + ), + local_path: LocalPath( + "8e49539d-0145-4e20-9017-4adc702fd4d5", + ), + file_name: FileName( + "a523ff42-1c2c-414a-b491-bedd396fa157", + ), + unique_name: UniqueName( + "ebd39341-c06d-46a7-8a59-9a08dd6babab", + ), + product_variant_id: ProductVariantId( + 133, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 189, + ), + local_path: LocalPath( + "3653e4a4-5d17-4fef-ab55-69944d93c8ca", + ), + file_name: FileName( + "23aa3f97-db96-4311-9f77-d2acba75f6ab", + ), + unique_name: UniqueName( + "1a7aea33-97e1-4867-855b-c12be218a1b4", + ), + product_variant_id: ProductVariantId( + 133, + ), + }, + ], + }, + DetailedProductVariant { + id: ProductVariantId( + 134, + ), + name: ProductName( + "a8480794-391c-47bd-8fa3-54ea852827c5", + ), + short_description: ProductShortDesc( + "ecca3052-db96-48ae-b451-d46045928181", + ), + long_description: ProductLongDesc( + "43b98161-03b1-47b5-b7c5-9f9d58dbd64f", + ), + price: Price( + NonNegative( + 0, + ), + ), + stocks: [ + Stock { + id: StockId( + 76, + ), + product_variant_id: ProductVariantId( + 134, + ), + quantity: Quantity( + NonNegative( + 345, + ), + ), + quantity_unit: Piece, + }, + ], + photos: [ + ProductLinkedPhoto { + photo_id: PhotoId( + 190, + ), + local_path: LocalPath( + "7e36e8c0-7141-4557-bb15-4c9fed8f9cdc", + ), + file_name: FileName( + "e4446e83-b766-4893-9306-09af45d04fce", + ), + unique_name: UniqueName( + "e7f86bc8-bf06-4f3b-a5b0-4e3610acd826", + ), + product_variant_id: ProductVariantId( + 134, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 191, + ), + local_path: LocalPath( + "b61c67a2-b104-4ef9-9fe7-bf45427ad1e4", + ), + file_name: FileName( + "3be64efc-782e-4b1d-bc2e-15902ddd400f", + ), + unique_name: UniqueName( + "dc7c76bc-db93-48e4-ae20-35de71672f4c", + ), + product_variant_id: ProductVariantId( + 134, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 192, + ), + local_path: LocalPath( + "c17c9655-4c10-4fa9-a549-e1bb60a42d21", + ), + file_name: FileName( + "c8ec1a1c-7264-4813-96f3-e3c776b5b33d", + ), + unique_name: UniqueName( + "3c51860d-b4f9-45ef-9aef-7483fb9e249b", + ), + product_variant_id: ProductVariantId( + 134, + ), + }, + ], + }, + ], + }, + ], +} diff --git a/crates/stock_manager/src/actions/snapshots/stock_manager__actions__load__tests__load_details.snap.new b/crates/stock_manager/src/actions/snapshots/stock_manager__actions__load__tests__load_details.snap.new new file mode 100644 index 0000000..1d9e921 --- /dev/null +++ b/crates/stock_manager/src/actions/snapshots/stock_manager__actions__load__tests__load_details.snap.new @@ -0,0 +1,940 @@ +--- +source: crates/stock_manager/./src/actions/load.rs +assertion_line: 315 +expression: res +--- +Details { + products: [ + DetailedProduct { + id: ProductId( + 139, + ), + name: ProductName( + "6757e180-f20e-4946-97f2-ef2e6cbe2480", + ), + category: None, + deliver_days_flag: Days( + [ + Friday, + Sunday, + ], + ), + variants: [ + DetailedProductVariant { + id: ProductVariantId( + 135, + ), + name: ProductName( + "63348738-0540-4873-8272-7a4da873af66", + ), + short_description: ProductShortDesc( + "436a6050-839e-49e8-8de5-38912b77319e", + ), + long_description: ProductLongDesc( + "1e3f627b-72ec-431d-b970-56dbd5bcca55", + ), + price: Price( + NonNegative( + 0, + ), + ), + stocks: [ + Stock { + id: StockId( + 77, + ), + product_variant_id: ProductVariantId( + 135, + ), + quantity: Quantity( + NonNegative( + 345, + ), + ), + quantity_unit: Piece, + }, + ], + photos: [ + ProductLinkedPhoto { + photo_id: PhotoId( + 193, + ), + local_path: LocalPath( + "3974997e-c1a6-4e78-bfba-ca5fdfef2b99", + ), + file_name: FileName( + "3fe1b917-0453-460e-9753-20d83ab773cb", + ), + unique_name: UniqueName( + "47bb8098-58de-4ea6-b251-6adbac2a6192", + ), + product_variant_id: ProductVariantId( + 135, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 194, + ), + local_path: LocalPath( + "b9127a53-aa7d-4a38-bfee-606ef736523a", + ), + file_name: FileName( + "dd670ac5-cf3f-4231-a49f-21ea987e8d1a", + ), + unique_name: UniqueName( + "a28c1adb-587f-42c9-a304-947557eb44fe", + ), + product_variant_id: ProductVariantId( + 135, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 195, + ), + local_path: LocalPath( + "665c928f-ba35-4320-8917-9b09658a2303", + ), + file_name: FileName( + "30c5df9c-1be2-4523-9b54-ea82b7ca57ef", + ), + unique_name: UniqueName( + "69065163-0684-4b03-82f4-ab147b692b4d", + ), + product_variant_id: ProductVariantId( + 135, + ), + }, + ], + }, + DetailedProductVariant { + id: ProductVariantId( + 136, + ), + name: ProductName( + "a27d752e-5544-4679-96a4-ae8173628e8c", + ), + short_description: ProductShortDesc( + "b84be335-8879-45b0-9b48-31ea9722c255", + ), + long_description: ProductLongDesc( + "d096233f-f5b4-4978-83f3-08fb99c6a800", + ), + price: Price( + NonNegative( + 0, + ), + ), + stocks: [ + Stock { + id: StockId( + 78, + ), + product_variant_id: ProductVariantId( + 136, + ), + quantity: Quantity( + NonNegative( + 345, + ), + ), + quantity_unit: Piece, + }, + ], + photos: [ + ProductLinkedPhoto { + photo_id: PhotoId( + 196, + ), + local_path: LocalPath( + "94672fee-f893-4413-a5c5-4b3f0645555a", + ), + file_name: FileName( + "f57af52b-46ac-44ed-8c7f-9b0e95c2fe44", + ), + unique_name: UniqueName( + "aa29310b-ec70-4fc2-9879-e431e663507f", + ), + product_variant_id: ProductVariantId( + 136, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 197, + ), + local_path: LocalPath( + "c6c55e0f-cc55-495e-9f0b-389f745c9d39", + ), + file_name: FileName( + "ed291f5f-a4e8-4097-9570-6fcf970c99dc", + ), + unique_name: UniqueName( + "d2da4f18-6c36-4bfb-a7cc-3a02412aef34", + ), + product_variant_id: ProductVariantId( + 136, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 198, + ), + local_path: LocalPath( + "59904639-ed9f-4a65-94b3-e44bd788acb1", + ), + file_name: FileName( + "6594df2d-82e8-4320-b542-2409581740d4", + ), + unique_name: UniqueName( + "9bb9118e-9afa-4386-84a1-8f192eca6b4d", + ), + product_variant_id: ProductVariantId( + 136, + ), + }, + ], + }, + DetailedProductVariant { + id: ProductVariantId( + 137, + ), + name: ProductName( + "a0ca4130-a0e1-46c9-b8aa-e66e76042097", + ), + short_description: ProductShortDesc( + "5b22977e-9aa7-4c26-9030-42a42541d80c", + ), + long_description: ProductLongDesc( + "dcd41ae9-ceb9-441c-bd38-835f57cfa0d8", + ), + price: Price( + NonNegative( + 0, + ), + ), + stocks: [ + Stock { + id: StockId( + 79, + ), + product_variant_id: ProductVariantId( + 137, + ), + quantity: Quantity( + NonNegative( + 345, + ), + ), + quantity_unit: Piece, + }, + ], + photos: [ + ProductLinkedPhoto { + photo_id: PhotoId( + 199, + ), + local_path: LocalPath( + "8e1241bd-a00c-4eda-a840-16a9f29e18bc", + ), + file_name: FileName( + "e32cb19e-5a9e-47bf-ae43-d5e191112b16", + ), + unique_name: UniqueName( + "e806e980-e2a9-4e2a-81ac-ede89b4ccd56", + ), + product_variant_id: ProductVariantId( + 137, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 200, + ), + local_path: LocalPath( + "712980c6-5dad-4012-b179-b580c0709da2", + ), + file_name: FileName( + "353a27ef-4833-4773-8e34-a072ea64d477", + ), + unique_name: UniqueName( + "36c3dc37-fe62-44da-a894-7f739739374e", + ), + product_variant_id: ProductVariantId( + 137, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 201, + ), + local_path: LocalPath( + "79648a75-389e-4c7d-9417-0370d77ddf30", + ), + file_name: FileName( + "c17bf5a9-c10e-4fe7-8cd0-187a4687c341", + ), + unique_name: UniqueName( + "68d4c0cf-e493-4347-85cf-1b4d67ba22a9", + ), + product_variant_id: ProductVariantId( + 137, + ), + }, + ], + }, + ], + }, + DetailedProduct { + id: ProductId( + 140, + ), + name: ProductName( + "65a75053-5213-4e29-a182-e40f27a70fc6", + ), + category: None, + deliver_days_flag: Days( + [ + Friday, + Sunday, + ], + ), + variants: [ + DetailedProductVariant { + id: ProductVariantId( + 138, + ), + name: ProductName( + "932b97a3-16d0-42bd-8260-69aa740a0ae0", + ), + short_description: ProductShortDesc( + "766cb4bd-7702-4f68-a38d-65fb29b5d1ff", + ), + long_description: ProductLongDesc( + "e3ec80d8-7296-417e-a3cc-4b747910b0c7", + ), + price: Price( + NonNegative( + 0, + ), + ), + stocks: [ + Stock { + id: StockId( + 80, + ), + product_variant_id: ProductVariantId( + 138, + ), + quantity: Quantity( + NonNegative( + 345, + ), + ), + quantity_unit: Piece, + }, + ], + photos: [ + ProductLinkedPhoto { + photo_id: PhotoId( + 202, + ), + local_path: LocalPath( + "469b253d-bcb3-49d9-a9b3-c5ce5f4a1e48", + ), + file_name: FileName( + "ef1a3db6-76d0-4c42-9c6f-1a3bc709bfcf", + ), + unique_name: UniqueName( + "8d9470a2-1a47-4ff1-b8dd-af72794c5a05", + ), + product_variant_id: ProductVariantId( + 138, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 203, + ), + local_path: LocalPath( + "a12f7daa-d611-4f0b-bc78-f46042f5cdae", + ), + file_name: FileName( + "28c99b25-f092-4715-97cf-5a923a24d1e8", + ), + unique_name: UniqueName( + "89d57281-fbd9-480d-bbd6-de50b4a16f09", + ), + product_variant_id: ProductVariantId( + 138, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 204, + ), + local_path: LocalPath( + "859ad5a9-6d48-4fdc-be6b-cc4ffd30e123", + ), + file_name: FileName( + "580a6f3f-054c-4d2e-b99e-75a4d9026be9", + ), + unique_name: UniqueName( + "23c1c21a-7a09-4c08-8cea-9a9634a04d99", + ), + product_variant_id: ProductVariantId( + 138, + ), + }, + ], + }, + DetailedProductVariant { + id: ProductVariantId( + 139, + ), + name: ProductName( + "643f4e9d-0938-4dbd-b94a-9f528123dca2", + ), + short_description: ProductShortDesc( + "0cf9a04a-6f02-4c41-9d78-d4a97c496c80", + ), + long_description: ProductLongDesc( + "07931de8-00af-44df-8e86-18ab2f9f9b1d", + ), + price: Price( + NonNegative( + 0, + ), + ), + stocks: [ + Stock { + id: StockId( + 81, + ), + product_variant_id: ProductVariantId( + 139, + ), + quantity: Quantity( + NonNegative( + 345, + ), + ), + quantity_unit: Piece, + }, + ], + photos: [ + ProductLinkedPhoto { + photo_id: PhotoId( + 205, + ), + local_path: LocalPath( + "8369a83f-7268-4735-a51b-62dd1a364e1c", + ), + file_name: FileName( + "c5f0dbc5-3430-4364-ba21-5d602573f2f6", + ), + unique_name: UniqueName( + "64cf47b7-ed11-4bb8-b11f-9be3c8d62bd7", + ), + product_variant_id: ProductVariantId( + 139, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 206, + ), + local_path: LocalPath( + "b58d2ecd-ecf2-4fe0-9a3f-22ec9596b746", + ), + file_name: FileName( + "bde7e93e-a566-4e9e-a16a-e19e2566b98c", + ), + unique_name: UniqueName( + "91561b33-a027-4bfd-baf7-a2f5d1ffc191", + ), + product_variant_id: ProductVariantId( + 139, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 207, + ), + local_path: LocalPath( + "d2d9461c-9efb-4b20-893d-eb8479ae6c42", + ), + file_name: FileName( + "1d06b1de-7a4b-48d3-8346-75fd4dc6b069", + ), + unique_name: UniqueName( + "b87937f7-dcce-4848-94e5-794c6a494f8f", + ), + product_variant_id: ProductVariantId( + 139, + ), + }, + ], + }, + DetailedProductVariant { + id: ProductVariantId( + 140, + ), + name: ProductName( + "08fc623b-575c-402f-848a-bb3dd3cfc480", + ), + short_description: ProductShortDesc( + "c114b73e-a695-4051-8c39-9e2746695f35", + ), + long_description: ProductLongDesc( + "f7fa3c0c-d3e1-40a2-a3bf-9651c4021081", + ), + price: Price( + NonNegative( + 0, + ), + ), + stocks: [ + Stock { + id: StockId( + 82, + ), + product_variant_id: ProductVariantId( + 140, + ), + quantity: Quantity( + NonNegative( + 345, + ), + ), + quantity_unit: Piece, + }, + ], + photos: [ + ProductLinkedPhoto { + photo_id: PhotoId( + 208, + ), + local_path: LocalPath( + "0ad8cfd9-da34-478e-8959-d6ad0dc98e2f", + ), + file_name: FileName( + "6c2b3581-c662-47a4-9d62-b21e6976759d", + ), + unique_name: UniqueName( + "d1eb14ff-caa0-48a5-a607-2c444fb44f75", + ), + product_variant_id: ProductVariantId( + 140, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 209, + ), + local_path: LocalPath( + "af6f87f7-255b-4064-a126-852f169af2eb", + ), + file_name: FileName( + "9e0d4d72-4d2d-48cc-940b-31b372c4a4c9", + ), + unique_name: UniqueName( + "760ba903-0395-43c7-ad81-bb2d7bda32c4", + ), + product_variant_id: ProductVariantId( + 140, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 210, + ), + local_path: LocalPath( + "f923f983-a580-4148-a140-67a666a96626", + ), + file_name: FileName( + "537b1067-2380-4fc7-9dd9-5b0c6c14f651", + ), + unique_name: UniqueName( + "9053de2a-8b81-4c37-a235-09d4cb28dd76", + ), + product_variant_id: ProductVariantId( + 140, + ), + }, + ], + }, + DetailedProductVariant { + id: ProductVariantId( + 141, + ), + name: ProductName( + "026b8f20-e70e-4471-b51b-c09305830c7b", + ), + short_description: ProductShortDesc( + "936497e3-c668-48da-8f0f-88fae113a3f9", + ), + long_description: ProductLongDesc( + "b677a454-1826-4294-badd-679accb4e143", + ), + price: Price( + NonNegative( + 0, + ), + ), + stocks: [ + Stock { + id: StockId( + 83, + ), + product_variant_id: ProductVariantId( + 141, + ), + quantity: Quantity( + NonNegative( + 345, + ), + ), + quantity_unit: Piece, + }, + ], + photos: [ + ProductLinkedPhoto { + photo_id: PhotoId( + 211, + ), + local_path: LocalPath( + "062f4634-84dc-4a78-876a-929a43257ad1", + ), + file_name: FileName( + "a27a1304-e19c-4a84-ada5-ac80b96ac918", + ), + unique_name: UniqueName( + "bf241c7a-c357-402b-acae-739f03580c15", + ), + product_variant_id: ProductVariantId( + 141, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 212, + ), + local_path: LocalPath( + "3b549514-5733-4432-abf3-a859ce8a06c7", + ), + file_name: FileName( + "3fa04f35-6943-40a0-9ac6-bec48b4d0032", + ), + unique_name: UniqueName( + "07e37cb5-54d5-47e6-a610-bf37f4af954c", + ), + product_variant_id: ProductVariantId( + 141, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 213, + ), + local_path: LocalPath( + "acb0eae0-aefd-4ed9-9591-5f311ab660c6", + ), + file_name: FileName( + "3153a928-04a6-43fc-82ca-56621f3a17ae", + ), + unique_name: UniqueName( + "79cfdf6a-8c45-418f-9914-96acafe95696", + ), + product_variant_id: ProductVariantId( + 141, + ), + }, + ], + }, + DetailedProductVariant { + id: ProductVariantId( + 142, + ), + name: ProductName( + "8b78214e-9732-4cf7-8f97-f5bab9a90a93", + ), + short_description: ProductShortDesc( + "130239a5-29b6-4dce-abd5-a62d07f36b92", + ), + long_description: ProductLongDesc( + "eb8b4e25-172f-4e2d-a6c6-83187c73684c", + ), + price: Price( + NonNegative( + 0, + ), + ), + stocks: [ + Stock { + id: StockId( + 84, + ), + product_variant_id: ProductVariantId( + 142, + ), + quantity: Quantity( + NonNegative( + 345, + ), + ), + quantity_unit: Piece, + }, + ], + photos: [ + ProductLinkedPhoto { + photo_id: PhotoId( + 214, + ), + local_path: LocalPath( + "bdc32dad-0dbb-40d9-8131-01886f7a4322", + ), + file_name: FileName( + "b762986e-9908-481f-b2aa-7129ecab73bc", + ), + unique_name: UniqueName( + "c6149195-3412-4da1-86c6-7598307e7541", + ), + product_variant_id: ProductVariantId( + 142, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 215, + ), + local_path: LocalPath( + "9d057572-933f-48b0-b2f6-117600b850cf", + ), + file_name: FileName( + "e4656c47-393d-4db6-ba0d-e8b559953425", + ), + unique_name: UniqueName( + "b661f962-7e5a-4237-9f49-e00eb11e2431", + ), + product_variant_id: ProductVariantId( + 142, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 216, + ), + local_path: LocalPath( + "d378747d-48f3-4f40-8ee0-467f9696bf39", + ), + file_name: FileName( + "493ae888-b415-4a1d-8a7c-0582971e7048", + ), + unique_name: UniqueName( + "78cdec2b-33b1-4300-8e8f-3497dc93772b", + ), + product_variant_id: ProductVariantId( + 142, + ), + }, + ], + }, + ], + }, + DetailedProduct { + id: ProductId( + 141, + ), + name: ProductName( + "3a7903bf-fbda-4184-bea7-90412a15f9cb", + ), + category: None, + deliver_days_flag: Days( + [ + Friday, + Sunday, + ], + ), + variants: [ + DetailedProductVariant { + id: ProductVariantId( + 143, + ), + name: ProductName( + "903a7945-4ee0-4c3f-9438-7a881d7dd1b1", + ), + short_description: ProductShortDesc( + "d76f0686-f0dc-4ac8-ba09-3f83b468c7a3", + ), + long_description: ProductLongDesc( + "185e393f-c705-49f8-afd6-dd472b9ea1a8", + ), + price: Price( + NonNegative( + 0, + ), + ), + stocks: [ + Stock { + id: StockId( + 85, + ), + product_variant_id: ProductVariantId( + 143, + ), + quantity: Quantity( + NonNegative( + 345, + ), + ), + quantity_unit: Piece, + }, + ], + photos: [ + ProductLinkedPhoto { + photo_id: PhotoId( + 217, + ), + local_path: LocalPath( + "8cc0c595-b557-4a91-8ec5-d2c47db2c622", + ), + file_name: FileName( + "131766fd-7acb-4a3e-97bc-730b7ddb95ad", + ), + unique_name: UniqueName( + "ed213f02-464e-46b9-9435-a548fba8255f", + ), + product_variant_id: ProductVariantId( + 143, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 218, + ), + local_path: LocalPath( + "c76bdd8b-46c1-4bbf-8598-76da38e6ed4f", + ), + file_name: FileName( + "9699a293-8acc-43c7-87ee-a31e454606f4", + ), + unique_name: UniqueName( + "f1ea07aa-15e8-4760-ac43-9918517f6014", + ), + product_variant_id: ProductVariantId( + 143, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 219, + ), + local_path: LocalPath( + "8e71b79f-2543-4963-a1dd-1bd9e31d89ef", + ), + file_name: FileName( + "669dbc98-44ac-405d-a6c2-22d9ea318217", + ), + unique_name: UniqueName( + "0e0254e5-5fb2-4005-941e-31fb9750d88a", + ), + product_variant_id: ProductVariantId( + 143, + ), + }, + ], + }, + DetailedProductVariant { + id: ProductVariantId( + 144, + ), + name: ProductName( + "e1892b73-7753-44ab-b294-4329fafd5d8f", + ), + short_description: ProductShortDesc( + "2b95d63c-bb37-445f-814e-3244fd6ea36a", + ), + long_description: ProductLongDesc( + "4c9589f3-5fa5-478a-976b-2c82333b0b50", + ), + price: Price( + NonNegative( + 0, + ), + ), + stocks: [ + Stock { + id: StockId( + 86, + ), + product_variant_id: ProductVariantId( + 144, + ), + quantity: Quantity( + NonNegative( + 345, + ), + ), + quantity_unit: Piece, + }, + ], + photos: [ + ProductLinkedPhoto { + photo_id: PhotoId( + 220, + ), + local_path: LocalPath( + "3a14ee12-5167-4319-9ff0-533b11f903bb", + ), + file_name: FileName( + "dc7e2524-6cfd-4114-b527-d86b50dbc76c", + ), + unique_name: UniqueName( + "9a0c64aa-1199-4ba2-8d28-bb1eca95e622", + ), + product_variant_id: ProductVariantId( + 144, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 221, + ), + local_path: LocalPath( + "8b151268-242a-40ac-8c6b-46bbd0c33d19", + ), + file_name: FileName( + "13f6efbb-0d50-45a7-b842-0c1a07ba44ce", + ), + unique_name: UniqueName( + "64175464-9242-45ad-9663-6a88799d921e", + ), + product_variant_id: ProductVariantId( + 144, + ), + }, + ProductLinkedPhoto { + photo_id: PhotoId( + 222, + ), + local_path: LocalPath( + "5d1e6c63-5944-4222-846a-a41159648d81", + ), + file_name: FileName( + "e81a611d-c050-4158-8fa1-6b29e285d792", + ), + unique_name: UniqueName( + "1b36c223-324a-45d2-839b-04fe1ce628da", + ), + product_variant_id: ProductVariantId( + 144, + ), + }, + ], + }, + ], + }, + ], +} diff --git a/crates/stock_manager/src/db/mod.rs b/crates/stock_manager/src/db/mod.rs index c16862e..fc9a25a 100644 --- a/crates/stock_manager/src/db/mod.rs +++ b/crates/stock_manager/src/db/mod.rs @@ -8,6 +8,12 @@ mod product_variants; mod products; mod stocks; +pub use photos::*; +pub use product_photos::*; +pub use product_variants::*; +pub use products::*; +pub use stocks::*; + #[derive(Clone)] pub struct Database { pub pool: sqlx::PgPool, diff --git a/crates/stock_manager/src/db/photos.rs b/crates/stock_manager/src/db/photos.rs index ede9d5f..8c95132 100644 --- a/crates/stock_manager/src/db/photos.rs +++ b/crates/stock_manager/src/db/photos.rs @@ -15,8 +15,8 @@ pub type Result = std::result::Result; #[derive(Debug)] pub struct AllPhotos { - pub limit: i32, - pub offset: i32, + pub limit: Limit, + pub offset: Offset, } impl AllPhotos { @@ -208,8 +208,8 @@ mod tests { let p3 = test_photo(&mut t, None, None, None).await; let all = AllPhotos { - limit: 1000, - offset: 0, + limit: Limit::from_u32(1000), + offset: Offset::from_u32(0), } .run(&mut t) .await diff --git a/crates/stock_manager/src/db/product_photos.rs b/crates/stock_manager/src/db/product_photos.rs index 5949216..7a0b798 100644 --- a/crates/stock_manager/src/db/product_photos.rs +++ b/crates/stock_manager/src/db/product_photos.rs @@ -1,8 +1,6 @@ use db_utils::PgT; use model::v2::*; -use crate::db::products::AllProducts; - #[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, thiserror::Error)] pub enum Error { #[error("Failed to attach photo to product")] @@ -17,8 +15,8 @@ pub type Result = std::result::Result; #[derive(Debug)] pub struct AllProductPhotos { - pub limit: Option, - pub offset: Option, + pub limit: Option, + pub offset: Option, } impl AllProductPhotos { @@ -178,6 +176,7 @@ mod tests { let deleted = DeleteProductPhoto { id: p2.id }.run(&mut t).await.unwrap(); testx::db_rollback!(t); + assert_ne!(deleted, Some(p1)); assert_eq!(deleted, Some(p2)); assert_ne!(deleted, Some(p3)); diff --git a/crates/stock_manager/src/db/product_variants.rs b/crates/stock_manager/src/db/product_variants.rs index 0d8101d..d8a3874 100644 --- a/crates/stock_manager/src/db/product_variants.rs +++ b/crates/stock_manager/src/db/product_variants.rs @@ -1,7 +1,7 @@ use db_utils::PgT; use model::v2::*; -#[derive(Debug, thiserror::Error)] +#[derive(Debug, PartialEq, thiserror::Error)] pub enum Error { #[error("Failed to create product")] CreateProductVariant, @@ -59,8 +59,8 @@ RETURNING id, #[derive(Debug)] pub struct ProductsVariants { pub product_ids: Vec, - pub limit: Option, - pub offset: Option, + pub limit: Option, + pub offset: Option, } impl ProductsVariants { @@ -79,14 +79,21 @@ INNER JOIN products ps ON pv.product_id = ps.id WHERE "#, - "products.id = ", + "ps.id = ", + ) + .with_padding( + self.limit.map(|n| **n).unwrap_or(200), + *self.offset.map(|n| *n).unwrap_or_default(), ) .allow_over_max() .with_size(1000) .load( self.product_ids.len(), self.product_ids.iter().copied().map(|id| *id), - |_| Error::ProductsVariants(self.product_ids.clone()), + |e| { + tracing::error!("{}", e); + Error::ProductsVariants(self.product_ids.clone()) + }, ) .await } @@ -111,3 +118,96 @@ WHERE id = $1 .map_err(|_e| Error::DeleteProductVariant(self.product_variant_id)) } } + +#[cfg(test)] +mod tests { + use config::UpdateConfig; + use db_utils::PgT; + use model::v2::*; + use uuid::Uuid; + + use super::*; + use crate::db::*; + + #[derive(Debug)] + pub struct NoOpts; + impl UpdateConfig for NoOpts {} + + async fn test_product_variant(product_id: ProductId, t: &mut PgT<'_>) -> ProductVariant { + CreateProductVariant { + product_id, + name: ProductName::new(format!("{}", Uuid::new_v4())), + short_description: ProductShortDesc::new(format!("{}", Uuid::new_v4())), + long_description: ProductLongDesc::new(format!("{}", Uuid::new_v4())), + price: Default::default(), + } + .run(t) + .await + .unwrap() + } + + async fn test_product( + t: &mut PgT<'_>, + name: Option, + category: Option, + deliver_days_flag: Option, + ) -> Product { + CreateProduct { + name: ProductName::new(name.unwrap_or_else(|| format!("{}", Uuid::new_v4()))), + category, + deliver_days_flag: deliver_days_flag + .unwrap_or_else(|| Days(vec![Day::Friday, Day::Sunday])), + } + .run(t) + .await + .unwrap() + } + + #[tokio::test] + async fn create() { + testx::db_t_ref!(t); + + let product = test_product(&mut t, None, None, None).await; + let dbm = CreateProductVariant { + product_id: product.id, + name: ProductName::new(format!("{}", Uuid::new_v4())), + short_description: ProductShortDesc::new("aosdjajsodjaoisdjoajs"), + long_description: ProductLongDesc::new("jsa a98dh 9ahsd ha89shd 98aus 98asu "), + price: Default::default(), + }; + let res = dbm.run(&mut t).await; + + testx::db_rollback!(t); + + res.unwrap(); + } + + #[tokio::test] + async fn all() { + testx::db_t_ref!(t); + + let product = test_product(&mut t, None, None, None).await; + let variant = test_product_variant(product.id, &mut t).await; + + let res1 = ProductsVariants { + product_ids: vec![product.id], + limit: Some(200.into()), + offset: Some(0.into()), + } + .run(&mut t) + .await; + + let res2 = ProductsVariants { + product_ids: vec![product.id], + limit: Some(2.into()), + offset: Some(100.into()), + } + .run(&mut t) + .await; + + testx::db_rollback!(t); + + assert_eq!(res1, Ok(vec![variant])); + assert_eq!(res2, Ok(vec![])); + } +} diff --git a/crates/stock_manager/src/db/products.rs b/crates/stock_manager/src/db/products.rs index a76c142..11252f0 100644 --- a/crates/stock_manager/src/db/products.rs +++ b/crates/stock_manager/src/db/products.rs @@ -1,5 +1,6 @@ +use db_utils::PgT; use model::v2::*; -use model::ShoppingCartId; +use model::{Ranged, ShoppingCartId}; #[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, thiserror::Error)] pub enum Error { @@ -23,8 +24,8 @@ pub type Result = std::result::Result; #[derive(Debug)] pub struct AllProducts { - limit: i32, - offset: i32, + pub limit: Limit, + pub offset: Offset, } impl AllProducts { @@ -192,15 +193,12 @@ RETURNING id, #[derive(Debug)] pub struct ShoppingCartProducts { pub shopping_cart_id: ShoppingCartId, - pub limit: i32, - pub offset: i32, + pub limit: Limit, + pub offset: Offset, } impl ShoppingCartProducts { - pub async fn shopping_cart_products<'e, E>(self, pool: E) -> Result> - where - E: sqlx::Executor<'e, Database = sqlx::Postgres>, - { + pub async fn run(self, t: &mut PgT<'_>) -> Result> { sqlx::query_as( r#" SELECT products.id, @@ -215,9 +213,9 @@ LIMIT $2 OFFSET $3 "#, ) .bind(self.shopping_cart_id) - .bind(self.limit.min(1).max(200)) - .bind(self.offset.min(0)) - .fetch_all(pool) + .bind(self.limit.in_range(1..200)) + .bind(self.offset.in_range(0..u32::MAX)) + .fetch_all(t) .await .map_err(|e| { tracing::error!("{e:?}"); @@ -280,10 +278,7 @@ mod tests { async fn test_product( t: &mut sqlx::Transaction<'_, sqlx::Postgres>, name: Option, - short_description: Option, - long_description: Option, category: Option, - price: Option, deliver_days_flag: Option, ) -> Product { CreateProduct { @@ -301,7 +296,7 @@ mod tests { async fn create() { testx::db_t_ref!(t); - test_product(&mut t, None, None, None, None, None, None).await; + test_product(&mut t, None, None, None).await; testx::db_rollback!(t); } @@ -310,13 +305,13 @@ mod tests { async fn all() { testx::db_t_ref!(t); - let p1 = test_product(&mut t, None, None, None, None, None, None).await; - let p2 = test_product(&mut t, None, None, None, None, None, None).await; - let p3 = test_product(&mut t, None, None, None, None, None, None).await; + let p1 = test_product(&mut t, None, None, None).await; + let p2 = test_product(&mut t, None, None, None).await; + let p3 = test_product(&mut t, None, None, None).await; let products = AllProducts { - limit: 10000, - offset: 0, + limit: 10000.into(), + offset: 0.into(), } .run(&mut t) .await @@ -330,13 +325,11 @@ mod tests { async fn find() { testx::db_t_ref!(t); - let p1 = test_product(&mut t, None, None, None, None, None, None).await; - let p2 = test_product(&mut t, None, None, None, None, None, None).await; - let p3 = test_product(&mut t, None, None, None, None, None, None).await; + let p1 = test_product(&mut t, None, None, None).await; + let p2 = test_product(&mut t, None, None, None).await; + let p3 = test_product(&mut t, None, None, None).await; - let product = find_product(FindProduct { product_id: p2.id }, &mut t) - .await - .unwrap(); + let product = FindProduct { product_id: p2.id }.run(&mut t).await.unwrap(); testx::db_rollback!(t); assert_ne!(product, p1); @@ -348,7 +341,7 @@ mod tests { async fn update() { testx::db_t_ref!(t); - let original = test_product(&mut t, None, None, None, None, None, None).await; + let original = test_product(&mut t, None, None, None).await; let updated = UpdateProduct { id: original.id, name: ProductName::new("a9s0dja0sjd0jas09dj"), diff --git a/crates/stock_manager/src/db/stocks.rs b/crates/stock_manager/src/db/stocks.rs index 27891da..14c1085 100644 --- a/crates/stock_manager/src/db/stocks.rs +++ b/crates/stock_manager/src/db/stocks.rs @@ -1,7 +1,5 @@ use model::v2::*; -use crate::db::products::AllProducts; - #[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, thiserror::Error)] pub enum Error { #[error("Unable to load all stocks")] @@ -104,7 +102,7 @@ RETURNING id, product_variant_id, quantity, quantity_unit #[derive(Debug)] pub struct UpdateStock { pub id: StockId, - pub product_id: ProductId, + pub product_variant_id: ProductVariantId, pub quantity: Quantity, pub quantity_unit: QuantityUnit, } @@ -121,7 +119,7 @@ WHERE id = $4 RETURNING id, product_variant_id, quantity, quantity_unit "#, ) - .bind(self.product_id) + .bind(self.product_variant_id) .bind(self.quantity) .bind(self.quantity_unit) .bind(self.id) @@ -164,7 +162,7 @@ pub struct ProductVariantsStock { } impl ProductVariantsStock { - async fn run(self, pool: &mut sqlx::Transaction<'_, sqlx::Postgres>) -> Result> { + pub async fn run(self, pool: &mut sqlx::Transaction<'_, sqlx::Postgres>) -> Result> { db_utils::MultiLoad::new( pool, r#" @@ -187,12 +185,12 @@ WHERE #[cfg(test)] mod tests { use config::UpdateConfig; - use fake::faker::lorem::en as lorem; - use fake::Fake; + use db_utils::PgT; use model::v2::*; use model::Day; use uuid::Uuid; + use crate::db::product_variants::CreateProductVariant; use crate::db::Database; pub struct NoOpts; @@ -213,6 +211,19 @@ mod tests { .unwrap() } + async fn test_product_variant(product_id: ProductId, t: &mut PgT<'_>) -> ProductVariant { + CreateProductVariant { + product_id, + name: ProductName::new(format!("{}", Uuid::new_v4())), + short_description: ProductShortDesc::new(format!("{}", Uuid::new_v4())), + long_description: ProductLongDesc::new(format!("{}", Uuid::new_v4())), + price: Default::default(), + } + .run(t) + .await + .unwrap() + } + async fn test_stock( pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, product_variant_id: Option, @@ -221,7 +232,10 @@ mod tests { ) -> Stock { let product_variant_id = match product_variant_id { Some(id) => id, - _ => test_product(&mut *pool).await.id, + _ => { + let product_id = test_product(&mut *pool).await.id; + test_product_variant(product_id, &mut *pool).await.id + } }; let quantity = quantity.unwrap_or_else(|| Quantity::from_u32(345)); let quantity_unit = quantity_unit.unwrap_or(QuantityUnit::Piece); @@ -253,7 +267,7 @@ mod tests { let second = test_stock(&mut t, None, None, None).await; let stocks: Vec = ProductVariantsStock { - product_variant_ids: vec![first.product_id, second.product_id], + product_variant_ids: vec![first.product_variant_id, second.product_variant_id], } .run(&mut t) .await @@ -289,18 +303,19 @@ mod tests { let first = test_stock(&mut t, None, None, None).await; let second = test_stock(&mut t, None, None, None).await; + let second_id = second.id; let deleted: Option = DeleteStock { - stock_id: second.id, + stock_id: second_id, } .run(&mut t) .await .unwrap(); - let reloaded = FindStock { id: second.id }.run(&mut t).await; + let reloaded = FindStock { id: second_id }.run(&mut t).await; testx::db_rollback!(t); assert_eq!(deleted, Some(second)); assert_ne!(deleted, Some(first)); - assert_eq!(reloaded, Err(super::Error::NotFound)); + assert_eq!(reloaded, Err(super::Error::NotFound(second_id))); } #[tokio::test] @@ -310,10 +325,11 @@ mod tests { let first = test_stock(&mut t, None, None, None).await; let second = test_stock(&mut t, None, None, None).await; let another_product = test_product(&mut t).await; + let another_product_variant = test_product_variant(another_product.id, &mut t).await; let updated: Stock = UpdateStock { id: second.id, - product_id: another_product.id, + product_variant_id: another_product_variant.id, quantity: Quantity::from_u32(19191), quantity_unit: QuantityUnit::Gram, } @@ -327,7 +343,7 @@ mod tests { updated, Stock { id: second.id, - product_id: another_product.id, + product_variant_id: another_product_variant.id, quantity: Quantity::from_u32(19191), quantity_unit: QuantityUnit::Gram, } diff --git a/crates/token_manager/Cargo.toml b/crates/token_manager/Cargo.toml index 7a18771..6b7bf6f 100644 --- a/crates/token_manager/Cargo.toml +++ b/crates/token_manager/Cargo.toml @@ -29,4 +29,5 @@ tracing = { version = "0.1.34" } uuid = { version = "1.2.1", features = ["serde"] } [dev-dependencies] +fake = { version = "2.5.0" } testx = { path = "../testx" } diff --git a/scripts/migrate.sh b/scripts/migrate.sh index 63630b7..5e137f7 100755 --- a/scripts/migrate.sh +++ b/scripts/migrate.sh @@ -2,6 +2,13 @@ source .env +if [[ "$1" == "purge" ]]; +then + psql postgres postgres -c "DROP DATABASE ${DATABASE_NAME}_accounts" || echo 0 + psql postgres postgres -c "DROP DATABASE ${DATABASE_NAME}_carts" || echo 0 + psql postgres postgres -c "DROP DATABASE ${DATABASE_NAME}_stocks" || echo 0 +fi + psql postgres postgres -c "CREATE DATABASE ${DATABASE_NAME}_accounts" || echo 0 sqlx migrate run -D "${ACCOUNT_DATABASE_URL}" --source ./crates/account_manager/migrations