feat: order: LineItem add and update cmd,events&service #59
4 changed files with 178 additions and 3 deletions
132
src/ordering/application/services/delete_line_item_service.rs
Normal file
132
src/ordering/application/services/delete_line_item_service.rs
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
// 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 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<LineItemDeletedEvent>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type DeleteLineItemServiceObj = Arc<dyn DeleteLineItemUseCase>;
|
||||||
|
|
||||||
|
#[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<LineItemDeletedEvent> {
|
||||||
|
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<usize>,
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,18 +10,21 @@ pub mod errors;
|
||||||
|
|
||||||
//services
|
//services
|
||||||
pub mod add_line_item_service;
|
pub mod add_line_item_service;
|
||||||
|
pub mod delete_line_item_service;
|
||||||
pub mod update_line_item_service;
|
pub mod update_line_item_service;
|
||||||
|
|
||||||
#[automock]
|
#[automock]
|
||||||
pub trait OrderingServicesInterface: Send + Sync {
|
pub trait OrderingServicesInterface: Send + Sync {
|
||||||
fn add_line_item(&self) -> add_line_item_service::AddLineItemServiceObj;
|
fn add_line_item(&self) -> add_line_item_service::AddLineItemServiceObj;
|
||||||
fn update_line_item(&self) -> update_line_item_service::UpdateLineItemServiceObj;
|
fn update_line_item(&self) -> update_line_item_service::UpdateLineItemServiceObj;
|
||||||
|
fn delete_line_item(&self) -> delete_line_item_service::DeleteLineItemServiceObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Builder)]
|
#[derive(Clone, Builder)]
|
||||||
pub struct OrderingServices {
|
pub struct OrderingServices {
|
||||||
add_line_item: add_line_item_service::AddLineItemServiceObj,
|
add_line_item: add_line_item_service::AddLineItemServiceObj,
|
||||||
update_line_item: update_line_item_service::UpdateLineItemServiceObj,
|
update_line_item: update_line_item_service::UpdateLineItemServiceObj,
|
||||||
|
delete_line_item: delete_line_item_service::DeleteLineItemServiceObj,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OrderingServicesInterface for OrderingServices {
|
impl OrderingServicesInterface for OrderingServices {
|
||||||
|
@ -31,4 +34,8 @@ impl OrderingServicesInterface for OrderingServices {
|
||||||
fn update_line_item(&self) -> update_line_item_service::UpdateLineItemServiceObj {
|
fn update_line_item(&self) -> update_line_item_service::UpdateLineItemServiceObj {
|
||||||
self.update_line_item.clone()
|
self.update_line_item.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn delete_line_item(&self) -> delete_line_item_service::DeleteLineItemServiceObj {
|
||||||
|
self.delete_line_item.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,8 @@ impl Aggregate for LineItem {
|
||||||
Ok(vec![OrderingEvent::LineItemUpdated(res)])
|
Ok(vec![OrderingEvent::LineItemUpdated(res)])
|
||||||
}
|
}
|
||||||
OrderingCommand::DeleteLineItem(cmd) => {
|
OrderingCommand::DeleteLineItem(cmd) => {
|
||||||
unimplemented!()
|
let res = services.delete_line_item().delete_line_item(cmd).await?;
|
||||||
|
Ok(vec![OrderingEvent::LineItemDeleted(res)])
|
||||||
} // _ => Ok(Vec::default()),
|
} // _ => Ok(Vec::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,10 +118,13 @@ mod aggregate_tests {
|
||||||
|
|
||||||
use add_line_item_service::tests::mock_add_line_item_service;
|
use add_line_item_service::tests::mock_add_line_item_service;
|
||||||
use cqrs_es::test::TestFramework;
|
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 update_line_item_service::tests::mock_update_line_item_service;
|
||||||
|
|
||||||
use super::*;
|
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::line_item_updated_event::tests::get_updated_line_item_event_from_command;
|
||||||
use crate::ordering::domain::update_line_item_command::UpdateLineItemCommand;
|
use crate::ordering::domain::update_line_item_command::UpdateLineItemCommand;
|
||||||
use crate::tests::bdd::*;
|
use crate::tests::bdd::*;
|
||||||
|
@ -170,4 +174,25 @@ mod aggregate_tests {
|
||||||
.when(OrderingCommand::UpdateLineItem(cmd))
|
.when(OrderingCommand::UpdateLineItem(cmd))
|
||||||
.then_expect_events(vec![expected]);
|
.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]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,15 +27,26 @@ pub mod tests {
|
||||||
pub fn get_deleted_line_item_event_from_command(
|
pub fn get_deleted_line_item_event_from_command(
|
||||||
cmd: &DeleteLineItemCommand,
|
cmd: &DeleteLineItemCommand,
|
||||||
) -> LineItemDeletedEvent {
|
) -> 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()
|
LineItemDeletedEventBuilder::default()
|
||||||
.added_by_user(cmd.adding_by().clone())
|
.added_by_user(cmd.adding_by().clone())
|
||||||
.line_item(cmd.line_item().clone())
|
.line_item(deleted_line_item)
|
||||||
.build()
|
.build()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_event() {
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue