diff --git a/src/ordering/application/services/delete_line_item_service.rs b/src/ordering/application/services/delete_line_item_service.rs new file mode 100644 index 0000000..493e127 --- /dev/null +++ b/src/ordering/application/services/delete_line_item_service.rs @@ -0,0 +1,132 @@ +// 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 time::OffsetDateTime; + +use super::errors::*; +use crate::ordering::{ + application::port::output::db::line_item_id_exists::*, + domain::{delete_line_item_command::*, line_item_aggregate::*, line_item_deleted_event::*}, +}; +use crate::utils::uuid::*; + +#[automock] +#[async_trait::async_trait] +pub trait DeleteLineItemUseCase: Send + Sync { + async fn delete_line_item( + &self, + cmd: DeleteLineItemCommand, + ) -> OrderingResult; +} + +pub type DeleteLineItemServiceObj = Arc; + +#[derive(Clone, Builder)] +pub struct DeleteLineItemService { + db_line_item_id_exists: LineItemIDExistsDBPortObj, +} + +#[async_trait::async_trait] +impl DeleteLineItemUseCase for DeleteLineItemService { + async fn delete_line_item( + &self, + cmd: DeleteLineItemCommand, + ) -> OrderingResult { + if !self + .db_line_item_id_exists + .line_item_id_exists(cmd.line_item().line_item_id()) + .await? + { + return Err(OrderingError::LineItemIDNotFound); + } + + let deleted_line_item = LineItemBuilder::default() + .sale_time(cmd.line_item().sale_time().clone()) + .product_name(cmd.line_item().product_name().into()) + .product_id(*cmd.line_item().product_id()) + .line_item_id(*cmd.line_item().line_item_id()) + .quantity(cmd.line_item().quantity().clone()) + .deleted(true) + .build() + .unwrap(); + + Ok(LineItemDeletedEventBuilder::default() + .added_by_user(*cmd.adding_by()) + .line_item(deleted_line_item) + .build() + .unwrap()) + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + + use crate::ordering::domain::line_item_deleted_event::tests::get_deleted_line_item_event_from_command; + use crate::tests::bdd::*; + + pub fn mock_delete_line_item_service( + times: Option, + cmd: DeleteLineItemCommand, + ) -> DeleteLineItemServiceObj { + let mut m = MockDeleteLineItemUseCase::new(); + + let res = get_deleted_line_item_event_from_command(&cmd); + if let Some(times) = times { + m.expect_delete_line_item() + .times(times) + .returning(move |_| Ok(res.clone())); + } else { + m.expect_delete_line_item() + .returning(move |_| Ok(res.clone())); + } + + Arc::new(m) + } + + #[actix_rt::test] + async fn test_service() { + let cmd = DeleteLineItemCommand::get_cmd(); + + let s = DeleteLineItemServiceBuilder::default() + .db_line_item_id_exists(mock_line_item_id_exists_db_port_true(IS_CALLED_ONLY_ONCE)) + .build() + .unwrap(); + + let res = s.delete_line_item(cmd.clone()).await.unwrap(); + assert_eq!( + res.line_item().product_name(), + cmd.line_item().product_name() + ); + assert_eq!(res.line_item().product_id(), cmd.line_item().product_id()); + assert_eq!(res.line_item().quantity(), cmd.line_item().quantity()); + assert_eq!( + res.line_item().line_item_id(), + cmd.line_item().line_item_id() + ); + assert!(res.line_item().deleted()); + + assert_eq!(res.added_by_user(), cmd.adding_by()); + } + + #[actix_rt::test] + async fn test_service_line_item_id_doesnt_exist() { + let cmd = DeleteLineItemCommand::get_cmd(); + + let s = DeleteLineItemServiceBuilder::default() + .db_line_item_id_exists(mock_line_item_id_exists_db_port_false(IS_CALLED_ONLY_ONCE)) + .build() + .unwrap(); + + assert_eq!( + s.delete_line_item(cmd.clone()).await, + Err(OrderingError::LineItemIDNotFound) + ); + } +} diff --git a/src/ordering/application/services/mod.rs b/src/ordering/application/services/mod.rs index b4e6f6c..705fc2f 100644 --- a/src/ordering/application/services/mod.rs +++ b/src/ordering/application/services/mod.rs @@ -10,18 +10,21 @@ pub mod errors; //services pub mod add_line_item_service; +pub mod delete_line_item_service; pub mod update_line_item_service; #[automock] pub trait OrderingServicesInterface: Send + Sync { fn add_line_item(&self) -> add_line_item_service::AddLineItemServiceObj; fn update_line_item(&self) -> update_line_item_service::UpdateLineItemServiceObj; + fn delete_line_item(&self) -> delete_line_item_service::DeleteLineItemServiceObj; } #[derive(Clone, Builder)] pub struct OrderingServices { add_line_item: add_line_item_service::AddLineItemServiceObj, update_line_item: update_line_item_service::UpdateLineItemServiceObj, + delete_line_item: delete_line_item_service::DeleteLineItemServiceObj, } impl OrderingServicesInterface for OrderingServices { @@ -31,4 +34,8 @@ impl OrderingServicesInterface for OrderingServices { fn update_line_item(&self) -> update_line_item_service::UpdateLineItemServiceObj { self.update_line_item.clone() } + + fn delete_line_item(&self) -> delete_line_item_service::DeleteLineItemServiceObj { + self.delete_line_item.clone() + } } diff --git a/src/ordering/domain/line_item_aggregate.rs b/src/ordering/domain/line_item_aggregate.rs index c83b96a..68aeab6 100644 --- a/src/ordering/domain/line_item_aggregate.rs +++ b/src/ordering/domain/line_item_aggregate.rs @@ -96,7 +96,8 @@ impl Aggregate for LineItem { Ok(vec![OrderingEvent::LineItemUpdated(res)]) } OrderingCommand::DeleteLineItem(cmd) => { - unimplemented!() + let res = services.delete_line_item().delete_line_item(cmd).await?; + Ok(vec![OrderingEvent::LineItemDeleted(res)]) } // _ => Ok(Vec::default()), } } @@ -117,10 +118,13 @@ mod aggregate_tests { use add_line_item_service::tests::mock_add_line_item_service; use cqrs_es::test::TestFramework; + use delete_line_item_service::tests::mock_delete_line_item_service; use update_line_item_service::tests::mock_update_line_item_service; use super::*; + use crate::ordering::domain::delete_line_item_command::DeleteLineItemCommand; + use crate::ordering::domain::line_item_deleted_event::tests::get_deleted_line_item_event_from_command; use crate::ordering::domain::line_item_updated_event::tests::get_updated_line_item_event_from_command; use crate::ordering::domain::update_line_item_command::UpdateLineItemCommand; use crate::tests::bdd::*; @@ -170,4 +174,25 @@ mod aggregate_tests { .when(OrderingCommand::UpdateLineItem(cmd)) .then_expect_events(vec![expected]); } + + #[test] + fn test_delete_line_item() { + let cmd = DeleteLineItemCommand::get_cmd(); + let expected = get_deleted_line_item_event_from_command(&cmd); + let expected = OrderingEvent::LineItemDeleted(expected); + + let mut services = MockOrderingServicesInterface::new(); + services + .expect_delete_line_item() + .times(IS_CALLED_ONLY_ONCE.unwrap()) + .return_const(mock_delete_line_item_service( + IS_CALLED_ONLY_ONCE, + cmd.clone(), + )); + + LineItemTestFramework::with(Arc::new(services)) + .given_no_previous_events() + .when(OrderingCommand::DeleteLineItem(cmd)) + .then_expect_events(vec![expected]); + } } diff --git a/src/ordering/domain/line_item_deleted_event.rs b/src/ordering/domain/line_item_deleted_event.rs index d90efb6..f3a113f 100644 --- a/src/ordering/domain/line_item_deleted_event.rs +++ b/src/ordering/domain/line_item_deleted_event.rs @@ -27,15 +27,26 @@ pub mod tests { pub fn get_deleted_line_item_event_from_command( cmd: &DeleteLineItemCommand, ) -> LineItemDeletedEvent { + let deleted_line_item = LineItemBuilder::default() + .sale_time(cmd.line_item().sale_time().clone()) + .product_name(cmd.line_item().product_name().into()) + .product_id(*cmd.line_item().product_id()) + .line_item_id(*cmd.line_item().line_item_id()) + .quantity(cmd.line_item().quantity().clone()) + .deleted(true) + .build() + .unwrap(); + LineItemDeletedEventBuilder::default() .added_by_user(cmd.adding_by().clone()) - .line_item(cmd.line_item().clone()) + .line_item(deleted_line_item) .build() .unwrap() } #[test] fn test_event() { - get_deleted_line_item_event_from_command(&DeleteLineItemCommand::get_cmd()); + let event = get_deleted_line_item_event_from_command(&DeleteLineItemCommand::get_cmd()); + assert!(event.line_item().deleted()); } }