fix: consistency check for Store before creating Category #48
4 changed files with 60 additions and 25 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
|
@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue