diff --git a/src/ordering/application/services/mod.rs b/src/ordering/application/services/mod.rs index ac7dd23..b4e6f6c 100644 --- a/src/ordering/application/services/mod.rs +++ b/src/ordering/application/services/mod.rs @@ -10,19 +10,25 @@ pub mod errors; //services pub mod add_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; } #[derive(Clone, Builder)] pub struct OrderingServices { add_line_item: add_line_item_service::AddLineItemServiceObj, + update_line_item: update_line_item_service::UpdateLineItemServiceObj, } impl OrderingServicesInterface for OrderingServices { fn add_line_item(&self) -> add_line_item_service::AddLineItemServiceObj { self.add_line_item.clone() } + fn update_line_item(&self) -> update_line_item_service::UpdateLineItemServiceObj { + self.update_line_item.clone() + } } diff --git a/src/ordering/application/services/update_line_item_service.rs b/src/ordering/application/services/update_line_item_service.rs new file mode 100644 index 0000000..b0e555d --- /dev/null +++ b/src/ordering/application/services/update_line_item_service.rs @@ -0,0 +1,134 @@ +// 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::{line_item_aggregate::*, line_item_updated_event::*, update_line_item_command::*}, +}; +use crate::utils::uuid::*; + +#[automock] +#[async_trait::async_trait] +pub trait UpdateLineItemUseCase: Send + Sync { + async fn update_line_item( + &self, + cmd: UpdateLineItemCommand, + ) -> OrderingResult; +} + +pub type UpdateLineItemServiceObj = Arc; + +#[derive(Clone, Builder)] +pub struct UpdateLineItemService { + db_line_item_id_exists: LineItemIDExistsDBPortObj, +} + +#[async_trait::async_trait] +impl UpdateLineItemUseCase for UpdateLineItemService { + async fn update_line_item( + &self, + cmd: UpdateLineItemCommand, + ) -> OrderingResult { + if !self + .db_line_item_id_exists + .line_item_id_exists(cmd.old_line_item().line_item_id()) + .await? + { + return Err(OrderingError::LineItemIDNotFound); + } + + let new_line_item = LineItemBuilder::default() + .sale_time(cmd.sale_time().clone()) + .product_name(cmd.product_name().into()) + .product_id(*cmd.product_id()) + .line_item_id(*cmd.old_line_item().line_item_id()) + .quantity(cmd.quantity().clone()) + .deleted(false) + .build() + .unwrap(); + + Ok(LineItemUpdatedEventBuilder::default() + .added_by_user(*cmd.adding_by()) + .new_line_item(new_line_item) + .old_line_item(cmd.old_line_item().clone()) + .build() + .unwrap()) + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + + use crate::ordering::domain::line_item_updated_event::tests::get_updated_line_item_event_from_command; + use crate::utils::uuid::tests::UUID; + use crate::{tests::bdd::*, utils::uuid::tests::mock_get_uuid}; + + pub fn mock_update_line_item_service( + times: Option, + cmd: UpdateLineItemCommand, + ) -> UpdateLineItemServiceObj { + let mut m = MockUpdateLineItemUseCase::new(); + + let res = get_updated_line_item_event_from_command(&cmd); + if let Some(times) = times { + m.expect_update_line_item() + .times(times) + .returning(move |_| Ok(res.clone())); + } else { + m.expect_update_line_item() + .returning(move |_| Ok(res.clone())); + } + + Arc::new(m) + } + + #[actix_rt::test] + async fn test_service() { + let cmd = UpdateLineItemCommand::get_cmd(); + + let s = UpdateLineItemServiceBuilder::default() + .db_line_item_id_exists(mock_line_item_id_exists_db_port_true(IS_CALLED_ONLY_ONCE)) + .build() + .unwrap(); + + let res = s.update_line_item(cmd.clone()).await.unwrap(); + assert_eq!(res.new_line_item().product_name(), cmd.product_name()); + assert_eq!(res.new_line_item().product_id(), cmd.product_id()); + assert_eq!(res.new_line_item().quantity(), cmd.quantity()); + assert_eq!(res.new_line_item().quantity(), cmd.quantity()); + assert_eq!( + res.new_line_item().line_item_id(), + cmd.old_line_item().line_item_id() + ); + assert!(!res.new_line_item().deleted()); + + assert_eq!(res.old_line_item(), cmd.old_line_item()); + + assert_eq!(res.added_by_user(), cmd.adding_by()); + } + + #[actix_rt::test] + async fn test_service_line_item_id_doesnt_exist() { + let cmd = UpdateLineItemCommand::get_cmd(); + + let s = UpdateLineItemServiceBuilder::default() + .db_line_item_id_exists(mock_line_item_id_exists_db_port_false(IS_CALLED_ONLY_ONCE)) + .build() + .unwrap(); + + assert_eq!( + s.update_line_item(cmd.clone()).await, + Err(OrderingError::LineItemIDNotFound) + ); + } +} diff --git a/src/ordering/domain/line_item_aggregate.rs b/src/ordering/domain/line_item_aggregate.rs index 29efd76..c83b96a 100644 --- a/src/ordering/domain/line_item_aggregate.rs +++ b/src/ordering/domain/line_item_aggregate.rs @@ -92,7 +92,8 @@ impl Aggregate for LineItem { Ok(vec![OrderingEvent::LineItemAdded(res)]) } OrderingCommand::UpdateLineItem(cmd) => { - unimplemented!() + let res = services.update_line_item().update_line_item(cmd).await?; + Ok(vec![OrderingEvent::LineItemUpdated(res)]) } OrderingCommand::DeleteLineItem(cmd) => { unimplemented!() @@ -116,9 +117,12 @@ mod aggregate_tests { use add_line_item_service::tests::mock_add_line_item_service; use cqrs_es::test::TestFramework; + use update_line_item_service::tests::mock_update_line_item_service; use super::*; + 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::*; use crate::ordering::domain::{ @@ -129,7 +133,7 @@ mod aggregate_tests { type LineItemTestFramework = TestFramework; #[test] - fn test_create_product() { + fn test_add_line_item() { let cmd = AddLineItemCommand::get_cmd(); let expected = get_added_line_item_event_from_command(&cmd); let expected = OrderingEvent::LineItemAdded(expected); @@ -145,4 +149,25 @@ mod aggregate_tests { .when(OrderingCommand::AddLineItem(cmd)) .then_expect_events(vec![expected]); } + + #[test] + fn test_update_line_item() { + let cmd = UpdateLineItemCommand::get_cmd(); + let expected = get_updated_line_item_event_from_command(&cmd); + let expected = OrderingEvent::LineItemUpdated(expected); + + let mut services = MockOrderingServicesInterface::new(); + services + .expect_update_line_item() + .times(IS_CALLED_ONLY_ONCE.unwrap()) + .return_const(mock_update_line_item_service( + IS_CALLED_ONLY_ONCE, + cmd.clone(), + )); + + LineItemTestFramework::with(Arc::new(services)) + .given_no_previous_events() + .when(OrderingCommand::UpdateLineItem(cmd)) + .then_expect_events(vec![expected]); + } }