// 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::billing::{ application::port::output::db::bill_id_exists::*, application::port::output::db::line_item_id_exists::*, domain::{add_line_item_command::*, line_item_added_event::*, line_item_aggregate::*}, }; use crate::utils::uuid::*; #[automock] #[async_trait::async_trait] pub trait AddLineItemUseCase: Send + Sync { async fn add_line_item(&self, cmd: AddLineItemCommand) -> BillingResult; } pub type AddLineItemServiceObj = Arc; #[derive(Clone, Builder)] pub struct AddLineItemService { db_line_item_id_exists: LineItemIDExistsDBPortObj, db_bill_id_exists: BillIDExistsDBPortObj, get_uuid: GetUUIDInterfaceObj, } #[async_trait::async_trait] impl AddLineItemUseCase for AddLineItemService { async fn add_line_item(&self, cmd: AddLineItemCommand) -> BillingResult { if !self.db_bill_id_exists.bill_id_exists(cmd.bill_id()).await? { return Err(BillingError::BillIDNotFound); } let mut line_item_id = self.get_uuid.get_uuid(); loop { if self .db_line_item_id_exists .line_item_id_exists(&line_item_id) .await? { line_item_id = self.get_uuid.get_uuid(); continue; } else { break; } } let line_item = LineItemBuilder::default() .created_time(cmd.created_time().clone()) .product_name(cmd.product_name().into()) .product_id(*cmd.product_id()) .bill_id(*cmd.bill_id()) .line_item_id(line_item_id) .quantity(cmd.quantity().clone()) .price_per_unit(cmd.price_per_unit().clone()) .deleted(false) .build() .unwrap(); Ok(LineItemAddedEventBuilder::default() .added_by_user(*cmd.adding_by()) .line_item(line_item) .build() .unwrap()) } } #[cfg(test)] pub mod tests { use super::*; use crate::billing::domain::line_item_added_event::tests::get_added_line_item_event_from_command; use crate::utils::uuid::tests::UUID; use crate::{tests::bdd::*, utils::uuid::tests::mock_get_uuid}; pub fn mock_add_line_item_service( times: Option, cmd: AddLineItemCommand, ) -> AddLineItemServiceObj { let mut m = MockAddLineItemUseCase::new(); let res = get_added_line_item_event_from_command(&cmd); if let Some(times) = times { m.expect_add_line_item() .times(times) .returning(move |_| Ok(res.clone())); } else { m.expect_add_line_item().returning(move |_| Ok(res.clone())); } Arc::new(m) } #[actix_rt::test] async fn test_service() { let cmd = AddLineItemCommand::get_cmd(); let s = AddLineItemServiceBuilder::default() .db_line_item_id_exists(mock_line_item_id_exists_db_port_false(IS_CALLED_ONLY_ONCE)) .db_bill_id_exists(mock_bill_id_exists_db_port_true(IS_CALLED_ONLY_ONCE)) .get_uuid(mock_get_uuid(IS_CALLED_ONLY_ONCE)) .build() .unwrap(); let res = s.add_line_item(cmd.clone()).await.unwrap(); assert_eq!(res.line_item().product_name(), cmd.product_name()); assert_eq!(res.line_item().product_id(), cmd.product_id()); assert_eq!(res.line_item().bill_id(), cmd.bill_id()); assert_eq!(res.line_item().quantity(), cmd.quantity()); assert_eq!(res.line_item().quantity(), cmd.quantity()); assert_eq!(res.line_item().created_time(), cmd.created_time()); assert!(!res.line_item().deleted()); assert_eq!(res.added_by_user(), cmd.adding_by()); } #[actix_rt::test] async fn test_service_bill_id_doesnt_exist() { let cmd = AddLineItemCommand::get_cmd(); let s = AddLineItemServiceBuilder::default() .db_line_item_id_exists(mock_line_item_id_exists_db_port_false(IS_NEVER_CALLED)) .db_bill_id_exists(mock_bill_id_exists_db_port_false(IS_CALLED_ONLY_ONCE)) .get_uuid(mock_get_uuid(IS_NEVER_CALLED)) .build() .unwrap(); assert_eq!( s.add_line_item(cmd.clone()).await, Err(BillingError::BillIDNotFound) ); } }