From feaab33d3328c8b175acae75c684feccbf90abef Mon Sep 17 00:00:00 2001 From: Aravinth Manivannan Date: Tue, 16 Jul 2024 20:56:31 +0530 Subject: [PATCH 1/2] fix: use product ID from customization obj --- .../db/postgres/customization_name_exists_for_product.rs | 9 ++++----- .../output/db/customization_name_exists_for_product.rs | 9 ++++----- .../application/services/add_customization_service.rs | 2 +- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/inventory/adapters/output/db/postgres/customization_name_exists_for_product.rs b/src/inventory/adapters/output/db/postgres/customization_name_exists_for_product.rs index b2a3daa..8e2aa35 100644 --- a/src/inventory/adapters/output/db/postgres/customization_name_exists_for_product.rs +++ b/src/inventory/adapters/output/db/postgres/customization_name_exists_for_product.rs @@ -14,7 +14,6 @@ impl CustomizationNameExistsForProductDBPort for InventoryDBPostgresAdapter { async fn customization_name_exists_for_product( &self, c: &Customization, - product_id: &Uuid, ) -> InventoryDBResult { let res = sqlx::query!( "SELECT EXISTS ( @@ -28,7 +27,7 @@ impl CustomizationNameExistsForProductDBPort for InventoryDBPostgresAdapter { deleted = false );", c.name(), - product_id, + c.product_id() ) .fetch_one(&self.pool) .await?; @@ -71,7 +70,7 @@ mod tests { // state doesn't exist assert!(!db - .customization_name_exists_for_product(&customization, &product_id) + .customization_name_exists_for_product(&customization) .await .unwrap()); @@ -79,7 +78,7 @@ mod tests { // state exists assert!(db - .customization_name_exists_for_product(&customization, &product_id) + .customization_name_exists_for_product(&customization) .await .unwrap()); @@ -103,7 +102,7 @@ mod tests { .await .unwrap(); assert!(!db - .customization_name_exists_for_product(&customization, &product_id) + .customization_name_exists_for_product(&customization) .await .unwrap()); diff --git a/src/inventory/application/port/output/db/customization_name_exists_for_product.rs b/src/inventory/application/port/output/db/customization_name_exists_for_product.rs index 632260b..311ccf3 100644 --- a/src/inventory/application/port/output/db/customization_name_exists_for_product.rs +++ b/src/inventory/application/port/output/db/customization_name_exists_for_product.rs @@ -19,7 +19,6 @@ pub trait CustomizationNameExistsForProductDBPort: Send + Sync { async fn customization_name_exists_for_product( &self, c: &Customization, - product_id: &Uuid, ) -> InventoryDBResult; } @@ -39,10 +38,10 @@ pub mod tests { if let Some(times) = times { m.expect_customization_name_exists_for_product() .times(times) - .returning(|_, _| Ok(false)); + .returning(|_| Ok(false)); } else { m.expect_customization_name_exists_for_product() - .returning(|_, _| Ok(false)); + .returning(|_| Ok(false)); } Arc::new(m) @@ -55,10 +54,10 @@ pub mod tests { if let Some(times) = times { m.expect_customization_name_exists_for_product() .times(times) - .returning(|_, _| Ok(true)); + .returning(|_| Ok(true)); } else { m.expect_customization_name_exists_for_product() - .returning(|_, _| Ok(true)); + .returning(|_| Ok(true)); } Arc::new(m) diff --git a/src/inventory/application/services/add_customization_service.rs b/src/inventory/application/services/add_customization_service.rs index e72190f..9dcedcf 100644 --- a/src/inventory/application/services/add_customization_service.rs +++ b/src/inventory/application/services/add_customization_service.rs @@ -83,7 +83,7 @@ impl AddCustomizationUseCase for AddCustomizationService { if self .db_customization_name_exists_for_product - .customization_name_exists_for_product(&customization, cmd.product_id()) + .customization_name_exists_for_product(&customization) .await? { return Err(InventoryError::DuplicateCustomizationName); From 987b48a3df55c6f47d3b5ae13a1a00631d26fd54 Mon Sep 17 00:00:00 2001 From: Aravinth Manivannan Date: Tue, 16 Jul 2024 20:56:47 +0530 Subject: [PATCH 2/2] feat: update Customization aggregate cmd, event and service --- .../output/db/postgres/customization_view.rs | 1 - .../adapters/output/db/postgres/store_view.rs | 9 +- src/inventory/application/services/errors.rs | 1 + src/inventory/application/services/mod.rs | 10 +- .../services/update_customization_service.rs | 209 ++++++++++++++++++ src/inventory/domain/commands.rs | 2 + .../domain/customization_aggregate.rs | 38 +++- .../domain/customization_updated_event.rs | 52 +++++ src/inventory/domain/events.rs | 6 +- src/inventory/domain/mod.rs | 2 + .../domain/update_customization_command.rs | 101 +++++++++ 11 files changed, 425 insertions(+), 6 deletions(-) create mode 100644 src/inventory/application/services/update_customization_service.rs create mode 100644 src/inventory/domain/customization_updated_event.rs create mode 100644 src/inventory/domain/update_customization_command.rs diff --git a/src/inventory/adapters/output/db/postgres/customization_view.rs b/src/inventory/adapters/output/db/postgres/customization_view.rs index f39df79..e399f50 100644 --- a/src/inventory/adapters/output/db/postgres/customization_view.rs +++ b/src/inventory/adapters/output/db/postgres/customization_view.rs @@ -60,7 +60,6 @@ impl View for CustomizationView { } } - #[async_trait] impl ViewRepository for InventoryDBPostgresAdapter { async fn load( diff --git a/src/inventory/adapters/output/db/postgres/store_view.rs b/src/inventory/adapters/output/db/postgres/store_view.rs index aae80d2..bf609af 100644 --- a/src/inventory/adapters/output/db/postgres/store_view.rs +++ b/src/inventory/adapters/output/db/postgres/store_view.rs @@ -215,13 +215,16 @@ mod tests { add_customization_service::tests::mock_add_customization_service, add_product_service::tests::mock_add_product_service, add_store_service::AddStoreServiceBuilder, + update_customization_service::tests::mock_update_customization_service, update_product_service::tests::mock_update_product_service, InventoryServicesBuilder, }, domain::{ add_category_command::AddCategoryCommand, add_customization_command, add_product_command::tests::get_command, add_store_command::AddStoreCommand, - commands::InventoryCommand, update_product_command, + commands::InventoryCommand, + update_customization_command::tests::get_update_customization_command, + update_product_command, }, }, tests::bdd::IS_NEVER_CALLED, @@ -266,6 +269,10 @@ mod tests { IS_NEVER_CALLED, update_product_command::tests::get_command(), )) + .update_customization(mock_update_customization_service( + IS_NEVER_CALLED, + get_update_customization_command(), + )) .build() .unwrap(); diff --git a/src/inventory/application/services/errors.rs b/src/inventory/application/services/errors.rs index 18a49d9..24c848b 100644 --- a/src/inventory/application/services/errors.rs +++ b/src/inventory/application/services/errors.rs @@ -18,6 +18,7 @@ pub enum InventoryError { DuplicateCustomizationName, ProductIDNotFound, CategoryIDNotFound, + CustomizationIDNotFound, StoreIDNotFound, InternalError, } diff --git a/src/inventory/application/services/mod.rs b/src/inventory/application/services/mod.rs index 321dcc9..b689067 100644 --- a/src/inventory/application/services/mod.rs +++ b/src/inventory/application/services/mod.rs @@ -13,6 +13,7 @@ pub mod add_category_service; pub mod add_customization_service; pub mod add_product_service; pub mod add_store_service; +pub mod update_customization_service; pub mod update_product_service; #[automock] @@ -22,6 +23,7 @@ pub trait InventoryServicesInterface: Send + Sync { fn add_product(&self) -> add_product_service::AddProductServiceObj; fn add_customization(&self) -> add_customization_service::AddCustomizationServiceObj; fn update_product(&self) -> update_product_service::UpdateProductServiceObj; + fn update_customization(&self) -> update_customization_service::UpdateCustomizationServiceObj; } #[derive(Clone, Builder)] @@ -31,6 +33,7 @@ pub struct InventoryServices { add_product: add_product_service::AddProductServiceObj, add_customization: add_customization_service::AddCustomizationServiceObj, update_product: update_product_service::UpdateProductServiceObj, + update_customization: update_customization_service::UpdateCustomizationServiceObj, } impl InventoryServicesInterface for InventoryServices { @@ -47,7 +50,12 @@ impl InventoryServicesInterface for InventoryServices { fn add_customization(&self) -> add_customization_service::AddCustomizationServiceObj { self.add_customization.clone() } + fn update_product(&self) -> update_product_service::UpdateProductServiceObj { - self.update_product().clone() + self.update_product.clone() + } + + fn update_customization(&self) -> update_customization_service::UpdateCustomizationServiceObj { + self.update_customization.clone() } } diff --git a/src/inventory/application/services/update_customization_service.rs b/src/inventory/application/services/update_customization_service.rs new file mode 100644 index 0000000..3a0c6ad --- /dev/null +++ b/src/inventory/application/services/update_customization_service.rs @@ -0,0 +1,209 @@ +/// 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::inventory::{ + 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, + ) -> InventoryResult; +} + +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, + ) -> InventoryResult { + if !self + .db_customization_id_exists + .customization_id_exists(cmd.old_customization().customization_id()) + .await? + { + return Err(InventoryError::CustomizationIDNotFound); + } + + if !self + .db_product_id_exists + .product_id_exists(cmd.old_customization().product_id()) + .await? + { + return Err(InventoryError::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(InventoryError::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::inventory::domain::customization_updated_event; + use crate::inventory::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(InventoryError::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(InventoryError::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(InventoryError::DuplicateCustomizationName) + ); + } +} diff --git a/src/inventory/domain/commands.rs b/src/inventory/domain/commands.rs index 33c82b4..f87ccb2 100644 --- a/src/inventory/domain/commands.rs +++ b/src/inventory/domain/commands.rs @@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize}; use super::{ add_category_command::AddCategoryCommand, add_customization_command::AddCustomizationCommand, add_product_command::AddProductCommand, add_store_command::AddStoreCommand, + update_customization_command::UpdateCustomizationCommand, update_product_command::UpdateProductCommand, }; @@ -18,4 +19,5 @@ pub enum InventoryCommand { AddProduct(AddProductCommand), AddCustomization(AddCustomizationCommand), UpdateProduct(UpdateProductCommand), + UpdateCustomization(UpdateCustomizationCommand), } diff --git a/src/inventory/domain/customization_aggregate.rs b/src/inventory/domain/customization_aggregate.rs index 533b79c..cea8fee 100644 --- a/src/inventory/domain/customization_aggregate.rs +++ b/src/inventory/domain/customization_aggregate.rs @@ -51,6 +51,14 @@ impl Aggregate for Customization { let res = services.add_customization().add_customization(cmd).await?; Ok(vec![InventoryEvent::CustomizationAdded(res)]) } + InventoryCommand::UpdateCustomization(cmd) => { + let res = services + .update_customization() + .update_customization(cmd) + .await?; + Ok(vec![InventoryEvent::CustomizationUpdated(res)]) + } + _ => Ok(Vec::default()), } } @@ -60,6 +68,9 @@ impl Aggregate for Customization { InventoryEvent::CustomizationAdded(e) => { *self = e.customization().clone(); } + InventoryEvent::CustomizationUpdated(e) => { + *self = e.new_customization().clone(); + } _ => (), } } @@ -73,11 +84,15 @@ mod aggregate_tests { use super::*; use crate::inventory::{ - application::services::{add_customization_service::tests::*, *}, + application::services::{ + add_customization_service::tests::*, update_customization_service::tests::*, *, + }, domain::{ add_customization_command, commands::InventoryCommand, customization_added_event::tests::get_customization_added_event_from_cmd, + customization_updated_event::tests::get_customization_updated_event_from_command, events::InventoryEvent, + update_customization_command::tests::get_update_customization_command, }, }; use crate::tests::bdd::*; @@ -104,4 +119,25 @@ mod aggregate_tests { .when(InventoryCommand::AddCustomization(cmd)) .then_expect_events(vec![expected]); } + + #[test] + fn test_update_customization() { + let cmd = get_update_customization_command(); + let expected = get_customization_updated_event_from_command(&cmd); + let expected = InventoryEvent::CustomizationUpdated(expected); + + let mut services = MockInventoryServicesInterface::new(); + services + .expect_update_customization() + .times(IS_CALLED_ONLY_ONCE.unwrap()) + .return_const(mock_update_customization_service( + IS_CALLED_ONLY_ONCE, + cmd.clone(), + )); + + CustomizationTestFramework::with(Arc::new(services)) + .given_no_previous_events() + .when(InventoryCommand::UpdateCustomization(cmd)) + .then_expect_events(vec![expected]); + } } diff --git a/src/inventory/domain/customization_updated_event.rs b/src/inventory/domain/customization_updated_event.rs new file mode 100644 index 0000000..8839c6b --- /dev/null +++ b/src/inventory/domain/customization_updated_event.rs @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use derive_builder::Builder; +use derive_getters::Getters; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use super::customization_aggregate::Customization; + +#[derive( + Clone, Debug, Builder, Serialize, Deserialize, Getters, Eq, PartialEq, Ord, PartialOrd, +)] +pub struct CustomizationUpdatedEvent { + added_by_user: Uuid, + + old_customization: Customization, + new_customization: Customization, +} + +#[cfg(test)] +pub mod tests { + use crate::inventory::domain::{ + customization_aggregate::*, update_customization_command::UpdateCustomizationCommand, + }; + use crate::utils::uuid::tests::UUID; + + use super::*; + + #[test] + fn test_name() {} + + pub fn get_customization_updated_event_from_command( + cmd: &UpdateCustomizationCommand, + ) -> CustomizationUpdatedEvent { + let customization = CustomizationBuilder::default() + .name(cmd.name().into()) + .deleted(false) + .customization_id(UUID.clone()) + .product_id(UUID.clone()) + .build() + .unwrap(); + + CustomizationUpdatedEventBuilder::default() + .added_by_user(cmd.adding_by().clone()) + .new_customization(customization) + .old_customization(cmd.old_customization().clone()) + .build() + .unwrap() + } +} diff --git a/src/inventory/domain/events.rs b/src/inventory/domain/events.rs index 047c5ce..269ac78 100644 --- a/src/inventory/domain/events.rs +++ b/src/inventory/domain/events.rs @@ -7,8 +7,8 @@ use serde::{Deserialize, Serialize}; use super::{ category_added_event::*, customization_added_event::CustomizationAddedEvent, - product_added_event::ProductAddedEvent, product_updated_event::ProductUpdatedEvent, - store_added_event::StoreAddedEvent, + customization_updated_event::CustomizationUpdatedEvent, product_added_event::ProductAddedEvent, + product_updated_event::ProductUpdatedEvent, store_added_event::StoreAddedEvent, }; #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)] @@ -18,6 +18,7 @@ pub enum InventoryEvent { ProductAdded(ProductAddedEvent), CustomizationAdded(CustomizationAddedEvent), ProductUpdated(ProductUpdatedEvent), + CustomizationUpdated(CustomizationUpdatedEvent), } impl DomainEvent for InventoryEvent { @@ -32,6 +33,7 @@ impl DomainEvent for InventoryEvent { InventoryEvent::ProductAdded { .. } => "InventoryProductAdded", InventoryEvent::CustomizationAdded { .. } => "InventoryCustomizationAdded", InventoryEvent::ProductUpdated { .. } => "InventoryProductUpdated", + InventoryEvent::CustomizationUpdated { .. } => "InventoryCustomizationUpdated", }; e.to_string() diff --git a/src/inventory/domain/mod.rs b/src/inventory/domain/mod.rs index 7417b87..6e13df7 100644 --- a/src/inventory/domain/mod.rs +++ b/src/inventory/domain/mod.rs @@ -15,11 +15,13 @@ pub mod add_customization_command; pub mod add_product_command; pub mod add_store_command; pub mod commands; +pub mod update_customization_command; pub mod update_product_command; // events pub mod category_added_event; pub mod customization_added_event; +pub mod customization_updated_event; pub mod events; pub mod product_added_event; pub mod product_updated_event; diff --git a/src/inventory/domain/update_customization_command.rs b/src/inventory/domain/update_customization_command.rs new file mode 100644 index 0000000..6b1e604 --- /dev/null +++ b/src/inventory/domain/update_customization_command.rs @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use derive_builder::Builder; +use derive_getters::Getters; +use derive_more::{Display, Error}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use super::customization_aggregate::Customization; + +#[derive(Debug, Error, Display, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +pub enum UpdateCustomizationCommandError { + NameIsEmpty, +} + +#[derive( + Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Getters, Builder, +)] +pub struct UnvalidatedUpdateCustomizationCommand { + name: String, + adding_by: Uuid, + + old_customization: Customization, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Getters)] +pub struct UpdateCustomizationCommand { + name: String, + old_customization: Customization, + adding_by: Uuid, +} + +impl UnvalidatedUpdateCustomizationCommand { + pub fn validate(self) -> Result { + let name = self.name.trim().to_owned(); + if name.is_empty() { + return Err(UpdateCustomizationCommandError::NameIsEmpty); + } + + Ok(UpdateCustomizationCommand { + name, + old_customization: self.old_customization, + adding_by: self.adding_by, + }) + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + + use crate::{ + inventory::domain::customization_aggregate::Customization, utils::uuid::tests::UUID, + }; + + pub fn get_update_customization_command() -> UpdateCustomizationCommand { + let customozation = Customization::default(); + UnvalidatedUpdateCustomizationCommandBuilder::default() + .name("foo".into()) + .old_customization(customozation) + .adding_by(UUID.clone()) + .build() + .unwrap() + .validate() + .unwrap() + } + + #[test] + fn test_cmd() { + let name = "foo"; + + let cmd = UnvalidatedUpdateCustomizationCommandBuilder::default() + .name(name.into()) + .old_customization(Customization::default()) + .adding_by(UUID.clone()) + .build() + .unwrap() + .validate() + .unwrap(); + + assert_eq!(cmd.name(), name); + assert_eq!(cmd.adding_by(), &UUID); + assert_eq!(cmd.old_customization(), &Customization::default()); + } + + #[test] + fn test_cmd_name_is_empty() { + assert_eq!( + UnvalidatedUpdateCustomizationCommandBuilder::default() + .name("".into()) + .adding_by(UUID.clone()) + .old_customization(Customization::default()) + .build() + .unwrap() + .validate(), + Err(UpdateCustomizationCommandError::NameIsEmpty) + ); + } +}