{% import "application/macros.tera" as macros %}use std::sync::Arc;
use async_trait::async_trait;
use puerto_core::RepositoryError;
use crate::domain::{{ snake }}::{
errors::{{ pascal }}Error,
events::{{ "{" }}{{ pascal }}Event, {{ pascal }}Snapshot},
repository::{{ pascal }}RepositoryTrait,
unit_of_work::{{ pascal }}UnitOfWorkFactory,
use_cases::delete_{{ snake }}::{Delete{{ pascal }}Params, Delete{{ pascal }}UseCaseTrait},
};
pub struct Delete{{ pascal }}UseCaseImpl {
pub repository: Arc<dyn {{ pascal }}RepositoryTrait>,
pub uow_factory: Arc<dyn {{ pascal }}UnitOfWorkFactory>,
}
#[async_trait]
impl Delete{{ pascal }}UseCaseTrait for Delete{{ pascal }}UseCaseImpl {
async fn execute(&self, params: Delete{{ pascal }}Params) -> Result<(), {{ pascal }}Error> {
tracing::info!(id = %params.id, "delete_{{ snake }}: start");
let failed = |e: &RepositoryError| tracing::error!(error = %e, "delete_{{ snake }}: repository failed");
let mut entity = self
.repository
.find_by_id(params.id)
.await
.inspect_err(failed)?
.ok_or_else(|| {
let err = {{ pascal }}Error::NotFound;
tracing::warn!(error = %err, "delete_{{ snake }}: rejected");
err
})?;
let now = chrono::Utc::now();
entity.deleted = true;
entity.deleted_at = Some(now);
entity.updated_at = now;
let mut uow = self.uow_factory.begin().await.inspect_err(failed)?;
uow.save(&entity).await.inspect_err(failed)?;
uow.record({{ pascal }}Event::Deleted({{ pascal }}Snapshot::from_entity(&entity))).await.inspect_err(failed)?;
uow.commit().await.inspect_err(failed)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::{{ snake }}::{
model::{{ "{" }}{{ pascal }}, {{ pascal }}Id, {{ pascal }}Props},
repository::mocks::Mock{{ pascal }}Repository,
unit_of_work::mocks::RecordingUnitOfWorkFactory,
};
use puerto_core::DomainEvent;
{% for import in test_imports %} use {{ import }};
{% endfor %}
#[tokio::test]
async fn should_record_{{ snake }}_deleted_when_id_exists() {
// Arrange
let entity = {{ pascal }}::new({{ pascal }}Props {
{{ macros::props(fields=fields, word="example") }} }).unwrap();
let entity_id = entity.id;
let mut mock_repo = Mock{{ pascal }}Repository::new();
mock_repo
.expect_find_by_id()
.returning(move |_| Ok(Some(entity.clone())));
let uow_factory = RecordingUnitOfWorkFactory::new();
let use_case = Delete{{ pascal }}UseCaseImpl {
repository: Arc::new(mock_repo),
uow_factory: Arc::new(uow_factory.clone()),
};
// Act
let result = use_case
.execute(Delete{{ pascal }}Params { id: entity_id })
.await;
// Assert
assert!(result.is_ok());
let recorded = uow_factory.recorded();
assert!(recorded.committed);
assert!(recorded.saved.iter().all(|saved| saved.deleted));
assert_eq!(recorded.events.len(), 1);
assert_eq!(recorded.events[0].event_type(), "{{ snake }}_deleted");
assert_eq!(recorded.events[0].aggregate_id(), entity_id.value());
}
#[tokio::test]
async fn should_return_not_found_and_record_nothing_when_the_{{ snake }}_does_not_exist() {
// Arrange
let mut mock_repo = Mock{{ pascal }}Repository::new();
mock_repo.expect_find_by_id().returning(|_| Ok(None));
let uow_factory = RecordingUnitOfWorkFactory::new();
let use_case = Delete{{ pascal }}UseCaseImpl {
repository: Arc::new(mock_repo),
uow_factory: Arc::new(uow_factory.clone()),
};
// Act
let result = use_case
.execute(Delete{{ pascal }}Params { id: {{ pascal }}Id::generate() })
.await;
// Assert
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "{{ snake }}.not_found");
assert!(uow_factory.recorded().events.is_empty());
}
}