Working tests, test detailed products

This commit is contained in:
Adrian Woźniak 2022-11-11 16:32:07 +01:00
parent 9c1b13bfb7
commit c7e9b25b97
No known key found for this signature in database
GPG Key ID: 0012845A89C7352B
40 changed files with 2793 additions and 331 deletions

80
Cargo.lock generated
View File

@ -830,12 +830,11 @@ dependencies = [
name = "cart_manager" name = "cart_manager"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"actix 0.13.0",
"actix-rt",
"channels", "channels",
"chrono", "chrono",
"config", "config",
"dotenv", "dotenv",
"fake",
"futures 0.3.25", "futures 0.3.25",
"model", "model",
"opentelemetry 0.17.0", "opentelemetry 0.17.0",
@ -846,6 +845,7 @@ dependencies = [
"sqlx", "sqlx",
"sqlx-core", "sqlx-core",
"tarpc", "tarpc",
"testx",
"thiserror", "thiserror",
"tokio", "tokio",
"tracing", "tracing",
@ -967,6 +967,19 @@ dependencies = [
"tracing", "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]] [[package]]
name = "console_error_panic_hook" name = "console_error_panic_hook"
version = "0.1.7" version = "0.1.7"
@ -1385,6 +1398,7 @@ dependencies = [
"chrono", "chrono",
"config", "config",
"dotenv", "dotenv",
"fake",
"handlebars", "handlebars",
"model", "model",
"opentelemetry 0.17.0", "opentelemetry 0.17.0",
@ -1395,6 +1409,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"tarpc", "tarpc",
"testx",
"thiserror", "thiserror",
"tokio", "tokio",
"tracing", "tracing",
@ -1409,6 +1424,12 @@ version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1056f553da426e9c025a662efa48b52e62e0a3a7648aa2d15aeaaf7f0d329357" checksum = "1056f553da426e9c025a662efa48b52e62e0a3a7648aa2d15aeaaf7f0d329357"
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.31" version = "0.8.31"
@ -2239,6 +2260,19 @@ dependencies = [
"generic-array", "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]] [[package]]
name = "instant" name = "instant"
version = "0.1.12" version = "0.1.12"
@ -2446,6 +2480,12 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]] [[package]]
name = "local-channel" name = "local-channel"
version = "0.1.3" version = "0.1.3"
@ -2962,10 +3002,12 @@ dependencies = [
"chrono", "chrono",
"config", "config",
"database_manager", "database_manager",
"fake",
"model", "model",
"pretty_env_logger", "pretty_env_logger",
"rumqttc", "rumqttc",
"serde", "serde",
"testx",
"thiserror", "thiserror",
"tracing", "tracing",
"uuid 1.2.1", "uuid 1.2.1",
@ -3094,6 +3136,7 @@ dependencies = [
"config", "config",
"database_manager", "database_manager",
"derive_more", "derive_more",
"fake",
"model", "model",
"parking_lot 0.12.1", "parking_lot 0.12.1",
"pay_u", "pay_u",
@ -3678,6 +3721,7 @@ dependencies = [
"config", "config",
"derive_more", "derive_more",
"dotenv", "dotenv",
"fake",
"futures 0.3.25", "futures 0.3.25",
"model", "model",
"opentelemetry 0.17.0", "opentelemetry 0.17.0",
@ -3688,6 +3732,7 @@ dependencies = [
"serde", "serde",
"sonic-channel", "sonic-channel",
"tarpc", "tarpc",
"testx",
"thiserror", "thiserror",
"tokio", "tokio",
"tracing", "tracing",
@ -3897,6 +3942,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "similar"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62ac7f900db32bf3fd12e0117dd3dc4da74bc52ebaac97f39668446d89694803"
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "0.3.10" version = "0.3.10"
@ -4108,6 +4159,7 @@ dependencies = [
"dotenv", "dotenv",
"fake", "fake",
"futures 0.3.25", "futures 0.3.25",
"insta",
"model", "model",
"opentelemetry 0.17.0", "opentelemetry 0.17.0",
"opentelemetry-jaeger", "opentelemetry-jaeger",
@ -4161,9 +4213,9 @@ dependencies = [
[[package]] [[package]]
name = "tarpc" name = "tarpc"
version = "0.30.0" version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dd84a0fdd485d04b67be6009a04603489c8cb00ade830e4dd2e3660bef855b1" checksum = "71a98cc1a0a9013e8df3900d09c597dd65cfc6ea4d42968629b1b9ea949acf8f"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"fnv", "fnv",
@ -4249,6 +4301,16 @@ dependencies = [
"winapi-util", "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]] [[package]]
name = "testx" name = "testx"
version = "0.1.0" version = "0.1.0"
@ -4379,6 +4441,7 @@ dependencies = [
"config", "config",
"database_manager", "database_manager",
"derive_more", "derive_more",
"fake",
"futures 0.3.25", "futures 0.3.25",
"futures-util", "futures-util",
"hmac", "hmac",
@ -5244,6 +5307,15 @@ dependencies = [
"winapi-build", "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]] [[package]]
name = "zstd" name = "zstd"
version = "0.11.2+zstd.1.5.2" version = "0.11.2+zstd.1.5.2"

View File

@ -16,7 +16,7 @@ dotenv = { version = "0.15.0" }
futures = { version = "0.3.25" } futures = { version = "0.3.25" }
gumdrop = { version = "0.8.1" } gumdrop = { version = "0.8.1" }
json = { version = "0.12.4" } json = { version = "0.12.4" }
model = { path = "../model" } model = { path = "../model", features = ['db'] }
opentelemetry = { version = "0.17.0" } opentelemetry = { version = "0.17.0" }
opentelemetry-jaeger = { version = "0.17.0" } opentelemetry-jaeger = { version = "0.17.0" }
pretty_env_logger = { version = "0.4", features = [] } pretty_env_logger = { version = "0.4", features = [] }
@ -24,7 +24,7 @@ rumqttc = { version = "*" }
serde = { version = "1.0.137", features = ["derive"] } serde = { version = "1.0.137", features = ["derive"] }
sqlx = { version = "0.6.2", features = ["migrate", "runtime-actix-rustls", "all-types", "postgres"] } sqlx = { version = "0.6.2", features = ["migrate", "runtime-actix-rustls", "all-types", "postgres"] }
sqlx-core = { version = "0.6.2", features = [] } 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" } thiserror = { version = "1.0.31" }
tokio = { version = "1.21.2", features = ['full'] } tokio = { version = "1.21.2", features = ['full'] }
tracing = { version = "0.1.6" } tracing = { version = "0.1.6" }

View File

@ -12,7 +12,7 @@ CREATE TYPE "Role" AS ENUM (
); );
CREATE TABLE public.accounts ( CREATE TABLE public.accounts (
id integer NOT NULL, id serial NOT NULL,
email character varying NOT NULL, email character varying NOT NULL,
login character varying NOT NULL, login character varying NOT NULL,
pass_hash character varying NOT NULL, pass_hash character varying NOT NULL,

View File

@ -1,5 +1,5 @@
CREATE TABLE public.account_addresses ( CREATE TABLE public.account_addresses (
id integer NOT NULL, id serial NOT NULL,
name text NOT NULL, name text NOT NULL,
email text NOT NULL, email text NOT NULL,
street text NOT NULL, street text NOT NULL,

View File

@ -17,19 +17,25 @@ pub enum Error {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct AllAccounts; pub struct AllAccounts {
pub limit: i32,
pub offset: i32,
}
impl AllAccounts { impl AllAccounts {
pub async fn run( pub async fn run(
_msg: AllAccounts, self,
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Vec<FullAccount>> { ) -> Result<Vec<FullAccount>> {
sqlx::query_as( sqlx::query_as(
r#" r#"
SELECT id, email, login, pass_hash, role, customer_id, state SELECT id, email, login, pass_hash, role, customer_id, state
FROM accounts FROM accounts
LIMIT $1 OFFSET $2
"#, "#,
) )
.bind(self.limit)
.bind(self.offset)
.fetch_all(pool) .fetch_all(pool)
.await .await
.map_err(|e| { .map_err(|e| {
@ -207,6 +213,7 @@ mod tests {
use model::*; use model::*;
use super::*; use super::*;
use crate::db::Database;
pub struct NoOpts; 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;
test_create_account(&mut t, None, None, None).await; test_create_account(&mut t, None, None, None).await;
let v: Vec<FullAccount> = AllAccounts.run(&mut t).await.unwrap(); let v: Vec<FullAccount> = AllAccounts {
limit: 200,
offset: 0,
}
.run(&mut t)
.await
.unwrap();
testx::db_rollback!(t); testx::db_rollback!(t);
assert!(v.len() >= 3); assert!(v.len() >= 3);

View File

@ -194,6 +194,7 @@ mod test {
use super::super::accounts::CreateAccount; use super::super::accounts::CreateAccount;
use super::*; use super::*;
use crate::db::Database;
pub struct NoOpts; pub struct NoOpts;
@ -267,13 +268,11 @@ mod test {
address address
}; };
let found = super::find_account_address( let found = FindAccountAddress {
FindAccountAddress { account_id: account.id,
account_id: account.id, address_id: address.id,
address_id: address.id, }
}, .run(&mut t)
&mut t,
)
.await .await
.unwrap(); .unwrap();

View File

@ -43,7 +43,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = [] } serde_json = { version = "1.0", features = [] }
sqlx = { version = "0.6.2", features = ["migrate", "runtime-actix-rustls", "all-types", "postgres"] } sqlx = { version = "0.6.2", features = ["migrate", "runtime-actix-rustls", "all-types", "postgres"] }
sqlx-core = { version = "0.6.2", features = [] } 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 = [] } tera = { version = "1.15", features = [] }
thiserror = { version = "1.0", features = [] } thiserror = { version = "1.0", features = [] }
token_manager = { path = "../token_manager" } token_manager = { path = "../token_manager" }

View File

@ -8,14 +8,12 @@ name = "cart-manager"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
actix = { version = "0.13", features = [] }
actix-rt = { version = "2.7", features = [] }
channels = { path = "../channels" } channels = { path = "../channels" }
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
config = { path = "../config" } config = { path = "../config" }
dotenv = { version = "0.15.0" } dotenv = { version = "0.15.0" }
futures = { version = "0.3.25" } futures = { version = "0.3.25" }
model = { path = "../model" } model = { path = "../model", features = ["db"] }
opentelemetry = { version = "0.17.0" } opentelemetry = { version = "0.17.0" }
opentelemetry-jaeger = { version = "0.17.0" } opentelemetry-jaeger = { version = "0.17.0" }
pretty_env_logger = { version = "0.4", features = [] } pretty_env_logger = { version = "0.4", features = [] }
@ -23,10 +21,14 @@ rumqttc = { version = "*" }
serde = { version = "1.0.137", features = ["derive"] } serde = { version = "1.0.137", features = ["derive"] }
sqlx = { version = "0.6.2", features = ["migrate", "runtime-actix-rustls", "all-types", "postgres"] } sqlx = { version = "0.6.2", features = ["migrate", "runtime-actix-rustls", "all-types", "postgres"] }
sqlx-core = { version = "0.6.2", features = [] } 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" } thiserror = { version = "1.0.31" }
tokio = { version = "1.21.2", features = ['full'] } tokio = { version = "1.21.2", features = ['full'] }
tracing = { version = "0.1.37" } tracing = { version = "0.1.37" }
tracing-opentelemetry = { version = "0.17.4" } tracing-opentelemetry = { version = "0.17.4" }
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } 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" }

View File

@ -16,7 +16,7 @@ CREATE TYPE "QuantityUnit" AS ENUM (
); );
CREATE TABLE shopping_carts ( CREATE TABLE shopping_carts (
id integer NOT NULL, id serial NOT NULL,
buyer_id integer NOT NULL, buyer_id integer NOT NULL,
payment_method "PaymentMethod" DEFAULT 'payment_on_the_spot'::"PaymentMethod" NOT NULL, payment_method "PaymentMethod" DEFAULT 'payment_on_the_spot'::"PaymentMethod" NOT NULL,
state "ShoppingCartState" DEFAULT 'active'::"ShoppingCartState" NOT NULL, state "ShoppingCartState" DEFAULT 'active'::"ShoppingCartState" NOT NULL,
@ -24,7 +24,7 @@ CREATE TABLE shopping_carts (
); );
CREATE TABLE shopping_cart_items ( CREATE TABLE shopping_cart_items (
id integer NOT NULL, id serial NOT NULL,
product_id integer NOT NULL, product_id integer NOT NULL,
shopping_cart_id integer, shopping_cart_id integer,
quantity integer DEFAULT 0 NOT NULL, quantity integer DEFAULT 0 NOT NULL,

View File

@ -366,9 +366,9 @@ RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use config::UpdateConfig; use config::UpdateConfig;
use fake::Fake;
use model::*; use crate::db::shopping_carts::*;
use uuid::Uuid; use crate::db::Database;
pub struct NoOpts; pub struct NoOpts;
@ -376,42 +376,6 @@ mod tests {
use super::*; 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<String>,
email: Option<String>,
hash: Option<String>,
) -> 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( async fn test_shopping_cart(
t: &mut sqlx::Transaction<'_, sqlx::Postgres>, t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
buyer_id: Option<AccountId>, buyer_id: Option<AccountId>,
@ -419,7 +383,7 @@ mod tests {
) -> ShoppingCart { ) -> ShoppingCart {
let buyer_id = match buyer_id { let buyer_id = match buyer_id {
Some(id) => id, Some(id) => id,
_ => test_account(&mut *t, None, None, None).await.id, _ => 1.into(),
}; };
sqlx::query( sqlx::query(
@ -469,7 +433,7 @@ WHERE buyer_id = $1
}; };
let product_id = match product_id { let product_id = match product_id {
Some(id) => id, Some(id) => id,
_ => test_product(&mut *t).await.id, _ => 1.into(),
}; };
CreateShoppingCartItem { CreateShoppingCartItem {
product_id, product_id,
@ -482,7 +446,7 @@ WHERE buyer_id = $1
.unwrap() .unwrap()
} }
#[actix::test] #[tokio::test]
async fn create() { async fn create() {
testx::db_t_ref!(t); testx::db_t_ref!(t);
@ -491,11 +455,11 @@ WHERE buyer_id = $1
testx::db_rollback!(t); testx::db_rollback!(t);
} }
#[actix::test] #[tokio::test]
async fn all() { async fn all() {
testx::db_t_ref!(t); 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); 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);
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) let all = AllShoppingCartItems.run(&mut t).await.unwrap();
.await
.unwrap();
testx::db_rollback!(t); testx::db_rollback!(t);
assert_eq!(all, items) assert_eq!(all, items)
} }
#[actix::test] #[tokio::test]
async fn account_cart_with_cart_id() { async fn account_cart_with_cart_id() {
testx::db_t_ref!(t); 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); 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;
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( let all = AccountShoppingCartItems {
AccountShoppingCartItems { account_id,
account_id, shopping_cart_id: Some(cart2.id),
shopping_cart_id: Some(cart2.id), }
}, .run(&mut t)
&mut t,
)
.await .await
.unwrap(); .unwrap();
@ -559,11 +519,11 @@ WHERE buyer_id = $1
assert_eq!(all, items) assert_eq!(all, items)
} }
#[actix::test] #[tokio::test]
async fn account_cart_without_cart_id() { async fn account_cart_without_cart_id() {
testx::db_t_ref!(t); 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); 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);
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( let all = AccountShoppingCartItems {
AccountShoppingCartItems { account_id,
account_id, shopping_cart_id: None,
shopping_cart_id: None, }
}, .run(&mut t)
&mut t,
)
.await .await
.unwrap(); .unwrap();
@ -596,23 +554,21 @@ WHERE buyer_id = $1
assert_eq!(all, items) assert_eq!(all, items)
} }
#[actix::test] #[tokio::test]
async fn update() { async fn update() {
testx::db_t_ref!(t); 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 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 item = test_shopping_cart_item(&mut t, Some(cart1.id), None).await;
let updated = update_shopping_cart_item( let updated = UpdateShoppingCartItem {
UpdateShoppingCartItem { id: item.id,
id: item.id, product_id: item.product_id,
product_id: item.product_id, shopping_cart_id: item.shopping_cart_id,
shopping_cart_id: item.shopping_cart_id, quantity: Quantity::from_u32(987979879),
quantity: Quantity::from_u32(987979879), quantity_unit: QuantityUnit::Kilogram,
quantity_unit: QuantityUnit::Kilogram, }
}, .run(&mut t)
&mut t,
)
.await .await
.unwrap(); .unwrap();

View File

@ -253,8 +253,8 @@ WHERE buyer_id = $1 AND state = 'active'
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use config::UpdateConfig; use config::UpdateConfig;
use fake::Fake;
use model::*; use crate::db::Database;
pub struct NoOpts; pub struct NoOpts;
@ -262,85 +262,53 @@ mod tests {
use super::*; use super::*;
async fn test_account(
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
login: Option<String>,
email: Option<String>,
hash: Option<String>,
) -> 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( async fn test_shopping_cart(
t: &mut sqlx::Transaction<'_, sqlx::Postgres>, t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
buyer_id: Option<AccountId>, buyer_id: Option<AccountId>,
) -> ShoppingCart { ) -> ShoppingCart {
let buyer_id = match buyer_id { let buyer_id = match buyer_id {
Some(id) => id, Some(id) => id,
_ => test_account(&mut *t, None, None, None).await.id, _ => 1.into(),
}; };
super::create_shopping_cart( CreateShoppingCart {
CreateShoppingCart { buyer_id,
buyer_id, payment_method: PaymentMethod::PaymentOnTheSpot,
payment_method: PaymentMethod::PaymentOnTheSpot, }
}, .run(t)
t,
)
.await .await
.unwrap() .unwrap()
} }
#[actix::test] #[tokio::test]
async fn create_shopping_cart() { async fn create_shopping_cart() {
testx::db_t_ref!(t); testx::db_t_ref!(t);
let account = test_account(&mut t, None, None, None).await; let cart = CreateShoppingCart {
buyer_id: 1.into(),
let cart = super::create_shopping_cart( payment_method: PaymentMethod::PaymentOnTheSpot,
CreateShoppingCart { }
buyer_id: account.id, .run(&mut t)
payment_method: PaymentMethod::PaymentOnTheSpot,
},
&mut t,
)
.await; .await;
testx::db_rollback!(t); testx::db_rollback!(t);
assert!(cart.is_ok()); assert!(cart.is_ok());
} }
#[actix::test] #[tokio::test]
async fn update_shopping_cart() { async fn update_shopping_cart() {
testx::db_t_ref!(t); 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 = UpdateShoppingCart {
id: original.id,
let cart = super::update_shopping_cart( buyer_id: 1.into(),
UpdateShoppingCart { payment_method: PaymentMethod::PayU,
id: original.id, state: ShoppingCartState::Closed,
buyer_id: account.id, checkout_notes: Some("Foo bar".into()),
payment_method: PaymentMethod::PayU, }
state: ShoppingCartState::Closed, .run(&mut t)
checkout_notes: Some("Foo bar".into()),
},
&mut t,
)
.await .await
.unwrap(); .unwrap();
@ -350,7 +318,7 @@ mod tests {
cart, cart,
ShoppingCart { ShoppingCart {
id: original.id, id: original.id,
buyer_id: account.id, buyer_id: 1.into(),
payment_method: PaymentMethod::PayU, payment_method: PaymentMethod::PayU,
state: ShoppingCartState::Closed, state: ShoppingCartState::Closed,
checkout_notes: Some("Foo bar".into()) checkout_notes: Some("Foo bar".into())
@ -358,29 +326,23 @@ mod tests {
); );
} }
#[actix::test] #[tokio::test]
async fn without_cart_ensure_shopping_cart() { async fn without_cart_ensure_shopping_cart() {
testx::db_t_ref!(t); testx::db_t_ref!(t);
let account = test_account(&mut t, None, None, None).await; let cart = EnsureActiveShoppingCart { buyer_id: 1.into() }
.run(&mut t)
let cart = super::ensure_active_shopping_cart( .await
EnsureActiveShoppingCart { .unwrap();
buyer_id: account.id,
},
&mut t,
)
.await
.unwrap();
let id = cart.id; let id = cart.id;
testx::db_rollback!(t); testx::db_rollback!(t);
assert_eq!( assert_eq!(
cart, cart,
model::ShoppingCart { ShoppingCart {
id, id,
buyer_id: account.id, buyer_id: 1.into(),
payment_method: Default::default(), payment_method: Default::default(),
state: ShoppingCartState::Active, state: ShoppingCartState::Active,
checkout_notes: None checkout_notes: None
@ -388,34 +350,26 @@ mod tests {
); );
} }
#[actix::test] #[tokio::test]
async fn with_inactive_cart_ensure_shopping_cart() { async fn with_inactive_cart_ensure_shopping_cart() {
testx::db_t_ref!(t); 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 _ = UpdateShoppingCart {
let original = test_shopping_cart(&mut t, Some(account.id)).await; id: original.id,
let _ = super::update_shopping_cart( buyer_id: 1.into(),
UpdateShoppingCart { payment_method: Default::default(),
id: original.id, state: ShoppingCartState::Closed,
buyer_id: account.id, checkout_notes: None,
payment_method: Default::default(), }
state: ShoppingCartState::Closed, .run(&mut t)
checkout_notes: None,
},
&mut t,
)
.await .await
.unwrap(); .unwrap();
let cart = super::ensure_active_shopping_cart( let cart = EnsureActiveShoppingCart { buyer_id: 1.into() }
EnsureActiveShoppingCart { .run(&mut t)
buyer_id: account.id, .await
}, .unwrap();
&mut t,
)
.await
.unwrap();
testx::db_rollback!(t); testx::db_rollback!(t);
assert_ne!(original, cart); assert_ne!(original, cart);

View File

@ -14,7 +14,7 @@ pub struct Opts {}
impl UpdateConfig for Opts {} impl UpdateConfig for Opts {}
#[actix::main] #[tokio::main]
async fn main() { async fn main() {
dotenv::dotenv().ok(); dotenv::dotenv().ok();
init_tracing("account-manager"); init_tracing("account-manager");

View File

@ -19,7 +19,7 @@ futures = { version = "0.3.25" }
model = { path = "../model" } model = { path = "../model" }
rumqttc = { version = "0.17.0" } rumqttc = { version = "0.17.0" }
serde = { version = "*", features = ['derive'] } 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" } thiserror = { version = "1.0.37" }
tokio = { version = "1.21.2", features = ['full'] } tokio = { version = "1.21.2", features = ['full'] }
tracing = { version = "0.1.37" } tracing = { version = "0.1.37" }

View File

@ -1,7 +1,7 @@
pub mod detailed_product { pub mod detailed_product {
use model::v2::*; use model::v2::*;
use crate::stocks::Error; pub use crate::stocks::Error;
#[derive(Debug, serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Input { pub struct Input {
@ -21,10 +21,13 @@ pub mod detailed_product {
pub mod detailed_products { pub mod detailed_products {
use model::v2::*; use model::v2::*;
use crate::stocks::Error; pub use crate::stocks::Error;
#[derive(Debug, serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Input {} pub struct Input {
pub limit: Limit,
pub offset: Offset,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Details { pub struct Details {

View File

@ -5,6 +5,8 @@ pub mod product_stock;
pub mod product_variant; pub mod product_variant;
pub use load::*; pub use load::*;
use model::v2::ProductVariantId;
use model::ProductId;
pub use product::*; pub use product::*;
pub use product_photo::*; pub use product_photo::*;
pub use product_stock::*; pub use product_stock::*;
@ -13,7 +15,18 @@ pub use product_variant::*;
pub static CLIENT_NAME: &str = "stocks"; pub static CLIENT_NAME: &str = "stocks";
#[derive(Debug, thiserror::Error, serde::Serialize, serde::Deserialize)] #[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<ProductId>),
#[error("Failed to load photos for products variants {0:?}")]
VariantStocks(Vec<ProductVariantId>),
#[error("Failed to load stocks for products variants {0:?}")]
VariantPhotos(Vec<ProductVariantId>),
}
pub mod rpc { pub mod rpc {
use config::SharedAppConfig; use config::SharedAppConfig;

View File

@ -4,6 +4,6 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
model = { path = "../model" } model = { path = "../model", features = ["db"] }
sqlx = { version = "0.6.2", features = ["migrate", "runtime-actix-rustls", "all-types", "postgres"] } sqlx = { version = "0.6.2", features = ["migrate", "runtime-actix-rustls", "all-types", "postgres"] }
sqlx-core = { version = "0.6.2", features = [] } sqlx-core = { version = "0.6.2", features = [] }

View File

@ -2,10 +2,16 @@ use sqlx::Arguments;
pub type PgT<'l> = sqlx::Transaction<'l, sqlx::Postgres>; 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> { pub struct MultiLoad<'transaction, 'transaction2, 'header, 'condition, T> {
pool: &'transaction mut sqlx::Transaction<'transaction2, sqlx::Postgres>, pool: &'transaction mut sqlx::Transaction<'transaction2, sqlx::Postgres>,
header: &'header str, header: &'header str,
condition: &'condition str, condition: &'condition str,
padding: Option<Padding>,
sort: Option<String>, sort: Option<String>,
size: Option<usize>, size: Option<usize>,
allow_over_max: bool, allow_over_max: bool,
@ -26,6 +32,7 @@ where
pool, pool,
header, header,
condition, condition,
padding: None,
sort: None, sort: None,
size: Some(200), size: Some(200),
allow_over_max: false, allow_over_max: false,
@ -48,6 +55,11 @@ where
self 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>( pub async fn load<'query, Error, ErrorFn, Ids>(
&mut self, &mut self,
len: usize, len: usize,
@ -77,11 +89,13 @@ where
}, },
) { ) {
let query: String = self.header.into(); let query: String = self.header.into();
let mut arg_cursor = 0;
let mut query = ids.iter().enumerate().fold(query, |mut q, (idx, _id)| { let mut query = ids.iter().enumerate().fold(query, |mut q, (idx, _id)| {
if idx != 0 { if idx != 0 {
q.push_str(" OR"); q.push_str(" OR");
} }
q.push_str(&format!(" {} ${}", self.condition, idx + 1)); q.push_str(&format!(" {} ${}", self.condition, idx + 1));
arg_cursor = idx;
q q
}); });
if let Some(s) = self.sort.as_deref() { if let Some(s) = self.sort.as_deref() {
@ -89,14 +103,32 @@ where
query.push_str(s); query.push_str(s);
query.push(' '); query.push(' ');
} }
let q = sqlx::query_as_with( if self.padding.is_some() {
query.as_str(), 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() ids.into_iter()
.fold(sqlx::postgres::PgArguments::default(), |mut args, id| { .fold(sqlx::postgres::PgArguments::default(), |mut args, id| {
eprintln!("id = {:?}", id);
args.add(id); args.add(id);
args 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<T> = match q.fetch_all(&mut *self.pool).await { let records: Vec<T> = match q.fetch_all(&mut *self.pool).await {
Ok(rec) => rec, Ok(rec) => rec,

View File

@ -23,10 +23,14 @@ rumqttc = { version = "*" }
sendgrid = { version = "0.18.1", features = ["async"] } sendgrid = { version = "0.18.1", features = ["async"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = [] } 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" } thiserror = { version = "1.0.31" }
tokio = { version = "1.21.2", features = ['full'] } tokio = { version = "1.21.2", features = ['full'] }
tracing = { version = "0.1.37" } tracing = { version = "0.1.37" }
tracing-opentelemetry = { version = "0.17.4" } tracing-opentelemetry = { version = "0.17.4" }
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
uuid = { version = "0.8", features = ["serde"] } uuid = { version = "0.8", features = ["serde"] }
[dev-dependencies]
fake = { version = "2.5.0" }
testx = { path = "../testx" }

View File

@ -8,7 +8,7 @@ pub mod encrypt;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::ops; use std::ops;
use std::ops::BitOr; use std::ops::{BitOr, Range};
use std::str::FromStr; use std::str::FromStr;
use derive_more::{Deref, DerefMut, Display, From}; use derive_more::{Deref, DerefMut, Display, From};
@ -311,6 +311,89 @@ impl ops::Mul<Quantity> for Price {
} }
} }
pub trait Ranged: Sized + From<u32> + Copy {
fn in_range(self, range: Range<u32>) -> 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<u32> 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<u32> 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 = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))] #[cfg_attr(feature = "db", sqlx(transparent))]
@ -905,7 +988,7 @@ pub mod v2 {
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub use crate::{ 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, ProductLongDesc, ProductName, ProductPhotoId, ProductShortDesc, Quantity, QuantityUnit,
RecordId, StockId, UniqueName, RecordId, StockId, UniqueName,
}; };
@ -937,19 +1020,26 @@ pub mod v2 {
#[serde(transparent)] #[serde(transparent)]
pub struct ProductVariantId(RecordId); pub struct ProductVariantId(RecordId);
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct DetailedProduct { pub struct DetailedProductVariant {
pub id: ProductId, pub id: ProductVariantId,
pub name: ProductName, pub name: ProductName,
pub short_description: ProductShortDesc, pub short_description: ProductShortDesc,
pub long_description: ProductLongDesc, pub long_description: ProductLongDesc,
pub category: Option<ProductCategory>,
pub price: Price, pub price: Price,
pub deliver_days_flag: Days,
pub stocks: Vec<Stock>, pub stocks: Vec<Stock>,
pub photos: Vec<Photo>, pub photos: Vec<ProductLinkedPhoto>,
}
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct DetailedProduct {
pub id: ProductId,
pub name: ProductName,
pub category: Option<ProductCategory>,
pub deliver_days_flag: Days,
pub variants: Vec<DetailedProductVariant>,
} }
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]

View File

@ -9,10 +9,14 @@ actix-rt = { version = "2.7", features = [] }
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
config = { path = "../config" } config = { path = "../config" }
database_manager = { path = "../database_manager" } database_manager = { path = "../database_manager" }
model = { path = "../model" } model = { path = "../model", features = ["db"] }
pretty_env_logger = { version = "0.4", features = [] } pretty_env_logger = { version = "0.4", features = [] }
rumqttc = { version = "*" } rumqttc = { version = "*" }
serde = { version = "1.0.137", features = ["derive"] } serde = { version = "1.0.137", features = ["derive"] }
thiserror = { version = "1.0.31" } thiserror = { version = "1.0.31" }
tracing = { version = "0.1.34" } tracing = { version = "0.1.34" }
uuid = { version = "1.2.1", features = ["serde"] } uuid = { version = "1.2.1", features = ["serde"] }
[dev-dependencies]
fake = { version = "2.5.0" }
testx = { path = "../testx" }

View File

@ -10,7 +10,7 @@ chrono = { version = "0.4", features = ["serde"] }
config = { path = "../config" } config = { path = "../config" }
database_manager = { path = "../database_manager" } database_manager = { path = "../database_manager" }
derive_more = { version = "0.99", features = [] } derive_more = { version = "0.99", features = [] }
model = { path = "../model" } model = { path = "../model", features = ["db"] }
parking_lot = { version = "0.12", features = [] } parking_lot = { version = "0.12", features = [] }
pay_u = { version = '0.1', features = ["single-client"] } pay_u = { version = '0.1', features = ["single-client"] }
pretty_env_logger = { version = "0.4", features = [] } pretty_env_logger = { version = "0.4", features = [] }
@ -21,4 +21,5 @@ tracing = { version = "0.1.34" }
uuid = { version = "0.8", features = ["serde"] } uuid = { version = "0.8", features = ["serde"] }
[dev-dependencies] [dev-dependencies]
fake = { version = "2.5.0" }
testx = { path = "../testx" } testx = { path = "../testx" }

View File

@ -332,6 +332,6 @@ mod tests {
testx::db!(config, db); testx::db!(config, db);
let db = db.start(); let db = db.start();
let manager = PaymentManager::build(config, db).await; let _manager = PaymentManager::build(config, db).await;
} }
} }

View File

@ -16,7 +16,7 @@ config = { path = "../config" }
derive_more = { version = "0.99", features = [] } derive_more = { version = "0.99", features = [] }
dotenv = { version = "0.15.0" } dotenv = { version = "0.15.0" }
futures = { version = "0.3.25" } futures = { version = "0.3.25" }
model = { path = "../model" } model = { path = "../model", features = ["db"] }
opentelemetry = { version = "0.17.0" } opentelemetry = { version = "0.17.0" }
opentelemetry-jaeger = { version = "0.17.0" } opentelemetry-jaeger = { version = "0.17.0" }
parking_lot = { version = "0.12", features = [] } parking_lot = { version = "0.12", features = [] }
@ -24,7 +24,7 @@ pretty_env_logger = { version = "0.4", features = [] }
rumqttc = { version = "*" } rumqttc = { version = "*" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
sonic-channel = { version = "1.1.0", features = ["ingest"] } 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" } thiserror = { version = "1.0.31" }
tokio = { version = "1.21.2", features = ['full'] } tokio = { version = "1.21.2", features = ['full'] }
tracing = { version = "0.1.6" } tracing = { version = "0.1.6" }
@ -32,3 +32,7 @@ tracing-opentelemetry = { version = "0.17.4" }
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
uuid = { version = "1.2.1", features = ["serde"] } uuid = { version = "1.2.1", features = ["serde"] }
whatlang = { version = "0.16.2" } whatlang = { version = "0.16.2" }
[dev-dependencies]
fake = { version = "2.5.0" }
testx = { path = "../testx" }

View File

@ -15,7 +15,7 @@ db-utils = { path = "../db-utils" }
derive_more = { version = "0.99", features = [] } derive_more = { version = "0.99", features = [] }
dotenv = { version = "0.15.0" } dotenv = { version = "0.15.0" }
futures = { version = "0.3.25" } futures = { version = "0.3.25" }
model = { path = "../model" } model = { path = "../model", features = ["db"] }
opentelemetry = { version = "0.17.0" } opentelemetry = { version = "0.17.0" }
opentelemetry-jaeger = { version = "0.17.0" } opentelemetry-jaeger = { version = "0.17.0" }
pretty_env_logger = { version = "0.4", features = [] } pretty_env_logger = { version = "0.4", features = [] }
@ -23,14 +23,15 @@ rumqttc = { version = "*" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
sqlx = { version = "0.6.2", features = ["migrate", "runtime-actix-rustls", "all-types", "postgres"] } sqlx = { version = "0.6.2", features = ["migrate", "runtime-actix-rustls", "all-types", "postgres"] }
sqlx-core = { version = "0.6.2", features = [] } 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" } thiserror = { version = "1.0.31" }
tokio = { version = "1.21.2", features = ['full'] } tokio = { version = "1.21.2", features = ['full'] }
tracing = { version = "0.1.6" } tracing = { version = "0.1.6" }
tracing-opentelemetry = { version = "0.17.4" } tracing-opentelemetry = { version = "0.17.4" }
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
uuid = { version = "1.2.1" } uuid = { version = "1.2.1", features = ['v4'] }
[dev-dependencies] [dev-dependencies]
fake = { version = "2.5.0" } fake = { version = "2.5.0" }
testx = { path = "../testx" } testx = { path = "../testx" }
insta = { version = "1.21.0" }

View File

@ -8,21 +8,21 @@ CREATE TYPE "QuantityUnit" AS ENUM (
); );
CREATE TABLE photos ( CREATE TABLE photos (
id integer NOT NULL PRIMARY KEY, id serial NOT NULL PRIMARY KEY,
local_path character varying NOT NULL, local_path character varying NOT NULL,
file_name character varying NOT NULL, file_name character varying NOT NULL,
unique_name text DEFAULT (gen_random_uuid())::text NOT NULL unique_name text DEFAULT (gen_random_uuid())::text NOT NULL
); );
CREATE TABLE products ( CREATE TABLE products (
id integer NOT NULL PRIMARY KEY, id serial NOT NULL PRIMARY KEY,
"name" character varying NOT NULL, "name" character varying NOT NULL,
category character varying, category character varying,
deliver_days_flag integer DEFAULT 127 NOT NULL deliver_days_flag integer DEFAULT 127 NOT NULL
); );
CREATE TABLE product_variants ( CREATE TABLE product_variants (
id integer NOT NULL PRIMARY KEY, id serial NOT NULL PRIMARY KEY,
product_id integer REFERENCES products (id) NOT NULL, product_id integer REFERENCES products (id) NOT NULL,
"name" character varying NOT NULL, "name" character varying NOT NULL,
short_description character varying NOT NULL, short_description character varying NOT NULL,
@ -32,7 +32,7 @@ CREATE TABLE product_variants (
); );
CREATE TABLE stocks ( 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, product_variant_id integer REFERENCES product_variants(id) NOT NULL,
quantity integer DEFAULT 0 NOT NULL, quantity integer DEFAULT 0 NOT NULL,
quantity_unit "QuantityUnit" NOT NULL, quantity_unit "QuantityUnit" NOT NULL,
@ -40,7 +40,7 @@ CREATE TABLE stocks (
); );
CREATE TABLE product_photos ( 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, product_variant_id integer REFERENCES product_variants(id) NOT NULL,
photo_id integer REFERENCES photos(id) NOT NULL photo_id integer REFERENCES photos(id) NOT NULL
); );

View File

@ -1,14 +1,19 @@
use std::collections::HashMap;
use channels::stocks::{detailed_product, detailed_products}; use channels::stocks::{detailed_product, detailed_products};
use channels::AsyncClient; use channels::AsyncClient;
use config::SharedAppConfig; 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( pub async fn detailed_product(
input: detailed_product::Input, _input: detailed_product::Input,
db: Database, _db: Database,
mqtt: AsyncClient, _mqtt: AsyncClient,
config: SharedAppConfig, _config: SharedAppConfig,
) -> detailed_product::Output { ) -> detailed_product::Output {
todo!() todo!()
} }
@ -16,8 +21,316 @@ pub async fn detailed_product(
pub async fn detailed_products( pub async fn detailed_products(
input: detailed_products::Input, input: detailed_products::Input,
db: Database, db: Database,
mqtt: AsyncClient, _mqtt: AsyncClient,
config: SharedAppConfig, _config: SharedAppConfig,
) -> detailed_products::Output { ) -> 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<AsyncClient>,
_config: Option<SharedAppConfig>,
) -> 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);
}
}
} }

View File

@ -5,28 +5,28 @@ use config::SharedAppConfig;
use crate::db::Database; use crate::db::Database;
pub async fn create_product( pub async fn create_product(
input: create_product::Input, _input: create_product::Input,
db: Database, _db: Database,
mqtt: AsyncClient, _mqtt: AsyncClient,
config: SharedAppConfig, _config: SharedAppConfig,
) -> create_product::Output { ) -> create_product::Output {
todo!() todo!()
} }
pub async fn update_product( pub async fn update_product(
input: update_product::Input, _input: update_product::Input,
db: Database, _db: Database,
mqtt: AsyncClient, _mqtt: AsyncClient,
config: SharedAppConfig, _config: SharedAppConfig,
) -> update_product::Output { ) -> update_product::Output {
todo!() todo!()
} }
pub async fn delete_product( pub async fn delete_product(
input: delete_product::Input, _input: delete_product::Input,
db: Database, _db: Database,
mqtt: AsyncClient, _mqtt: AsyncClient,
config: SharedAppConfig, _config: SharedAppConfig,
) -> delete_product::Output { ) -> delete_product::Output {
todo!() todo!()
} }

View File

@ -5,19 +5,19 @@ use config::SharedAppConfig;
use crate::db::Database; use crate::db::Database;
pub async fn add_product_photo( pub async fn add_product_photo(
input: add_product_photo::Input, _input: add_product_photo::Input,
db: Database, _db: Database,
mqtt: AsyncClient, _mqtt: AsyncClient,
config: SharedAppConfig, _config: SharedAppConfig,
) -> add_product_photo::Output { ) -> add_product_photo::Output {
todo!() todo!()
} }
pub async fn delete_product_photo( pub async fn delete_product_photo(
input: delete_product_photo::Input, _input: delete_product_photo::Input,
db: Database, _db: Database,
mqtt: AsyncClient, _mqtt: AsyncClient,
config: SharedAppConfig, _config: SharedAppConfig,
) -> delete_product_photo::Output { ) -> delete_product_photo::Output {
todo!() todo!()
} }

View File

@ -5,19 +5,19 @@ use config::SharedAppConfig;
use crate::db::Database; use crate::db::Database;
pub async fn create_product_stock( pub async fn create_product_stock(
input: create_product_stock::Input, _input: create_product_stock::Input,
db: Database, _db: Database,
mqtt: AsyncClient, _mqtt: AsyncClient,
config: SharedAppConfig, _config: SharedAppConfig,
) -> create_product_stock::Output { ) -> create_product_stock::Output {
todo!() todo!()
} }
pub async fn update_product_stock( pub async fn update_product_stock(
input: update_product_stock::Input, _input: update_product_stock::Input,
db: Database, _db: Database,
mqtt: AsyncClient, _mqtt: AsyncClient,
config: SharedAppConfig, _config: SharedAppConfig,
) -> update_product_stock::Output { ) -> update_product_stock::Output {
todo!() todo!()
} }

View File

@ -5,28 +5,28 @@ use config::SharedAppConfig;
use crate::db::Database; use crate::db::Database;
pub async fn create_product_variant( pub async fn create_product_variant(
input: create_product_variant::Input, _input: create_product_variant::Input,
db: Database, _db: Database,
mqtt: AsyncClient, _mqtt: AsyncClient,
config: SharedAppConfig, _config: SharedAppConfig,
) -> create_product_variant::Output { ) -> create_product_variant::Output {
todo!() todo!()
} }
pub async fn update_product_variant( pub async fn update_product_variant(
input: update_product_variant::Input, _input: update_product_variant::Input,
db: Database, _db: Database,
mqtt: AsyncClient, _mqtt: AsyncClient,
config: SharedAppConfig, _config: SharedAppConfig,
) -> update_product_variant::Output { ) -> update_product_variant::Output {
todo!() todo!()
} }
pub async fn delete_product_variant( pub async fn delete_product_variant(
input: delete_product_variant::Input, _input: delete_product_variant::Input,
db: Database, _db: Database,
mqtt: AsyncClient, _mqtt: AsyncClient,
config: SharedAppConfig, _config: SharedAppConfig,
) -> delete_product_variant::Output { ) -> delete_product_variant::Output {
todo!() todo!()
} }

View File

@ -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,
),
},
],
},
],
},
],
}

View File

@ -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,
),
},
],
},
],
},
],
}

View File

@ -8,6 +8,12 @@ mod product_variants;
mod products; mod products;
mod stocks; mod stocks;
pub use photos::*;
pub use product_photos::*;
pub use product_variants::*;
pub use products::*;
pub use stocks::*;
#[derive(Clone)] #[derive(Clone)]
pub struct Database { pub struct Database {
pub pool: sqlx::PgPool, pub pool: sqlx::PgPool,

View File

@ -15,8 +15,8 @@ pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)] #[derive(Debug)]
pub struct AllPhotos { pub struct AllPhotos {
pub limit: i32, pub limit: Limit,
pub offset: i32, pub offset: Offset,
} }
impl AllPhotos { impl AllPhotos {
@ -208,8 +208,8 @@ mod tests {
let p3 = test_photo(&mut t, None, None, None).await; let p3 = test_photo(&mut t, None, None, None).await;
let all = AllPhotos { let all = AllPhotos {
limit: 1000, limit: Limit::from_u32(1000),
offset: 0, offset: Offset::from_u32(0),
} }
.run(&mut t) .run(&mut t)
.await .await

View File

@ -1,8 +1,6 @@
use db_utils::PgT; use db_utils::PgT;
use model::v2::*; use model::v2::*;
use crate::db::products::AllProducts;
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, thiserror::Error)] #[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, thiserror::Error)]
pub enum Error { pub enum Error {
#[error("Failed to attach photo to product")] #[error("Failed to attach photo to product")]
@ -17,8 +15,8 @@ pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)] #[derive(Debug)]
pub struct AllProductPhotos { pub struct AllProductPhotos {
pub limit: Option<i32>, pub limit: Option<Limit>,
pub offset: Option<i32>, pub offset: Option<Offset>,
} }
impl AllProductPhotos { impl AllProductPhotos {
@ -178,6 +176,7 @@ mod tests {
let deleted = DeleteProductPhoto { id: p2.id }.run(&mut t).await.unwrap(); let deleted = DeleteProductPhoto { id: p2.id }.run(&mut t).await.unwrap();
testx::db_rollback!(t); testx::db_rollback!(t);
assert_ne!(deleted, Some(p1)); assert_ne!(deleted, Some(p1));
assert_eq!(deleted, Some(p2)); assert_eq!(deleted, Some(p2));
assert_ne!(deleted, Some(p3)); assert_ne!(deleted, Some(p3));

View File

@ -1,7 +1,7 @@
use db_utils::PgT; use db_utils::PgT;
use model::v2::*; use model::v2::*;
#[derive(Debug, thiserror::Error)] #[derive(Debug, PartialEq, thiserror::Error)]
pub enum Error { pub enum Error {
#[error("Failed to create product")] #[error("Failed to create product")]
CreateProductVariant, CreateProductVariant,
@ -59,8 +59,8 @@ RETURNING id,
#[derive(Debug)] #[derive(Debug)]
pub struct ProductsVariants { pub struct ProductsVariants {
pub product_ids: Vec<ProductId>, pub product_ids: Vec<ProductId>,
pub limit: Option<i32>, pub limit: Option<Limit>,
pub offset: Option<i32>, pub offset: Option<Offset>,
} }
impl ProductsVariants { impl ProductsVariants {
@ -79,14 +79,21 @@ INNER JOIN products ps
ON pv.product_id = ps.id ON pv.product_id = ps.id
WHERE 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() .allow_over_max()
.with_size(1000) .with_size(1000)
.load( .load(
self.product_ids.len(), self.product_ids.len(),
self.product_ids.iter().copied().map(|id| *id), 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 .await
} }
@ -111,3 +118,96 @@ WHERE id = $1
.map_err(|_e| Error::DeleteProductVariant(self.product_variant_id)) .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<String>,
category: Option<ProductCategory>,
deliver_days_flag: Option<Days>,
) -> 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![]));
}
}

View File

@ -1,5 +1,6 @@
use db_utils::PgT;
use model::v2::*; use model::v2::*;
use model::ShoppingCartId; use model::{Ranged, ShoppingCartId};
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, thiserror::Error)] #[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, thiserror::Error)]
pub enum Error { pub enum Error {
@ -23,8 +24,8 @@ pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)] #[derive(Debug)]
pub struct AllProducts { pub struct AllProducts {
limit: i32, pub limit: Limit,
offset: i32, pub offset: Offset,
} }
impl AllProducts { impl AllProducts {
@ -192,15 +193,12 @@ RETURNING id,
#[derive(Debug)] #[derive(Debug)]
pub struct ShoppingCartProducts { pub struct ShoppingCartProducts {
pub shopping_cart_id: ShoppingCartId, pub shopping_cart_id: ShoppingCartId,
pub limit: i32, pub limit: Limit,
pub offset: i32, pub offset: Offset,
} }
impl ShoppingCartProducts { impl ShoppingCartProducts {
pub async fn shopping_cart_products<'e, E>(self, pool: E) -> Result<Vec<Product>> pub async fn run(self, t: &mut PgT<'_>) -> Result<Vec<Product>> {
where
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
{
sqlx::query_as( sqlx::query_as(
r#" r#"
SELECT products.id, SELECT products.id,
@ -215,9 +213,9 @@ LIMIT $2 OFFSET $3
"#, "#,
) )
.bind(self.shopping_cart_id) .bind(self.shopping_cart_id)
.bind(self.limit.min(1).max(200)) .bind(self.limit.in_range(1..200))
.bind(self.offset.min(0)) .bind(self.offset.in_range(0..u32::MAX))
.fetch_all(pool) .fetch_all(t)
.await .await
.map_err(|e| { .map_err(|e| {
tracing::error!("{e:?}"); tracing::error!("{e:?}");
@ -280,10 +278,7 @@ mod tests {
async fn test_product( async fn test_product(
t: &mut sqlx::Transaction<'_, sqlx::Postgres>, t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
name: Option<String>, name: Option<String>,
short_description: Option<String>,
long_description: Option<String>,
category: Option<ProductCategory>, category: Option<ProductCategory>,
price: Option<u32>,
deliver_days_flag: Option<Days>, deliver_days_flag: Option<Days>,
) -> Product { ) -> Product {
CreateProduct { CreateProduct {
@ -301,7 +296,7 @@ mod tests {
async fn create() { async fn create() {
testx::db_t_ref!(t); 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); testx::db_rollback!(t);
} }
@ -310,13 +305,13 @@ mod tests {
async fn all() { async fn all() {
testx::db_t_ref!(t); testx::db_t_ref!(t);
let p1 = 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, None, None, None).await; let p2 = test_product(&mut t, None, None, None).await;
let p3 = test_product(&mut t, None, None, None, None, None, None).await; let p3 = test_product(&mut t, None, None, None).await;
let products = AllProducts { let products = AllProducts {
limit: 10000, limit: 10000.into(),
offset: 0, offset: 0.into(),
} }
.run(&mut t) .run(&mut t)
.await .await
@ -330,13 +325,11 @@ mod tests {
async fn find() { async fn find() {
testx::db_t_ref!(t); testx::db_t_ref!(t);
let p1 = 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, None, None, None).await; let p2 = test_product(&mut t, None, None, None).await;
let p3 = test_product(&mut t, None, None, None, 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) let product = FindProduct { product_id: p2.id }.run(&mut t).await.unwrap();
.await
.unwrap();
testx::db_rollback!(t); testx::db_rollback!(t);
assert_ne!(product, p1); assert_ne!(product, p1);
@ -348,7 +341,7 @@ mod tests {
async fn update() { async fn update() {
testx::db_t_ref!(t); 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 { let updated = UpdateProduct {
id: original.id, id: original.id,
name: ProductName::new("a9s0dja0sjd0jas09dj"), name: ProductName::new("a9s0dja0sjd0jas09dj"),

View File

@ -1,7 +1,5 @@
use model::v2::*; use model::v2::*;
use crate::db::products::AllProducts;
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, thiserror::Error)] #[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, thiserror::Error)]
pub enum Error { pub enum Error {
#[error("Unable to load all stocks")] #[error("Unable to load all stocks")]
@ -104,7 +102,7 @@ RETURNING id, product_variant_id, quantity, quantity_unit
#[derive(Debug)] #[derive(Debug)]
pub struct UpdateStock { pub struct UpdateStock {
pub id: StockId, pub id: StockId,
pub product_id: ProductId, pub product_variant_id: ProductVariantId,
pub quantity: Quantity, pub quantity: Quantity,
pub quantity_unit: QuantityUnit, pub quantity_unit: QuantityUnit,
} }
@ -121,7 +119,7 @@ WHERE id = $4
RETURNING id, product_variant_id, quantity, quantity_unit RETURNING id, product_variant_id, quantity, quantity_unit
"#, "#,
) )
.bind(self.product_id) .bind(self.product_variant_id)
.bind(self.quantity) .bind(self.quantity)
.bind(self.quantity_unit) .bind(self.quantity_unit)
.bind(self.id) .bind(self.id)
@ -164,7 +162,7 @@ pub struct ProductVariantsStock {
} }
impl ProductVariantsStock { impl ProductVariantsStock {
async fn run(self, pool: &mut sqlx::Transaction<'_, sqlx::Postgres>) -> Result<Vec<Stock>> { pub async fn run(self, pool: &mut sqlx::Transaction<'_, sqlx::Postgres>) -> Result<Vec<Stock>> {
db_utils::MultiLoad::new( db_utils::MultiLoad::new(
pool, pool,
r#" r#"
@ -187,12 +185,12 @@ WHERE
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use config::UpdateConfig; use config::UpdateConfig;
use fake::faker::lorem::en as lorem; use db_utils::PgT;
use fake::Fake;
use model::v2::*; use model::v2::*;
use model::Day; use model::Day;
use uuid::Uuid; use uuid::Uuid;
use crate::db::product_variants::CreateProductVariant;
use crate::db::Database; use crate::db::Database;
pub struct NoOpts; pub struct NoOpts;
@ -213,6 +211,19 @@ mod tests {
.unwrap() .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( async fn test_stock(
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
product_variant_id: Option<ProductVariantId>, product_variant_id: Option<ProductVariantId>,
@ -221,7 +232,10 @@ mod tests {
) -> Stock { ) -> Stock {
let product_variant_id = match product_variant_id { let product_variant_id = match product_variant_id {
Some(id) => 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 = quantity.unwrap_or_else(|| Quantity::from_u32(345));
let quantity_unit = quantity_unit.unwrap_or(QuantityUnit::Piece); 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 second = test_stock(&mut t, None, None, None).await;
let stocks: Vec<Stock> = ProductVariantsStock { let stocks: Vec<Stock> = 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) .run(&mut t)
.await .await
@ -289,18 +303,19 @@ mod tests {
let first = test_stock(&mut t, None, None, None).await; let first = test_stock(&mut t, None, None, None).await;
let second = 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<Stock> = DeleteStock { let deleted: Option<Stock> = DeleteStock {
stock_id: second.id, stock_id: second_id,
} }
.run(&mut t) .run(&mut t)
.await .await
.unwrap(); .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); testx::db_rollback!(t);
assert_eq!(deleted, Some(second)); assert_eq!(deleted, Some(second));
assert_ne!(deleted, Some(first)); assert_ne!(deleted, Some(first));
assert_eq!(reloaded, Err(super::Error::NotFound)); assert_eq!(reloaded, Err(super::Error::NotFound(second_id)));
} }
#[tokio::test] #[tokio::test]
@ -310,10 +325,11 @@ mod tests {
let first = test_stock(&mut t, None, None, None).await; let first = test_stock(&mut t, None, None, None).await;
let second = 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 = test_product(&mut t).await;
let another_product_variant = test_product_variant(another_product.id, &mut t).await;
let updated: Stock = UpdateStock { let updated: Stock = UpdateStock {
id: second.id, id: second.id,
product_id: another_product.id, product_variant_id: another_product_variant.id,
quantity: Quantity::from_u32(19191), quantity: Quantity::from_u32(19191),
quantity_unit: QuantityUnit::Gram, quantity_unit: QuantityUnit::Gram,
} }
@ -327,7 +343,7 @@ mod tests {
updated, updated,
Stock { Stock {
id: second.id, id: second.id,
product_id: another_product.id, product_variant_id: another_product_variant.id,
quantity: Quantity::from_u32(19191), quantity: Quantity::from_u32(19191),
quantity_unit: QuantityUnit::Gram, quantity_unit: QuantityUnit::Gram,
} }

View File

@ -29,4 +29,5 @@ tracing = { version = "0.1.34" }
uuid = { version = "1.2.1", features = ["serde"] } uuid = { version = "1.2.1", features = ["serde"] }
[dev-dependencies] [dev-dependencies]
fake = { version = "2.5.0" }
testx = { path = "../testx" } testx = { path = "../testx" }

View File

@ -2,6 +2,13 @@
source .env 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 psql postgres postgres -c "CREATE DATABASE ${DATABASE_NAME}_accounts" || echo 0
sqlx migrate run -D "${ACCOUNT_DATABASE_URL}" --source ./crates/account_manager/migrations sqlx migrate run -D "${ACCOUNT_DATABASE_URL}" --source ./crates/account_manager/migrations