From ae8615b8eef8aaf5be5bd37346e4e0c4d1d18f14 Mon Sep 17 00:00:00 2001 From: Aravinth Manivannan Date: Tue, 16 Jul 2024 17:28:30 +0530 Subject: [PATCH 1/2] fix: use UUID instead of Store obj to check constraint violations w DB port --- .../output/db/postgres/store_id_exists.rs | 9 +++--- .../port/output/db/store_id_exists.rs | 3 +- .../application/services/add_store_service.rs | 30 ++++++++----------- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/inventory/adapters/output/db/postgres/store_id_exists.rs b/src/inventory/adapters/output/db/postgres/store_id_exists.rs index 64cb992..8cdcc06 100644 --- a/src/inventory/adapters/output/db/postgres/store_id_exists.rs +++ b/src/inventory/adapters/output/db/postgres/store_id_exists.rs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2024 Aravinth Manivannan // // SPDX-License-Identifier: AGPL-3.0-or-later +use uuid::Uuid; use super::InventoryDBPostgresAdapter; use crate::inventory::application::port::output::db::{errors::*, store_id_exists::*}; @@ -8,7 +9,7 @@ use crate::inventory::domain::store_aggregate::*; #[async_trait::async_trait] impl StoreIDExistsDBPort for InventoryDBPostgresAdapter { - async fn store_id_exists(&self, s: &Store) -> InventoryDBResult { + async fn store_id_exists(&self, store_id: &Uuid) -> InventoryDBResult { let res = sqlx::query!( "SELECT EXISTS ( SELECT 1 @@ -16,7 +17,7 @@ impl StoreIDExistsDBPort for InventoryDBPostgresAdapter { WHERE store_id = $1 );", - s.store_id(), + store_id ) .fetch_one(&self.pool) .await?; @@ -73,12 +74,12 @@ pub mod tests { .unwrap(); // state doesn't exist - assert!(!db.store_id_exists(&store).await.unwrap()); + assert!(!db.store_id_exists(store.store_id()).await.unwrap()); create_dummy_store_record(&store, &db).await; // state exists - assert!(db.store_id_exists(&store).await.unwrap()); + assert!(db.store_id_exists(store.store_id()).await.unwrap()); settings.drop_db().await; } diff --git a/src/inventory/application/port/output/db/store_id_exists.rs b/src/inventory/application/port/output/db/store_id_exists.rs index a1321af..1c8733e 100644 --- a/src/inventory/application/port/output/db/store_id_exists.rs +++ b/src/inventory/application/port/output/db/store_id_exists.rs @@ -4,6 +4,7 @@ use mockall::predicate::*; use mockall::*; +use uuid::Uuid; use crate::inventory::domain::store_aggregate::Store; @@ -15,7 +16,7 @@ pub use tests::*; #[automock] #[async_trait::async_trait] pub trait StoreIDExistsDBPort: Send + Sync { - async fn store_id_exists(&self, s: &Store) -> InventoryDBResult; + async fn store_id_exists(&self, store_id: &Uuid) -> InventoryDBResult; } pub type StoreIDExistsDBPortObj = std::sync::Arc; diff --git a/src/inventory/application/services/add_store_service.rs b/src/inventory/application/services/add_store_service.rs index 75d94d8..30d6912 100644 --- a/src/inventory/application/services/add_store_service.rs +++ b/src/inventory/application/services/add_store_service.rs @@ -38,7 +38,17 @@ pub struct AddStoreService { impl AddStoreUseCase for AddStoreService { async fn add_store(&self, cmd: AddStoreCommand) -> InventoryResult { let mut store_id = self.get_uuid.get_uuid(); - let mut store = StoreBuilder::default() + + loop { + if self.db_store_id_exists.store_id_exists(&store_id).await? { + store_id = self.get_uuid.get_uuid(); + continue; + } else { + break; + } + } + + let store = StoreBuilder::default() .name(cmd.name().into()) .address(cmd.address().as_ref().map(|s| s.to_string())) .owner(*cmd.owner()) @@ -50,22 +60,6 @@ impl AddStoreUseCase for AddStoreService { return Err(InventoryError::DuplicateStoreName); } - loop { - if self.db_store_id_exists.store_id_exists(&store).await? { - store_id = self.get_uuid.get_uuid(); - store = StoreBuilder::default() - .name(cmd.name().into()) - .address(cmd.address().as_ref().map(|s| s.to_string())) - .owner(*cmd.owner()) - .store_id(store_id) - .build() - .unwrap(); - continue; - } else { - break; - } - } - Ok(StoreAddedEventBuilder::default() .name(store.name().into()) .address(store.address().as_ref().map(|s| s.to_string())) @@ -141,7 +135,7 @@ pub mod tests { let cmd = AddStoreCommand::new(name.into(), Some(address.into()), owner).unwrap(); let s = AddStoreServiceBuilder::default() - .db_store_id_exists(mock_store_id_exists_db_port_false(IS_NEVER_CALLED)) + .db_store_id_exists(mock_store_id_exists_db_port_false(IS_CALLED_ONLY_ONCE)) .db_store_name_exists(mock_store_name_exists_db_port_true(IS_CALLED_ONLY_ONCE)) .get_uuid(mock_get_uuid(IS_CALLED_ONLY_ONCE)) .build() From 3a65f2ca179f7273c0757628e8236bd842f00235 Mon Sep 17 00:00:00 2001 From: Aravinth Manivannan Date: Tue, 16 Jul 2024 17:30:34 +0530 Subject: [PATCH 2/2] fix: consistency check for Store before creating Category --- .../services/add_category_service.rs | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/inventory/application/services/add_category_service.rs b/src/inventory/application/services/add_category_service.rs index 4fe8c78..8e16448 100644 --- a/src/inventory/application/services/add_category_service.rs +++ b/src/inventory/application/services/add_category_service.rs @@ -10,7 +10,9 @@ use mockall::*; use super::errors::*; use crate::inventory::{ - application::port::output::db::{category_id_exists::*, category_name_exists_for_store::*}, + application::port::output::db::{ + category_id_exists::*, category_name_exists_for_store::*, store_id_exists::*, + }, domain::{ add_category_command::AddCategoryCommand, category_added_event::{CategoryAddedEvent, CategoryAddedEventBuilder}, @@ -29,7 +31,7 @@ pub type AddCategoryServiceObj = Arc; #[derive(Clone, Builder)] pub struct AddCategoryService { - // TODO: check if store ID exists + db_store_id_exists: StoreIDExistsDBPortObj, db_category_name_exists_for_store: CategoryNameExistsForStoreDBPortObj, db_category_id_exists: CategoryIDExistsDBPortObj, get_uuid: GetUUIDInterfaceObj, @@ -38,6 +40,14 @@ pub struct AddCategoryService { #[async_trait::async_trait] impl AddCategoryUseCase for AddCategoryService { async fn add_category(&self, cmd: AddCategoryCommand) -> InventoryResult { + if !self + .db_store_id_exists + .store_id_exists(cmd.store_id()) + .await? + { + return Err(InventoryError::StoreIDNotFound); + } + let mut category_id = self.get_uuid.get_uuid(); loop { @@ -128,6 +138,7 @@ pub mod tests { .unwrap(); let s = AddCategoryServiceBuilder::default() + .db_store_id_exists(mock_store_id_exists_db_port_true(IS_CALLED_ONLY_ONCE)) .db_category_name_exists_for_store(mock_category_name_exists_for_store_db_port_false( IS_CALLED_ONLY_ONCE, )) @@ -156,6 +167,7 @@ pub mod tests { .unwrap(); let s = AddCategoryServiceBuilder::default() + .db_store_id_exists(mock_store_id_exists_db_port_true(IS_CALLED_ONLY_ONCE)) .db_category_name_exists_for_store(mock_category_name_exists_for_store_db_port_true( IS_CALLED_ONLY_ONCE, )) @@ -169,4 +181,31 @@ pub mod tests { Err(InventoryError::DuplicateCategoryName) ) } + + #[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 s = AddCategoryServiceBuilder::default() + .db_store_id_exists(mock_store_id_exists_db_port_false(IS_CALLED_ONLY_ONCE)) + .db_category_name_exists_for_store(mock_category_name_exists_for_store_db_port_false( + 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(); + + assert_eq!( + s.add_category(cmd.clone()).await, + Err(InventoryError::StoreIDNotFound) + ) + } }