// SPDX-FileCopyrightText: 2024 Aravinth Manivannan // // 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::ordering::{ application::port::output::db::{ customization_id_exists::*, customization_name_exists_for_product::*, product_id_exists::*, }, domain::{ customization_aggregate::*, customization_updated_event::*, update_customization_command::*, }, }; use crate::utils::uuid::*; #[automock] #[async_trait::async_trait] pub trait UpdateCustomizationUseCase: Send + Sync { async fn update_customization( &self, cmd: UpdateCustomizationCommand, ) -> OrderingResult; } pub type UpdateCustomizationServiceObj = Arc; #[derive(Clone, Builder)] pub struct UpdateCustomizationService { // TODO: check if product ID exists db_customization_name_exists_for_product: CustomizationNameExistsForProductDBPortObj, db_customization_id_exists: CustomizationIDExistsDBPortObj, db_product_id_exists: ProductIDExistsDBPortObj, } #[async_trait::async_trait] impl UpdateCustomizationUseCase for UpdateCustomizationService { async fn update_customization( &self, cmd: UpdateCustomizationCommand, ) -> OrderingResult { if !self .db_customization_id_exists .customization_id_exists(cmd.old_customization().customization_id()) .await? { return Err(OrderingError::CustomizationIDNotFound); } if !self .db_product_id_exists .product_id_exists(cmd.old_customization().product_id()) .await? { return Err(OrderingError::ProductIDNotFound); } let updated_customization = CustomizationBuilder::default() .name(cmd.name().into()) .product_id(*cmd.old_customization().product_id()) .customization_id(*cmd.old_customization().customization_id()) .deleted(*cmd.old_customization().deleted()) .build() .unwrap(); if updated_customization.name() != cmd.old_customization().name() { if self .db_customization_name_exists_for_product .customization_name_exists_for_product(&updated_customization) .await? { return Err(OrderingError::DuplicateCustomizationName); } } Ok(CustomizationUpdatedEventBuilder::default() .added_by_user(*cmd.adding_by()) .old_customization(cmd.old_customization().clone()) .new_customization(updated_customization) .build() .unwrap()) } } #[cfg(test)] pub mod tests { use super::*; use crate::ordering::domain::customization_updated_event; use crate::ordering::domain::update_customization_command::tests::get_update_customization_command; use crate::tests::bdd::*; pub fn mock_update_customization_service( times: Option, cmd: UpdateCustomizationCommand, ) -> UpdateCustomizationServiceObj { let mut m = MockUpdateCustomizationUseCase::new(); let res = customization_updated_event::tests::get_customization_updated_event_from_command(&cmd); if let Some(times) = times { m.expect_update_customization() .times(times) .returning(move |_| Ok(res.clone())); } else { m.expect_update_customization() .returning(move |_| Ok(res.clone())); } Arc::new(m) } #[actix_rt::test] async fn test_service() { let cmd = get_update_customization_command(); let s = UpdateCustomizationServiceBuilder::default() .db_product_id_exists(mock_product_id_exists_db_port_true(IS_CALLED_ONLY_ONCE)) .db_customization_name_exists_for_product( mock_customization_name_exists_for_product_db_port_false(IS_CALLED_ONLY_ONCE), ) .db_customization_id_exists(mock_customization_id_exists_db_port_true( IS_CALLED_ONLY_ONCE, )) .build() .unwrap(); let res = s.update_customization(cmd.clone()).await.unwrap(); assert_eq!(res.new_customization().name(), cmd.name()); assert_eq!( res.new_customization().product_id(), cmd.old_customization().product_id() ); assert_eq!( res.new_customization().customization_id(), cmd.old_customization().customization_id() ); assert_eq!(res.old_customization(), cmd.old_customization()); assert_eq!(res.added_by_user(), cmd.adding_by()); } #[actix_rt::test] async fn test_service_product_doesnt_exist() { let cmd = get_update_customization_command(); let s = UpdateCustomizationServiceBuilder::default() .db_product_id_exists(mock_product_id_exists_db_port_false(IS_CALLED_ONLY_ONCE)) .db_customization_name_exists_for_product( mock_customization_name_exists_for_product_db_port_false(IS_NEVER_CALLED), ) .db_customization_id_exists(mock_customization_id_exists_db_port_true( IS_CALLED_ONLY_ONCE, )) .build() .unwrap(); assert_eq!( s.update_customization(cmd.clone()).await, Err(OrderingError::ProductIDNotFound) ); } #[actix_rt::test] async fn test_customization_id_not_found() { let cmd = get_update_customization_command(); let s = UpdateCustomizationServiceBuilder::default() .db_product_id_exists(mock_product_id_exists_db_port_true(IS_NEVER_CALLED)) .db_customization_name_exists_for_product( mock_customization_name_exists_for_product_db_port_false(IS_NEVER_CALLED), ) .db_customization_id_exists(mock_customization_id_exists_db_port_false( IS_CALLED_ONLY_ONCE, )) .build() .unwrap(); assert_eq!( s.update_customization(cmd.clone()).await, Err(OrderingError::CustomizationIDNotFound) ); } #[actix_rt::test] async fn test_duplicate_new_name() { let cmd = get_update_customization_command(); let s = UpdateCustomizationServiceBuilder::default() .db_product_id_exists(mock_product_id_exists_db_port_true(IS_CALLED_ONLY_ONCE)) .db_customization_name_exists_for_product( mock_customization_name_exists_for_product_db_port_true(IS_CALLED_ONLY_ONCE), ) .db_customization_id_exists(mock_customization_id_exists_db_port_true( IS_CALLED_ONLY_ONCE, )) .build() .unwrap(); assert_eq!( s.update_customization(cmd.clone()).await, Err(OrderingError::DuplicateCustomizationName) ); } }