Merge pull request 'feat: use an interface for generating UUIDs' (#26) from uuid-interface into master
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Reviewed-on: #26
This commit is contained in:
commit
718eabe28b
5 changed files with 226 additions and 2 deletions
157
src/inventory/application/services/add_category_service.rs
Normal file
157
src/inventory/application/services/add_category_service.rs
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use derive_builder::Builder;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::errors::*;
|
||||||
|
use crate::inventory::{
|
||||||
|
application::port::output::db::{category_id_exists::*, category_name_exists_for_store::*},
|
||||||
|
domain::{
|
||||||
|
add_category_command::AddCategoryCommand,
|
||||||
|
category_added_event::{CategoryAddedEvent, CategoryAddedEventBuilder},
|
||||||
|
category_aggregate::*,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use crate::utils::uuid::*;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait AddCategoryUseCase: Send + Sync {
|
||||||
|
async fn add_category(&self, cmd: AddCategoryCommand) -> InventoryResult<CategoryAddedEvent>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type AddCategoryServiceObj = std::sync::Arc<dyn AddCategoryUseCase>;
|
||||||
|
|
||||||
|
#[derive(Clone, Builder)]
|
||||||
|
pub struct AddCategoryService {
|
||||||
|
db_category_name_exists_for_store: CategoryNameExistsForStoreDBPortObj,
|
||||||
|
db_category_id_exists: CategoryIDExistsDBPortObj,
|
||||||
|
get_uuid: GetUUIDInterfaceObj,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl AddCategoryUseCase for AddCategoryService {
|
||||||
|
async fn add_category(&self, cmd: AddCategoryCommand) -> InventoryResult<CategoryAddedEvent> {
|
||||||
|
let mut category_id = self.get_uuid.get_uuid();
|
||||||
|
let mut category = CategoryBuilder::default()
|
||||||
|
.name(cmd.name().into())
|
||||||
|
.description(cmd.description().as_ref().map(|s| s.to_string()))
|
||||||
|
.store_id(cmd.store_id().clone())
|
||||||
|
.category_id(category_id)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if self
|
||||||
|
.db_category_name_exists_for_store
|
||||||
|
.category_name_exists_for_store(&category)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
return Err(InventoryError::DuplicateCategoryName);
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if self
|
||||||
|
.db_category_id_exists
|
||||||
|
.category_id_exists(&category)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
category_id = self.get_uuid.get_uuid();
|
||||||
|
category = CategoryBuilder::default()
|
||||||
|
.name(cmd.name().into())
|
||||||
|
.description(cmd.description().as_ref().map(|s| s.to_string()))
|
||||||
|
.store_id(cmd.store_id().clone())
|
||||||
|
.category_id(category_id)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(CategoryAddedEventBuilder::default()
|
||||||
|
.name(category.name().into())
|
||||||
|
.description(category.description().as_ref().map(|s| s.to_string()))
|
||||||
|
.added_by_user(cmd.adding_by().into())
|
||||||
|
.store_id(category.store_id().clone())
|
||||||
|
.category_id(category.category_id().clone())
|
||||||
|
.build()
|
||||||
|
.unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::utils::uuid::tests::UUID;
|
||||||
|
use crate::{tests::bdd::*, utils::uuid::tests::mock_get_uuid};
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_service_category_doesnt_exist() {
|
||||||
|
let name = "foo";
|
||||||
|
let description = "bar";
|
||||||
|
let username = "baz";
|
||||||
|
let store_id = Uuid::new_v4();
|
||||||
|
|
||||||
|
// description = None
|
||||||
|
let cmd = AddCategoryCommand::new(
|
||||||
|
name.into(),
|
||||||
|
Some(description.into()),
|
||||||
|
store_id.clone(),
|
||||||
|
username.into(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let s = AddCategoryServiceBuilder::default()
|
||||||
|
.db_category_name_exists_for_store(mock_category_name_exists_for_store_db_port_false(
|
||||||
|
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();
|
||||||
|
|
||||||
|
let res = s.add_category(cmd.clone()).await.unwrap();
|
||||||
|
assert_eq!(res.name(), cmd.name());
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_service_category_name_exists_for_store() {
|
||||||
|
let name = "foo";
|
||||||
|
let description = "bar";
|
||||||
|
let username = "baz";
|
||||||
|
let store_id = Uuid::new_v4();
|
||||||
|
|
||||||
|
// description = None
|
||||||
|
let cmd = AddCategoryCommand::new(
|
||||||
|
name.into(),
|
||||||
|
Some(description.into()),
|
||||||
|
store_id.clone(),
|
||||||
|
username.into(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let s = AddCategoryServiceBuilder::default()
|
||||||
|
.db_category_name_exists_for_store(mock_category_name_exists_for_store_db_port_true(
|
||||||
|
IS_CALLED_ONLY_ONCE,
|
||||||
|
))
|
||||||
|
.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::DuplicateCategoryName)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ use crate::inventory::{
|
||||||
store_aggregate::*,
|
store_aggregate::*,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use crate::utils::uuid::*;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait AddStoreUseCase: Send + Sync {
|
pub trait AddStoreUseCase: Send + Sync {
|
||||||
|
@ -25,12 +26,13 @@ pub type AddStoreServiceObj = std::sync::Arc<dyn AddStoreUseCase>;
|
||||||
#[derive(Clone, Builder)]
|
#[derive(Clone, Builder)]
|
||||||
pub struct AddStoreService {
|
pub struct AddStoreService {
|
||||||
db_store_id_exists: StoreIDExistsDBPortObj,
|
db_store_id_exists: StoreIDExistsDBPortObj,
|
||||||
|
get_uuid: GetUUIDInterfaceObj,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
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 = Uuid::new_v4();
|
let mut store_id = self.get_uuid.get_uuid();
|
||||||
let mut store = StoreBuilder::default()
|
let mut 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()))
|
||||||
|
@ -40,7 +42,7 @@ impl AddStoreUseCase for AddStoreService {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
loop {
|
loop {
|
||||||
if self.db_store_id_exists.store_id_exists(&store).await? {
|
if self.db_store_id_exists.store_id_exists(&store).await? {
|
||||||
store_id = Uuid::new_v4();
|
store_id = self.get_uuid.get_uuid();
|
||||||
store = StoreBuilder::default()
|
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()))
|
||||||
|
@ -68,6 +70,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use crate::tests::bdd::*;
|
use crate::tests::bdd::*;
|
||||||
|
use crate::utils::uuid::tests::*;
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_service_store_id_doesnt_exist() {
|
async fn test_service_store_id_doesnt_exist() {
|
||||||
|
@ -80,6 +83,7 @@ mod tests {
|
||||||
|
|
||||||
let s = AddStoreServiceBuilder::default()
|
let s = AddStoreServiceBuilder::default()
|
||||||
.db_store_id_exists(mock_store_id_exists_db_port_false(IS_CALLED_ONLY_ONCE))
|
.db_store_id_exists(mock_store_id_exists_db_port_false(IS_CALLED_ONLY_ONCE))
|
||||||
|
.get_uuid(mock_get_uuid(IS_CALLED_ONLY_ONCE))
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -87,5 +91,6 @@ mod tests {
|
||||||
assert_eq!(res.name(), cmd.name());
|
assert_eq!(res.name(), cmd.name());
|
||||||
assert_eq!(res.address(), cmd.address());
|
assert_eq!(res.address(), cmd.address());
|
||||||
assert_eq!(res.owner(), cmd.owner());
|
assert_eq!(res.owner(), cmd.owner());
|
||||||
|
assert_eq!(res.store_id(), &UUID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,7 @@ async fn main() {
|
||||||
)
|
)
|
||||||
// .configure(auth::adapter::load_adapters(db.pool.clone(), &settings))
|
// .configure(auth::adapter::load_adapters(db.pool.clone(), &settings))
|
||||||
.configure(utils::random_string::GenerateRandomString::inject())
|
.configure(utils::random_string::GenerateRandomString::inject())
|
||||||
|
.configure(utils::uuid::GenerateUUID::inject())
|
||||||
})
|
})
|
||||||
.bind(&socket_addr)
|
.bind(&socket_addr)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
|
@ -3,3 +3,4 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
pub mod random_string;
|
pub mod random_string;
|
||||||
|
pub mod uuid;
|
||||||
|
|
60
src/utils/uuid.rs
Normal file
60
src/utils/uuid.rs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use actix_web::web;
|
||||||
|
use mockall::predicate::*;
|
||||||
|
use mockall::*;
|
||||||
|
#[cfg(test)]
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use tests::*;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub type GetUUIDInterfaceObj = Arc<dyn GetUUID>;
|
||||||
|
pub type WebGetUUIDInterfaceObj = web::Data<GetUUIDInterfaceObj>;
|
||||||
|
|
||||||
|
#[automock]
|
||||||
|
pub trait GetUUID: Send + Sync {
|
||||||
|
fn get_uuid(&self) -> Uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GenerateUUID;
|
||||||
|
impl GetUUID for GenerateUUID {
|
||||||
|
fn get_uuid(&self) -> Uuid {
|
||||||
|
Uuid::new_v4()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GenerateUUID {
|
||||||
|
pub fn inject() -> impl FnOnce(&mut web::ServiceConfig) {
|
||||||
|
let g = WebGetUUIDInterfaceObj::new(Arc::new(GenerateUUID));
|
||||||
|
let f = move |cfg: &mut web::ServiceConfig| {
|
||||||
|
cfg.app_data(g);
|
||||||
|
};
|
||||||
|
|
||||||
|
Box::new(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
use uuid::uuid;
|
||||||
|
pub const UUID: Uuid = uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8");
|
||||||
|
|
||||||
|
pub fn mock_get_uuid(times: Option<usize>) -> Arc<dyn GetUUID> {
|
||||||
|
let mut m = MockGetUUID::new();
|
||||||
|
|
||||||
|
if let Some(times) = times {
|
||||||
|
m.expect_get_uuid().times(times).return_const(UUID.clone());
|
||||||
|
} else {
|
||||||
|
m.expect_get_uuid().return_const(UUID.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Arc::new(m)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue