Merge pull request 'fix: consistency check for Store before creating Category' (#48) from store-exists-check into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

Reviewed-on: #48
This commit is contained in:
Aravinth Manivannan 2024-07-16 17:39:00 +05:30
commit 9070bea10d
4 changed files with 60 additions and 25 deletions

View file

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net> // SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
// //
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
use uuid::Uuid;
use super::InventoryDBPostgresAdapter; use super::InventoryDBPostgresAdapter;
use crate::inventory::application::port::output::db::{errors::*, store_id_exists::*}; 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] #[async_trait::async_trait]
impl StoreIDExistsDBPort for InventoryDBPostgresAdapter { impl StoreIDExistsDBPort for InventoryDBPostgresAdapter {
async fn store_id_exists(&self, s: &Store) -> InventoryDBResult<bool> { async fn store_id_exists(&self, store_id: &Uuid) -> InventoryDBResult<bool> {
let res = sqlx::query!( let res = sqlx::query!(
"SELECT EXISTS ( "SELECT EXISTS (
SELECT 1 SELECT 1
@ -16,7 +17,7 @@ impl StoreIDExistsDBPort for InventoryDBPostgresAdapter {
WHERE WHERE
store_id = $1 store_id = $1
);", );",
s.store_id(), store_id
) )
.fetch_one(&self.pool) .fetch_one(&self.pool)
.await?; .await?;
@ -73,12 +74,12 @@ pub mod tests {
.unwrap(); .unwrap();
// state doesn't exist // 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; create_dummy_store_record(&store, &db).await;
// state exists // state exists
assert!(db.store_id_exists(&store).await.unwrap()); assert!(db.store_id_exists(store.store_id()).await.unwrap());
settings.drop_db().await; settings.drop_db().await;
} }

View file

@ -4,6 +4,7 @@
use mockall::predicate::*; use mockall::predicate::*;
use mockall::*; use mockall::*;
use uuid::Uuid;
use crate::inventory::domain::store_aggregate::Store; use crate::inventory::domain::store_aggregate::Store;
@ -15,7 +16,7 @@ pub use tests::*;
#[automock] #[automock]
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait StoreIDExistsDBPort: Send + Sync { pub trait StoreIDExistsDBPort: Send + Sync {
async fn store_id_exists(&self, s: &Store) -> InventoryDBResult<bool>; async fn store_id_exists(&self, store_id: &Uuid) -> InventoryDBResult<bool>;
} }
pub type StoreIDExistsDBPortObj = std::sync::Arc<dyn StoreIDExistsDBPort>; pub type StoreIDExistsDBPortObj = std::sync::Arc<dyn StoreIDExistsDBPort>;

View file

@ -10,7 +10,9 @@ use mockall::*;
use super::errors::*; use super::errors::*;
use crate::inventory::{ 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::{ domain::{
add_category_command::AddCategoryCommand, add_category_command::AddCategoryCommand,
category_added_event::{CategoryAddedEvent, CategoryAddedEventBuilder}, category_added_event::{CategoryAddedEvent, CategoryAddedEventBuilder},
@ -29,7 +31,7 @@ pub type AddCategoryServiceObj = Arc<dyn AddCategoryUseCase>;
#[derive(Clone, Builder)] #[derive(Clone, Builder)]
pub struct AddCategoryService { pub struct AddCategoryService {
// TODO: check if store ID exists db_store_id_exists: StoreIDExistsDBPortObj,
db_category_name_exists_for_store: CategoryNameExistsForStoreDBPortObj, db_category_name_exists_for_store: CategoryNameExistsForStoreDBPortObj,
db_category_id_exists: CategoryIDExistsDBPortObj, db_category_id_exists: CategoryIDExistsDBPortObj,
get_uuid: GetUUIDInterfaceObj, get_uuid: GetUUIDInterfaceObj,
@ -38,6 +40,14 @@ pub struct AddCategoryService {
#[async_trait::async_trait] #[async_trait::async_trait]
impl AddCategoryUseCase for AddCategoryService { impl AddCategoryUseCase for AddCategoryService {
async fn add_category(&self, cmd: AddCategoryCommand) -> InventoryResult<CategoryAddedEvent> { async fn add_category(&self, cmd: AddCategoryCommand) -> InventoryResult<CategoryAddedEvent> {
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(); let mut category_id = self.get_uuid.get_uuid();
loop { loop {
@ -128,6 +138,7 @@ pub mod tests {
.unwrap(); .unwrap();
let s = AddCategoryServiceBuilder::default() 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( .db_category_name_exists_for_store(mock_category_name_exists_for_store_db_port_false(
IS_CALLED_ONLY_ONCE, IS_CALLED_ONLY_ONCE,
)) ))
@ -156,6 +167,7 @@ pub mod tests {
.unwrap(); .unwrap();
let s = AddCategoryServiceBuilder::default() 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( .db_category_name_exists_for_store(mock_category_name_exists_for_store_db_port_true(
IS_CALLED_ONLY_ONCE, IS_CALLED_ONLY_ONCE,
)) ))
@ -169,4 +181,31 @@ pub mod tests {
Err(InventoryError::DuplicateCategoryName) 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)
)
}
} }

View file

@ -38,7 +38,17 @@ pub struct AddStoreService {
impl AddStoreUseCase for AddStoreService { impl AddStoreUseCase for AddStoreService {
async fn add_store(&self, cmd: AddStoreCommand) -> InventoryResult<StoreAddedEvent> { async fn add_store(&self, cmd: AddStoreCommand) -> InventoryResult<StoreAddedEvent> {
let mut store_id = self.get_uuid.get_uuid(); 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()) .name(cmd.name().into())
.address(cmd.address().as_ref().map(|s| s.to_string())) .address(cmd.address().as_ref().map(|s| s.to_string()))
.owner(*cmd.owner()) .owner(*cmd.owner())
@ -50,22 +60,6 @@ impl AddStoreUseCase for AddStoreService {
return Err(InventoryError::DuplicateStoreName); 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() Ok(StoreAddedEventBuilder::default()
.name(store.name().into()) .name(store.name().into())
.address(store.address().as_ref().map(|s| s.to_string())) .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 cmd = AddStoreCommand::new(name.into(), Some(address.into()), owner).unwrap();
let s = AddStoreServiceBuilder::default() 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)) .db_store_name_exists(mock_store_name_exists_db_port_true(IS_CALLED_ONLY_ONCE))
.get_uuid(mock_get_uuid(IS_CALLED_ONLY_ONCE)) .get_uuid(mock_get_uuid(IS_CALLED_ONLY_ONCE))
.build() .build()