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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -43,7 +43,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = [] }
sqlx = { version = "0.6.2", features = ["migrate", "runtime-actix-rustls", "all-types", "postgres"] }
sqlx-core = { version = "0.6.2", features = [] }
tarpc = { version = "0.30.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] }
tarpc = { version = "0.31.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] }
tera = { version = "1.15", features = [] }
thiserror = { version = "1.0", features = [] }
token_manager = { path = "../token_manager" }

View File

@ -8,14 +8,12 @@ name = "cart-manager"
path = "src/main.rs"
[dependencies]
actix = { version = "0.13", features = [] }
actix-rt = { version = "2.7", features = [] }
channels = { path = "../channels" }
chrono = { version = "0.4", features = ["serde"] }
config = { path = "../config" }
dotenv = { version = "0.15.0" }
futures = { version = "0.3.25" }
model = { path = "../model" }
model = { path = "../model", features = ["db"] }
opentelemetry = { version = "0.17.0" }
opentelemetry-jaeger = { version = "0.17.0" }
pretty_env_logger = { version = "0.4", features = [] }
@ -23,10 +21,14 @@ rumqttc = { version = "*" }
serde = { version = "1.0.137", features = ["derive"] }
sqlx = { version = "0.6.2", features = ["migrate", "runtime-actix-rustls", "all-types", "postgres"] }
sqlx-core = { version = "0.6.2", features = [] }
tarpc = { version = "0.30.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] }
tarpc = { version = "0.31.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] }
thiserror = { version = "1.0.31" }
tokio = { version = "1.21.2", features = ['full'] }
tracing = { version = "0.1.37" }
tracing-opentelemetry = { version = "0.17.4" }
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
uuid = { version = "0.8", features = ["serde"] }
uuid = { version = "0.8", features = ["serde", "v4"] }
[dev-dependencies]
fake = { version = "2.5.0" }
testx = { path = "../testx" }

View File

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

View File

@ -366,9 +366,9 @@ RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit
#[cfg(test)]
mod tests {
use config::UpdateConfig;
use fake::Fake;
use model::*;
use uuid::Uuid;
use crate::db::shopping_carts::*;
use crate::db::Database;
pub struct NoOpts;
@ -376,42 +376,6 @@ mod tests {
use super::*;
async fn test_product(t: &mut sqlx::Transaction<'_, sqlx::Postgres>) -> Product {
CreateProduct {
name: ProductName::new(format!("{}", Uuid::new_v4())),
short_description: ProductShortDesc::new(format!("{}", Uuid::new_v4())),
long_description: ProductLongDesc::new(format!("{}", Uuid::new_v4())),
category: None,
price: Price::from_u32(4687),
deliver_days_flag: Days(vec![Day::Friday, Day::Sunday]),
}
.run(t)
.await
.unwrap()
}
async fn test_account(
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
login: Option<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(
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
buyer_id: Option<AccountId>,
@ -419,7 +383,7 @@ mod tests {
) -> ShoppingCart {
let buyer_id = match buyer_id {
Some(id) => id,
_ => test_account(&mut *t, None, None, None).await.id,
_ => 1.into(),
};
sqlx::query(
@ -469,7 +433,7 @@ WHERE buyer_id = $1
};
let product_id = match product_id {
Some(id) => id,
_ => test_product(&mut *t).await.id,
_ => 1.into(),
};
CreateShoppingCartItem {
product_id,
@ -482,7 +446,7 @@ WHERE buyer_id = $1
.unwrap()
}
#[actix::test]
#[tokio::test]
async fn create() {
testx::db_t_ref!(t);
@ -491,11 +455,11 @@ WHERE buyer_id = $1
testx::db_rollback!(t);
}
#[actix::test]
#[tokio::test]
async fn all() {
testx::db_t_ref!(t);
let account_id = test_account(&mut t, None, None, None).await.id;
let account_id = 1.into();
let mut items = Vec::with_capacity(9);
@ -514,19 +478,17 @@ WHERE buyer_id = $1
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None).await);
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None).await);
let all = all_shopping_cart_items(AllShoppingCartItems, &mut t)
.await
.unwrap();
let all = AllShoppingCartItems.run(&mut t).await.unwrap();
testx::db_rollback!(t);
assert_eq!(all, items)
}
#[actix::test]
#[tokio::test]
async fn account_cart_with_cart_id() {
testx::db_t_ref!(t);
let account_id = test_account(&mut t, None, None, None).await.id;
let account_id = 1.into();
let mut items = Vec::with_capacity(9);
@ -545,13 +507,11 @@ WHERE buyer_id = $1
test_shopping_cart_item(&mut t, Some(cart3.id), None).await;
test_shopping_cart_item(&mut t, Some(cart3.id), None).await;
let all = account_shopping_cart_items(
AccountShoppingCartItems {
let all = AccountShoppingCartItems {
account_id,
shopping_cart_id: Some(cart2.id),
},
&mut t,
)
}
.run(&mut t)
.await
.unwrap();
@ -559,11 +519,11 @@ WHERE buyer_id = $1
assert_eq!(all, items)
}
#[actix::test]
#[tokio::test]
async fn account_cart_without_cart_id() {
testx::db_t_ref!(t);
let account_id = test_account(&mut t, None, None, None).await.id;
let account_id = 1.into();
let mut items = Vec::with_capacity(9);
@ -582,13 +542,11 @@ WHERE buyer_id = $1
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None).await);
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None).await);
let all = account_shopping_cart_items(
AccountShoppingCartItems {
let all = AccountShoppingCartItems {
account_id,
shopping_cart_id: None,
},
&mut t,
)
}
.run(&mut t)
.await
.unwrap();
@ -596,23 +554,21 @@ WHERE buyer_id = $1
assert_eq!(all, items)
}
#[actix::test]
#[tokio::test]
async fn update() {
testx::db_t_ref!(t);
let account_id = test_account(&mut t, None, None, None).await.id;
let account_id = 1.into();
let cart1 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Closed).await;
let item = test_shopping_cart_item(&mut t, Some(cart1.id), None).await;
let updated = update_shopping_cart_item(
UpdateShoppingCartItem {
let updated = UpdateShoppingCartItem {
id: item.id,
product_id: item.product_id,
shopping_cart_id: item.shopping_cart_id,
quantity: Quantity::from_u32(987979879),
quantity_unit: QuantityUnit::Kilogram,
},
&mut t,
)
}
.run(&mut t)
.await
.unwrap();

View File

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

View File

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

View File

@ -19,7 +19,7 @@ futures = { version = "0.3.25" }
model = { path = "../model" }
rumqttc = { version = "0.17.0" }
serde = { version = "*", features = ['derive'] }
tarpc = { version = "0.30.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] }
tarpc = { version = "0.31.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] }
thiserror = { version = "1.0.37" }
tokio = { version = "1.21.2", features = ['full'] }
tracing = { version = "0.1.37" }

View File

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

View File

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

View File

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

View File

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

View File

@ -23,10 +23,14 @@ rumqttc = { version = "*" }
sendgrid = { version = "0.18.1", features = ["async"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = [] }
tarpc = { version = "0.30.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] }
tarpc = { version = "0.31.0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] }
thiserror = { version = "1.0.31" }
tokio = { version = "1.21.2", features = ['full'] }
tracing = { version = "0.1.37" }
tracing-opentelemetry = { version = "0.17.4" }
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
uuid = { version = "0.8", features = ["serde"] }
[dev-dependencies]
fake = { version = "2.5.0" }
testx = { path = "../testx" }

View File

@ -8,7 +8,7 @@ pub mod encrypt;
use std::fmt::{Display, Formatter};
use std::ops;
use std::ops::BitOr;
use std::ops::{BitOr, Range};
use std::str::FromStr;
use derive_more::{Deref, DerefMut, Display, From};
@ -311,6 +311,89 @@ impl ops::Mul<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 = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))]
@ -905,7 +988,7 @@ pub mod v2 {
use serde::{Deserialize, Serialize};
pub use crate::{
Day, Days, FileName, LocalPath, PhotoId, Price, ProductCategory, ProductId,
Day, Days, FileName, Limit, LocalPath, Offset, PhotoId, Price, ProductCategory, ProductId,
ProductLongDesc, ProductName, ProductPhotoId, ProductShortDesc, Quantity, QuantityUnit,
RecordId, StockId, UniqueName,
};
@ -937,19 +1020,26 @@ pub mod v2 {
#[serde(transparent)]
pub struct ProductVariantId(RecordId);
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct DetailedProduct {
pub id: ProductId,
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct DetailedProductVariant {
pub id: ProductVariantId,
pub name: ProductName,
pub short_description: ProductShortDesc,
pub long_description: ProductLongDesc,
pub category: Option<ProductCategory>,
pub price: Price,
pub deliver_days_flag: Days,
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))]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,14 +1,19 @@
use std::collections::HashMap;
use channels::stocks::{detailed_product, detailed_products};
use channels::AsyncClient;
use config::SharedAppConfig;
use db_utils::PgT;
use model::v2::{DetailedProduct, DetailedProductVariant, Product, ProductVariant};
use model::Limit;
use crate::db::Database;
use crate::db::{Database, PhotosForProductVariants, ProductVariantsStock, ProductsVariants};
pub async fn detailed_product(
input: detailed_product::Input,
db: Database,
mqtt: AsyncClient,
config: SharedAppConfig,
_input: detailed_product::Input,
_db: Database,
_mqtt: AsyncClient,
_config: SharedAppConfig,
) -> detailed_product::Output {
todo!()
}
@ -16,8 +21,316 @@ pub async fn detailed_product(
pub async fn detailed_products(
input: detailed_products::Input,
db: Database,
mqtt: AsyncClient,
config: SharedAppConfig,
_mqtt: AsyncClient,
_config: SharedAppConfig,
) -> detailed_products::Output {
todo!()
let mut t = match db.pool().begin().await {
Err(e) => {
tracing::error!("{}", e);
return Err(detailed_products::Error::InternalServerError);
}
Ok(t) => t,
};
let res = inner_detailed_products(input, &mut t, Some(_mqtt), Some(_config)).await;
t.commit().await.ok();
res
}
async fn inner_detailed_products(
input: detailed_products::Input,
t: &mut PgT<'_>,
_mqtt: Option<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;
pub async fn create_product(
input: create_product::Input,
db: Database,
mqtt: AsyncClient,
config: SharedAppConfig,
_input: create_product::Input,
_db: Database,
_mqtt: AsyncClient,
_config: SharedAppConfig,
) -> create_product::Output {
todo!()
}
pub async fn update_product(
input: update_product::Input,
db: Database,
mqtt: AsyncClient,
config: SharedAppConfig,
_input: update_product::Input,
_db: Database,
_mqtt: AsyncClient,
_config: SharedAppConfig,
) -> update_product::Output {
todo!()
}
pub async fn delete_product(
input: delete_product::Input,
db: Database,
mqtt: AsyncClient,
config: SharedAppConfig,
_input: delete_product::Input,
_db: Database,
_mqtt: AsyncClient,
_config: SharedAppConfig,
) -> delete_product::Output {
todo!()
}

View File

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

View File

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

View File

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

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 stocks;
pub use photos::*;
pub use product_photos::*;
pub use product_variants::*;
pub use products::*;
pub use stocks::*;
#[derive(Clone)]
pub struct Database {
pub pool: sqlx::PgPool,

View File

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

View File

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

View File

@ -1,7 +1,7 @@
use db_utils::PgT;
use model::v2::*;
#[derive(Debug, thiserror::Error)]
#[derive(Debug, PartialEq, thiserror::Error)]
pub enum Error {
#[error("Failed to create product")]
CreateProductVariant,
@ -59,8 +59,8 @@ RETURNING id,
#[derive(Debug)]
pub struct ProductsVariants {
pub product_ids: Vec<ProductId>,
pub limit: Option<i32>,
pub offset: Option<i32>,
pub limit: Option<Limit>,
pub offset: Option<Offset>,
}
impl ProductsVariants {
@ -79,14 +79,21 @@ INNER JOIN products ps
ON pv.product_id = ps.id
WHERE
"#,
"products.id = ",
"ps.id = ",
)
.with_padding(
self.limit.map(|n| **n).unwrap_or(200),
*self.offset.map(|n| *n).unwrap_or_default(),
)
.allow_over_max()
.with_size(1000)
.load(
self.product_ids.len(),
self.product_ids.iter().copied().map(|id| *id),
|_| Error::ProductsVariants(self.product_ids.clone()),
|e| {
tracing::error!("{}", e);
Error::ProductsVariants(self.product_ids.clone())
},
)
.await
}
@ -111,3 +118,96 @@ WHERE id = $1
.map_err(|_e| Error::DeleteProductVariant(self.product_variant_id))
}
}
#[cfg(test)]
mod tests {
use config::UpdateConfig;
use db_utils::PgT;
use model::v2::*;
use uuid::Uuid;
use super::*;
use crate::db::*;
#[derive(Debug)]
pub struct NoOpts;
impl UpdateConfig for NoOpts {}
async fn test_product_variant(product_id: ProductId, t: &mut PgT<'_>) -> ProductVariant {
CreateProductVariant {
product_id,
name: ProductName::new(format!("{}", Uuid::new_v4())),
short_description: ProductShortDesc::new(format!("{}", Uuid::new_v4())),
long_description: ProductLongDesc::new(format!("{}", Uuid::new_v4())),
price: Default::default(),
}
.run(t)
.await
.unwrap()
}
async fn test_product(
t: &mut PgT<'_>,
name: Option<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::ShoppingCartId;
use model::{Ranged, ShoppingCartId};
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, thiserror::Error)]
pub enum Error {
@ -23,8 +24,8 @@ pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub struct AllProducts {
limit: i32,
offset: i32,
pub limit: Limit,
pub offset: Offset,
}
impl AllProducts {
@ -192,15 +193,12 @@ RETURNING id,
#[derive(Debug)]
pub struct ShoppingCartProducts {
pub shopping_cart_id: ShoppingCartId,
pub limit: i32,
pub offset: i32,
pub limit: Limit,
pub offset: Offset,
}
impl ShoppingCartProducts {
pub async fn shopping_cart_products<'e, E>(self, pool: E) -> Result<Vec<Product>>
where
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
{
pub async fn run(self, t: &mut PgT<'_>) -> Result<Vec<Product>> {
sqlx::query_as(
r#"
SELECT products.id,
@ -215,9 +213,9 @@ LIMIT $2 OFFSET $3
"#,
)
.bind(self.shopping_cart_id)
.bind(self.limit.min(1).max(200))
.bind(self.offset.min(0))
.fetch_all(pool)
.bind(self.limit.in_range(1..200))
.bind(self.offset.in_range(0..u32::MAX))
.fetch_all(t)
.await
.map_err(|e| {
tracing::error!("{e:?}");
@ -280,10 +278,7 @@ mod tests {
async fn test_product(
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
name: Option<String>,
short_description: Option<String>,
long_description: Option<String>,
category: Option<ProductCategory>,
price: Option<u32>,
deliver_days_flag: Option<Days>,
) -> Product {
CreateProduct {
@ -301,7 +296,7 @@ mod tests {
async fn create() {
testx::db_t_ref!(t);
test_product(&mut t, None, None, None, None, None, None).await;
test_product(&mut t, None, None, None).await;
testx::db_rollback!(t);
}
@ -310,13 +305,13 @@ mod tests {
async fn all() {
testx::db_t_ref!(t);
let p1 = test_product(&mut t, None, None, None, None, None, None).await;
let p2 = test_product(&mut t, None, None, None, None, None, None).await;
let p3 = test_product(&mut t, None, None, None, None, None, None).await;
let p1 = test_product(&mut t, None, None, None).await;
let p2 = test_product(&mut t, None, None, None).await;
let p3 = test_product(&mut t, None, None, None).await;
let products = AllProducts {
limit: 10000,
offset: 0,
limit: 10000.into(),
offset: 0.into(),
}
.run(&mut t)
.await
@ -330,13 +325,11 @@ mod tests {
async fn find() {
testx::db_t_ref!(t);
let p1 = test_product(&mut t, None, None, None, None, None, None).await;
let p2 = test_product(&mut t, None, None, None, None, None, None).await;
let p3 = test_product(&mut t, None, None, None, None, None, None).await;
let p1 = test_product(&mut t, None, None, None).await;
let p2 = test_product(&mut t, None, None, None).await;
let p3 = test_product(&mut t, None, None, None).await;
let product = find_product(FindProduct { product_id: p2.id }, &mut t)
.await
.unwrap();
let product = FindProduct { product_id: p2.id }.run(&mut t).await.unwrap();
testx::db_rollback!(t);
assert_ne!(product, p1);
@ -348,7 +341,7 @@ mod tests {
async fn update() {
testx::db_t_ref!(t);
let original = test_product(&mut t, None, None, None, None, None, None).await;
let original = test_product(&mut t, None, None, None).await;
let updated = UpdateProduct {
id: original.id,
name: ProductName::new("a9s0dja0sjd0jas09dj"),

View File

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

View File

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

View File

@ -2,6 +2,13 @@
source .env
if [[ "$1" == "purge" ]];
then
psql postgres postgres -c "DROP DATABASE ${DATABASE_NAME}_accounts" || echo 0
psql postgres postgres -c "DROP DATABASE ${DATABASE_NAME}_carts" || echo 0
psql postgres postgres -c "DROP DATABASE ${DATABASE_NAME}_stocks" || echo 0
fi
psql postgres postgres -c "CREATE DATABASE ${DATABASE_NAME}_accounts" || echo 0
sqlx migrate run -D "${ACCOUNT_DATABASE_URL}" --source ./crates/account_manager/migrations