diff --git a/.sqlx/query-d896f6ffb486efad5ed10a9c824656d863de0c9140054de66eef32491ace9ddb.json b/.sqlx/query-289e6d9fe105ae9e91d947c22d0a5979786aac15b0c78359b3f634b74c4686d4.json similarity index 63% rename from .sqlx/query-d896f6ffb486efad5ed10a9c824656d863de0c9140054de66eef32491ace9ddb.json rename to .sqlx/query-289e6d9fe105ae9e91d947c22d0a5979786aac15b0c78359b3f634b74c4686d4.json index db85039..b3a9c5a 100644 --- a/.sqlx/query-d896f6ffb486efad5ed10a9c824656d863de0c9140054de66eef32491ace9ddb.json +++ b/.sqlx/query-289e6d9fe105ae9e91d947c22d0a5979786aac15b0c78359b3f634b74c4686d4.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE\n cqrs_ordering_category_query\n SET\n version = $1,\n name = $2,\n description = $3,\n category_id = $4,\n store_id = $5,\n deleted = $6;", + "query": "UPDATE\n cqrs_ordering_category_query\n SET\n version = $1,\n name = $2,\n description = $3,\n store_id = $4,\n deleted = $5;", "describe": { "columns": [], "parameters": { @@ -9,11 +9,10 @@ "Text", "Text", "Uuid", - "Uuid", "Bool" ] }, "nullable": [] }, - "hash": "d896f6ffb486efad5ed10a9c824656d863de0c9140054de66eef32491ace9ddb" + "hash": "289e6d9fe105ae9e91d947c22d0a5979786aac15b0c78359b3f634b74c4686d4" } diff --git a/src/ordering/adapters/output/db/category_view.rs b/src/ordering/adapters/output/db/category_view.rs index e817a24..247799d 100644 --- a/src/ordering/adapters/output/db/category_view.rs +++ b/src/ordering/adapters/output/db/category_view.rs @@ -10,7 +10,7 @@ use uuid::Uuid; use super::errors::*; use super::OrderingDBPostgresAdapter; -use crate::ordering::domain::category_aggregate::Category; +use crate::ordering::domain::category_aggregate::*; use crate::ordering::domain::events::OrderingEvent; use crate::utils::parse_aggregate_id::parse_aggregate_id; @@ -27,6 +27,19 @@ pub struct CategoryView { deleted: bool, } +impl From for Category { + fn from(v: CategoryView) -> Self { + CategoryBuilder::default() + .name(v.name) + .description(v.description) + .category_id(v.category_id) + .store_id(v.store_id) + .deleted(v.deleted) + .build() + .unwrap() + } +} + // This updates the view with events as they are committed. // The logic should be minimal here, e.g., don't calculate the account balance, // design the events to carry the balance information instead. @@ -157,13 +170,11 @@ impl ViewRepository for OrderingDBPostgresAdapter { version = $1, name = $2, description = $3, - category_id = $4, - store_id = $5, - deleted = $6;", + store_id = $4, + deleted = $5;", version, view.name, view.description, - view.category_id, view.store_id, view.deleted ) @@ -210,3 +221,146 @@ impl Query for OrderingDBPostgresAdapter { self.update_view(view, view_context).await.unwrap(); } } + +#[cfg(test)] +mod tests { + use super::*; + + use postgres_es::PostgresCqrs; + + use crate::{ + db::migrate::*, + ordering::{ + application::services::{ + add_category_service::*, update_category_service::*, MockOrderingServicesInterface, + }, + domain::{ + add_category_command::*, category_aggregate::*, commands::*, events::*, + store_aggregate::*, update_category_command::*, + }, + }, + tests::bdd::*, + utils::{random_string::GenerateRandomStringInterface, uuid::tests::UUID}, + }; + use std::sync::Arc; + + #[actix_rt::test] + async fn pg_query_ordering_category_view() { + let settings = crate::settings::tests::get_settings().await; + //let settings = crate::settings::Settings::new().unwrap(); + settings.create_db().await; + + let db = crate::db::sqlx_postgres::Postgres::init(&settings.database.url).await; + db.migrate().await; + let db = OrderingDBPostgresAdapter::new(db.pool.clone()); + + let store = Store::default(); + crate::ordering::adapters::output::db::store_id_exists::tests::create_dummy_store_record( + &store, &db, + ) + .await; + + let queries: Vec>> = vec![Box::new(db.clone())]; + + let mut mock_services = MockOrderingServicesInterface::new(); + + let db2 = Arc::new(db.clone()); + mock_services + .expect_add_category() + .times(IS_CALLED_ONLY_ONCE.unwrap()) + .returning(move || { + Arc::new( + AddCategoryServiceBuilder::default() + .db_store_id_exists(db2.clone()) + .db_category_name_exists_for_store(db2.clone()) + .db_category_id_exists(db2.clone()) + .build() + .unwrap(), + ) + }); + + let db2 = Arc::new(db.clone()); + mock_services + .expect_update_category() + .times(IS_CALLED_ONLY_ONCE.unwrap()) + .returning(move || { + Arc::new( + UpdateCategoryServiceBuilder::default() + .db_store_id_exists(db2.clone()) + .db_category_name_exists_for_store(db2.clone()) + .db_category_id_exists(db2.clone()) + .build() + .unwrap(), + ) + }); + + let (cqrs, category_query): ( + Arc>, + Arc>, + ) = ( + Arc::new(postgres_es::postgres_cqrs( + db.pool.clone(), + queries, + Arc::new(mock_services), + )), + Arc::new(db.clone()), + ); + + let rand = crate::utils::random_string::GenerateRandomString {}; + + let cmd = AddCategoryCommandBuilder::default() + .name(rand.get_random(10)) + .description(None) + .store_id(*store.store_id()) + .adding_by(UUID) + .category_id(UUID) + .build() + .unwrap(); + + cqrs.execute( + &cmd.category_id().to_string(), + OrderingCommand::AddCategory(cmd.clone()), + ) + .await + .unwrap(); + + let category = category_query + .load(&(*cmd.category_id()).to_string()) + .await + .unwrap() + .unwrap(); + let category: Category = category.into(); + assert_eq!(category.name(), cmd.name()); + assert_eq!(category.description(), cmd.description()); + assert_eq!(category.category_id(), cmd.category_id()); + assert_eq!(category.store_id(), cmd.store_id()); + assert!(!store.deleted()); + + let update_category_cmd = UpdateCategoryCommand::new( + rand.get_random(10), + Some(rand.get_random(10)), + category, + UUID, + ) + .unwrap(); + cqrs.execute( + &cmd.category_id().to_string(), + OrderingCommand::UpdateCategory(update_category_cmd.clone()), + ) + .await + .unwrap(); + let category = category_query + .load(&(*cmd.category_id()).to_string()) + .await + .unwrap() + .unwrap(); + let category: Category = category.into(); + assert_eq!(category.name(), update_category_cmd.name()); + assert_eq!(category.description(), update_category_cmd.description()); + assert_eq!(category.category_id(), cmd.category_id()); + assert_eq!(category.store_id(), cmd.store_id()); + assert!(!category.deleted()); + + settings.drop_db().await; + } +} diff --git a/src/ordering/adapters/output/db/store_id_exists.rs b/src/ordering/adapters/output/db/store_id_exists.rs index c67fbe0..1254cb1 100644 --- a/src/ordering/adapters/output/db/store_id_exists.rs +++ b/src/ordering/adapters/output/db/store_id_exists.rs @@ -45,7 +45,7 @@ pub mod tests { VALUES ($1, $2, $3, $4, $5 ,$6);", 1, s.name(), - s.address().as_ref().unwrap(), + s.address().as_ref().map(|s| s.as_str()), s.store_id(), s.owner(), false diff --git a/src/ordering/application/services/add_category_service.rs b/src/ordering/application/services/add_category_service.rs index 61e04ba..64aa569 100644 --- a/src/ordering/application/services/add_category_service.rs +++ b/src/ordering/application/services/add_category_service.rs @@ -19,7 +19,6 @@ use crate::ordering::{ category_aggregate::*, }, }; -use crate::utils::uuid::*; #[automock] #[async_trait::async_trait] @@ -34,7 +33,6 @@ pub struct AddCategoryService { db_store_id_exists: StoreIDExistsDBPortObj, db_category_name_exists_for_store: CategoryNameExistsForStoreDBPortObj, db_category_id_exists: CategoryIDExistsDBPortObj, - get_uuid: GetUUIDInterfaceObj, } #[async_trait::async_trait] @@ -48,27 +46,19 @@ impl AddCategoryUseCase for AddCategoryService { return Err(OrderingError::StoreIDNotFound); } - let mut category_id = self.get_uuid.get_uuid(); - - loop { - if self - .db_category_id_exists - .category_id_exists(&category_id) - .await? - { - category_id = self.get_uuid.get_uuid(); - - continue; - } else { - break; - } + if self + .db_category_id_exists + .category_id_exists(cmd.category_id()) + .await? + { + return Err(OrderingError::DuplicateCategoryID); } let category = CategoryBuilder::default() .name(cmd.name().into()) .description(cmd.description().as_ref().map(|s| s.to_string())) .store_id(*cmd.store_id()) - .category_id(category_id) + .category_id(*cmd.category_id()) .build() .unwrap(); @@ -97,8 +87,9 @@ pub mod tests { use uuid::Uuid; - use crate::utils::uuid::tests::UUID; - use crate::{tests::bdd::*, utils::uuid::tests::mock_get_uuid}; + use crate::tests::bdd::*; + + use crate::ordering::domain::add_category_command::tests::get_add_category_cmd; pub fn mock_add_category_service( times: Option, @@ -111,7 +102,7 @@ pub mod tests { .description(cmd.description().as_ref().map(|s| s.to_string())) .added_by_user(*cmd.adding_by()) .store_id(*cmd.store_id()) - .category_id(UUID) + .category_id(*cmd.category_id()) .build() .unwrap(); @@ -128,14 +119,7 @@ pub mod tests { #[actix_rt::test] async fn test_service_category_doesnt_exist() { - let name = "foo"; - let description = "bar"; - let user_id = UUID; - let store_id = Uuid::new_v4(); - - // description = None - let cmd = AddCategoryCommand::new(name.into(), Some(description.into()), store_id, user_id) - .unwrap(); + let cmd = get_add_category_cmd(); let s = AddCategoryServiceBuilder::default() .db_store_id_exists(mock_store_id_exists_db_port_true(IS_CALLED_ONLY_ONCE)) @@ -143,7 +127,6 @@ pub mod tests { IS_CALLED_ONLY_ONCE, )) .db_category_id_exists(mock_category_id_exists_db_port_false(IS_CALLED_ONLY_ONCE)) - .get_uuid(mock_get_uuid(IS_CALLED_ONLY_ONCE)) .build() .unwrap(); @@ -152,19 +135,12 @@ pub mod tests { assert_eq!(res.description(), cmd.description()); assert_eq!(res.added_by_user(), cmd.adding_by()); assert_eq!(res.store_id(), cmd.store_id()); - assert_eq!(res.category_id(), &UUID); + assert_eq!(res.category_id(), cmd.category_id()); } #[actix_rt::test] async fn test_service_category_name_exists_for_store() { - let name = "foo"; - let description = "bar"; - let user_id = UUID; - let store_id = Uuid::new_v4(); - - // description = None - let cmd = AddCategoryCommand::new(name.into(), Some(description.into()), store_id, user_id) - .unwrap(); + let cmd = get_add_category_cmd(); let s = AddCategoryServiceBuilder::default() .db_store_id_exists(mock_store_id_exists_db_port_true(IS_CALLED_ONLY_ONCE)) @@ -172,7 +148,6 @@ pub mod tests { IS_CALLED_ONLY_ONCE, )) .db_category_id_exists(mock_category_id_exists_db_port_false(IS_CALLED_ONLY_ONCE)) - .get_uuid(mock_get_uuid(IS_CALLED_ONLY_ONCE)) .build() .unwrap(); @@ -184,14 +159,7 @@ pub mod tests { #[actix_rt::test] async fn test_service_store_doesnt_exist() { - let name = "foo"; - let description = "bar"; - let user_id = UUID; - let store_id = Uuid::new_v4(); - - // description = None - let cmd = AddCategoryCommand::new(name.into(), Some(description.into()), store_id, user_id) - .unwrap(); + let cmd = get_add_category_cmd(); let s = AddCategoryServiceBuilder::default() .db_store_id_exists(mock_store_id_exists_db_port_false(IS_CALLED_ONLY_ONCE)) @@ -199,7 +167,6 @@ pub mod tests { IS_NEVER_CALLED, )) .db_category_id_exists(mock_category_id_exists_db_port_false(IS_NEVER_CALLED)) - .get_uuid(mock_get_uuid(IS_NEVER_CALLED)) .build() .unwrap(); diff --git a/src/ordering/domain/add_category_command.rs b/src/ordering/domain/add_category_command.rs index 9c8d0eb..ec116de 100644 --- a/src/ordering/domain/add_category_command.rs +++ b/src/ordering/domain/add_category_command.rs @@ -1,7 +1,100 @@ +//// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +//// +//// SPDX-License-Identifier: AGPL-3.0-or-later +// +//use derive_getters::Getters; +//use derive_more::{Display, Error}; +//use serde::{Deserialize, Serialize}; +//use uuid::Uuid; +// +//#[derive(Debug, Error, Display, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +//pub enum AddCategoryCommandError { +// NameIsEmpty, +//} +// +//#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Getters)] +//pub struct AddCategoryCommand { +// name: String, +// description: Option, +// store_id: Uuid, +// adding_by: Uuid, +//} +// +//impl AddCategoryCommand { +// pub fn new( +// name: String, +// description: Option, +// store_id: Uuid, +// adding_by: Uuid, +// ) -> Result { +// let description: Option = if let Some(description) = description { +// let description = description.trim(); +// if description.is_empty() { +// None +// } else { +// Some(description.to_owned()) +// } +// } else { +// None +// }; +// +// let name = name.trim().to_owned(); +// if name.is_empty() { +// return Err(AddCategoryCommandError::NameIsEmpty); +// } +// +// Ok(Self { +// name, +// store_id, +// description, +// adding_by, +// }) +// } +//} +// +//#[cfg(test)] +//mod tests { +// use super::*; +// +// use crate::utils::uuid::tests::UUID; +// +// #[test] +// fn test_cmd() { +// let name = "foo"; +// let description = "bar"; +// let adding_by = UUID; +// let store_id = Uuid::new_v4(); +// +// // description = None +// let cmd = AddCategoryCommand::new(name.into(), None, store_id, adding_by).unwrap(); +// assert_eq!(cmd.name(), name); +// assert_eq!(cmd.description(), &None); +// assert_eq!(cmd.adding_by(), &adding_by); +// assert_eq!(cmd.store_id(), &store_id); +// +// // description = Some +// let cmd = +// AddCategoryCommand::new(name.into(), Some(description.into()), store_id, adding_by) +// .unwrap(); +// assert_eq!(cmd.name(), name); +// assert_eq!(cmd.description(), &Some(description.to_owned())); +// assert_eq!(cmd.adding_by(), &adding_by); +// assert_eq!(cmd.store_id(), &store_id); +// +// // AddCategoryCommandError::NameIsEmpty +// assert_eq!( +// AddCategoryCommand::new("".into(), Some(description.into()), store_id, adding_by,), +// Err(AddCategoryCommandError::NameIsEmpty) +// ) +// } +//} +// +// // SPDX-FileCopyrightText: 2024 Aravinth Manivannan // // SPDX-License-Identifier: AGPL-3.0-or-later +use derive_builder::Builder; use derive_getters::Getters; use derive_more::{Display, Error}; use serde::{Deserialize, Serialize}; @@ -12,21 +105,22 @@ pub enum AddCategoryCommandError { NameIsEmpty, } -#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Getters)] +#[derive( + Clone, Debug, Builder, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Getters, +)] +#[builder(build_fn(validate = "Self::validate"))] pub struct AddCategoryCommand { + #[builder(setter(custom))] name: String, + #[builder(setter(custom))] description: Option, store_id: Uuid, + category_id: Uuid, adding_by: Uuid, } -impl AddCategoryCommand { - pub fn new( - name: String, - description: Option, - store_id: Uuid, - adding_by: Uuid, - ) -> Result { +impl AddCategoryCommandBuilder { + pub fn description(&mut self, description: Option) -> &mut Self { let description: Option = if let Some(description) = description { let description = description.trim(); if description.is_empty() { @@ -37,27 +131,46 @@ impl AddCategoryCommand { } else { None }; + self.description = Some(description); + self + } + pub fn name(&mut self, name: String) -> &mut Self { let name = name.trim().to_owned(); - if name.is_empty() { - return Err(AddCategoryCommandError::NameIsEmpty); + self.name = Some(name); + self + } + + pub fn validate(&self) -> Result<(), String> { + if self.name.as_ref().unwrap().is_empty() { + return Err(AddCategoryCommandError::NameIsEmpty.to_string()); } - Ok(Self { - name, - store_id, - description, - adding_by, - }) + Ok(()) } } #[cfg(test)] -mod tests { +pub mod tests { use super::*; use crate::utils::uuid::tests::UUID; + pub fn get_add_category_cmd() -> AddCategoryCommand { + let name = "foo"; + let adding_by = UUID; + let store_id = Uuid::new_v4(); + + AddCategoryCommandBuilder::default() + .name(name.into()) + .description(None) + .store_id(store_id) + .adding_by(adding_by) + .category_id(UUID) + .build() + .unwrap() + } + #[test] fn test_cmd() { let name = "foo"; @@ -65,26 +178,41 @@ mod tests { let adding_by = UUID; let store_id = Uuid::new_v4(); - // description = None - let cmd = AddCategoryCommand::new(name.into(), None, store_id, adding_by).unwrap(); + let cmd = AddCategoryCommandBuilder::default() + .name(name.into()) + .description(None) + .store_id(store_id) + .adding_by(adding_by) + .category_id(UUID) + .build() + .unwrap(); assert_eq!(cmd.name(), name); assert_eq!(cmd.description(), &None); assert_eq!(cmd.adding_by(), &adding_by); assert_eq!(cmd.store_id(), &store_id); - // description = Some - let cmd = - AddCategoryCommand::new(name.into(), Some(description.into()), store_id, adding_by) - .unwrap(); + let cmd = AddCategoryCommandBuilder::default() + .name(name.into()) + .description(Some(description.into())) + .store_id(store_id) + .adding_by(adding_by) + .category_id(UUID) + .build() + .unwrap(); + assert_eq!(cmd.name(), name); assert_eq!(cmd.description(), &Some(description.to_owned())); assert_eq!(cmd.adding_by(), &adding_by); assert_eq!(cmd.store_id(), &store_id); // AddCategoryCommandError::NameIsEmpty - assert_eq!( - AddCategoryCommand::new("".into(), Some(description.into()), store_id, adding_by,), - Err(AddCategoryCommandError::NameIsEmpty) - ) + assert!(AddCategoryCommandBuilder::default() + .name("".into()) + .description(Some(description.into())) + .store_id(store_id) + .adding_by(adding_by) + .category_id(UUID) + .build() + .is_err()) } } diff --git a/src/ordering/domain/category_aggregate.rs b/src/ordering/domain/category_aggregate.rs index c14130f..e207da2 100644 --- a/src/ordering/domain/category_aggregate.rs +++ b/src/ordering/domain/category_aggregate.rs @@ -81,8 +81,8 @@ mod aggregate_tests { use std::sync::Arc; use cqrs_es::test::TestFramework; + use tests::get_add_category_cmd; use update_category_service::tests::mock_update_category_service; - use uuid::Uuid; use super::*; use crate::ordering::{ @@ -95,27 +95,19 @@ mod aggregate_tests { }, }; use crate::tests::bdd::*; - use crate::utils::uuid::tests::*; type CategoryTestFramework = TestFramework; #[test] fn test_create_category() { - let name = "category_name"; - let description = Some("category_description".to_string()); - let adding_by = UUID; - let store_id = Uuid::new_v4(); - let category_id = UUID; - - let cmd = - AddCategoryCommand::new(name.into(), description.clone(), store_id, adding_by).unwrap(); + let cmd = get_add_category_cmd(); let expected = CategoryAddedEventBuilder::default() .name(cmd.name().into()) .description(cmd.description().as_ref().map(|s| s.to_string())) .added_by_user(*cmd.adding_by()) .store_id(*cmd.store_id()) - .category_id(category_id) + .category_id(*cmd.category_id()) .build() .unwrap(); let expected = OrderingEvent::CategoryAdded(expected);