feat: impl add and update store services for identity domain
This commit is contained in:
parent
3d57a26d34
commit
6dbeded24a
2 changed files with 298 additions and 0 deletions
152
src/identity/application/services/add_store_service.rs
Normal file
152
src/identity/application/services/add_store_service.rs
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use derive_builder::Builder;
|
||||||
|
use mockall::predicate::*;
|
||||||
|
use mockall::*;
|
||||||
|
|
||||||
|
use super::errors::*;
|
||||||
|
use crate::identity::{
|
||||||
|
application::port::output::db::{store_id_exists::*, store_name_exists::*},
|
||||||
|
domain::{
|
||||||
|
add_store_command::*,
|
||||||
|
store_added_event::{StoreAddedEvent, StoreAddedEventBuilder},
|
||||||
|
store_aggregate::*,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[automock]
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait AddStoreUseCase: Send + Sync {
|
||||||
|
async fn add_store(&self, cmd: AddStoreCommand) -> IdentityResult<StoreAddedEvent>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type AddStoreServiceObj = Arc<dyn AddStoreUseCase>;
|
||||||
|
|
||||||
|
#[derive(Clone, Builder)]
|
||||||
|
pub struct AddStoreService {
|
||||||
|
db_store_id_exists: StoreIDExistsDBPortObj,
|
||||||
|
db_store_name_exists: StoreNameExistsDBPortObj,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl AddStoreUseCase for AddStoreService {
|
||||||
|
async fn add_store(&self, cmd: AddStoreCommand) -> IdentityResult<StoreAddedEvent> {
|
||||||
|
if self
|
||||||
|
.db_store_id_exists
|
||||||
|
.store_id_exists(cmd.store_id())
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
return Err(IdentityError::DuplicateStoreID);
|
||||||
|
}
|
||||||
|
|
||||||
|
let store = StoreBuilder::default()
|
||||||
|
.name(cmd.name().into())
|
||||||
|
.address(cmd.address().as_ref().map(|s| s.to_string()))
|
||||||
|
.owner(*cmd.owner())
|
||||||
|
.store_id(*cmd.store_id())
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if self.db_store_name_exists.store_name_exists(&store).await? {
|
||||||
|
return Err(IdentityError::DuplicateStoreName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(StoreAddedEventBuilder::default()
|
||||||
|
.name(store.name().into())
|
||||||
|
.address(store.address().as_ref().map(|s| s.to_string()))
|
||||||
|
.owner(*cmd.owner())
|
||||||
|
.store_id(*cmd.store_id())
|
||||||
|
.build()
|
||||||
|
.unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use crate::tests::bdd::*;
|
||||||
|
use crate::utils::uuid::tests::*;
|
||||||
|
|
||||||
|
pub fn mock_add_store_service(
|
||||||
|
times: Option<usize>,
|
||||||
|
cmd: AddStoreCommand,
|
||||||
|
) -> AddStoreServiceObj {
|
||||||
|
let mut m = MockAddStoreUseCase::new();
|
||||||
|
|
||||||
|
let res = StoreAddedEventBuilder::default()
|
||||||
|
.name(cmd.name().into())
|
||||||
|
.address(cmd.address().as_ref().map(|s| s.to_string()))
|
||||||
|
.owner(*cmd.owner())
|
||||||
|
.store_id(*cmd.store_id())
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if let Some(times) = times {
|
||||||
|
m.expect_add_store()
|
||||||
|
.times(times)
|
||||||
|
.returning(move |_| Ok(res.clone()));
|
||||||
|
} else {
|
||||||
|
m.expect_add_store().returning(move |_| Ok(res.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Arc::new(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_service_store_id_doesnt_exist() {
|
||||||
|
let name = "foo";
|
||||||
|
let address = "bar";
|
||||||
|
let owner = UUID;
|
||||||
|
|
||||||
|
let cmd = AddStoreCommandBuilder::default()
|
||||||
|
.name(name.into())
|
||||||
|
.address(Some(address.into()))
|
||||||
|
.owner(owner)
|
||||||
|
.store_id(UUID)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let s = AddStoreServiceBuilder::default()
|
||||||
|
.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_false(IS_CALLED_ONLY_ONCE))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let res = s.add_store(cmd.clone()).await.unwrap();
|
||||||
|
assert_eq!(res.name(), cmd.name());
|
||||||
|
assert_eq!(res.address(), cmd.address());
|
||||||
|
assert_eq!(res.owner(), cmd.owner());
|
||||||
|
assert_eq!(res.store_id(), cmd.store_id());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_service_store_name_exists() {
|
||||||
|
let name = "foo";
|
||||||
|
let address = "bar";
|
||||||
|
let owner = UUID;
|
||||||
|
|
||||||
|
let cmd = AddStoreCommandBuilder::default()
|
||||||
|
.name(name.into())
|
||||||
|
.address(Some(address.into()))
|
||||||
|
.owner(owner)
|
||||||
|
.store_id(UUID)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let s = AddStoreServiceBuilder::default()
|
||||||
|
.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))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
s.add_store(cmd.clone()).await,
|
||||||
|
Err(IdentityError::DuplicateStoreName)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
146
src/identity/application/services/update_store_service.rs
Normal file
146
src/identity/application/services/update_store_service.rs
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use derive_builder::Builder;
|
||||||
|
use mockall::predicate::*;
|
||||||
|
use mockall::*;
|
||||||
|
|
||||||
|
use super::errors::*;
|
||||||
|
use crate::identity::{
|
||||||
|
application::port::output::db::{store_id_exists::*, store_name_exists::*},
|
||||||
|
domain::{
|
||||||
|
store_aggregate::*, store_updated_event::*, update_store_command::UpdateStoreCommand,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use crate::utils::uuid::*;
|
||||||
|
|
||||||
|
#[automock]
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait UpdateStoreUseCase: Send + Sync {
|
||||||
|
async fn update_store(&self, cmd: UpdateStoreCommand) -> IdentityResult<StoreUpdatedEvent>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type UpdateStoreServiceObj = Arc<dyn UpdateStoreUseCase>;
|
||||||
|
|
||||||
|
#[derive(Clone, Builder)]
|
||||||
|
pub struct UpdateStoreService {
|
||||||
|
db_store_id_exists: StoreIDExistsDBPortObj,
|
||||||
|
db_store_name_exists: StoreNameExistsDBPortObj,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl UpdateStoreUseCase for UpdateStoreService {
|
||||||
|
async fn update_store(&self, cmd: UpdateStoreCommand) -> IdentityResult<StoreUpdatedEvent> {
|
||||||
|
if !self
|
||||||
|
.db_store_id_exists
|
||||||
|
.store_id_exists(cmd.old_store().store_id())
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
return Err(IdentityError::StoreIDNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
let store = StoreBuilder::default()
|
||||||
|
.name(cmd.name().into())
|
||||||
|
.address(cmd.address().as_ref().map(|s| s.to_string()))
|
||||||
|
.owner(*cmd.owner())
|
||||||
|
.store_id(*cmd.old_store().store_id())
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if cmd.name() != cmd.old_store().name() {
|
||||||
|
if self.db_store_name_exists.store_name_exists(&store).await? {
|
||||||
|
return Err(IdentityError::DuplicateStoreName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(StoreUpdatedEventBuilder::default()
|
||||||
|
.added_by_user(*cmd.adding_by())
|
||||||
|
.new_store(store)
|
||||||
|
.old_store(cmd.old_store().clone())
|
||||||
|
.build()
|
||||||
|
.unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use crate::identity::domain::store_updated_event::tests::get_store_updated_event_from_command;
|
||||||
|
use crate::identity::domain::update_store_command::tests::get_update_store_cmd;
|
||||||
|
use crate::tests::bdd::*;
|
||||||
|
use crate::utils::uuid::tests::*;
|
||||||
|
|
||||||
|
pub fn mock_update_store_service(
|
||||||
|
times: Option<usize>,
|
||||||
|
cmd: UpdateStoreCommand,
|
||||||
|
) -> UpdateStoreServiceObj {
|
||||||
|
let mut m = MockUpdateStoreUseCase::new();
|
||||||
|
|
||||||
|
let res = get_store_updated_event_from_command(&cmd);
|
||||||
|
|
||||||
|
if let Some(times) = times {
|
||||||
|
m.expect_update_store()
|
||||||
|
.times(times)
|
||||||
|
.returning(move |_| Ok(res.clone()));
|
||||||
|
} else {
|
||||||
|
m.expect_update_store().returning(move |_| Ok(res.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Arc::new(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_service() {
|
||||||
|
let cmd = get_update_store_cmd();
|
||||||
|
|
||||||
|
let s = UpdateStoreServiceBuilder::default()
|
||||||
|
.db_store_id_exists(mock_store_id_exists_db_port_true(IS_CALLED_ONLY_ONCE))
|
||||||
|
.db_store_name_exists(mock_store_name_exists_db_port_false(IS_CALLED_ONLY_ONCE))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let res = s.update_store(cmd.clone()).await.unwrap();
|
||||||
|
assert_eq!(res.new_store().name(), cmd.name());
|
||||||
|
assert_eq!(res.new_store().address(), cmd.address());
|
||||||
|
assert_eq!(res.new_store().owner(), cmd.owner());
|
||||||
|
assert_eq!(res.new_store().store_id(), cmd.old_store().store_id());
|
||||||
|
assert_eq!(res.old_store(), cmd.old_store());
|
||||||
|
assert_eq!(res.added_by_user(), cmd.adding_by());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_service_store_name_exists() {
|
||||||
|
let cmd = get_update_store_cmd();
|
||||||
|
|
||||||
|
let s = UpdateStoreServiceBuilder::default()
|
||||||
|
.db_store_id_exists(mock_store_id_exists_db_port_true(IS_CALLED_ONLY_ONCE))
|
||||||
|
.db_store_name_exists(mock_store_name_exists_db_port_true(IS_CALLED_ONLY_ONCE))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
s.update_store(cmd.clone()).await,
|
||||||
|
Err(IdentityError::DuplicateStoreName)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_service_store_id_doesnt_exist() {
|
||||||
|
let cmd = get_update_store_cmd();
|
||||||
|
|
||||||
|
let s = UpdateStoreServiceBuilder::default()
|
||||||
|
.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_false(IS_NEVER_CALLED))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
s.update_store(cmd.clone()).await,
|
||||||
|
Err(IdentityError::StoreIDNotFound)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue