Files
movies-diary/crates/application/src/use_cases/delete_review.rs
Gabriel Kaszewski 68a939f6c4 Refactor code for improved readability and consistency
- Simplified error handling in `PostgresApContentQuery` and `SqliteApContentQuery` by aligning the formatting of `try_get` calls.
- Removed unnecessary line breaks and improved formatting in various repository implementations for better readability.
- Consolidated imports in `lib.rs` and `factory.rs` to maintain a cleaner structure.
- Enhanced consistency in async function signatures across multiple files.
- Updated test helpers and use cases to streamline code and improve clarity.
- Refactored `InMemory` repositories to enhance readability by aligning method implementations.
2026-05-29 10:58:44 +02:00

164 lines
4.9 KiB
Rust

use crate::{commands::DeleteReviewCommand, context::AppContext};
use domain::{
errors::DomainError,
events::DomainEvent,
value_objects::{ReviewId, UserId},
};
pub async fn execute(ctx: &AppContext, cmd: DeleteReviewCommand) -> Result<(), DomainError> {
let review_id = ReviewId::from_uuid(cmd.review_id);
let requesting_user_id = UserId::from_uuid(cmd.requesting_user_id);
let review = ctx
.review_repository
.get_review_by_id(&review_id)
.await?
.ok_or_else(|| DomainError::NotFound(format!("review {}", cmd.review_id)))?;
if review.user_id() != &requesting_user_id {
return Err(DomainError::Unauthorized("not your review".into()));
}
let movie_id = review.movie_id().clone();
ctx.review_repository.delete_review(&review_id).await?;
if let Err(e) = ctx
.event_publisher
.publish(&DomainEvent::ReviewDeleted {
review_id: review_id.clone(),
user_id: requesting_user_id.clone(),
})
.await
{
tracing::warn!("failed to publish ReviewDeleted: {e}");
}
let history = ctx.diary_repository.get_review_history(&movie_id).await?;
if history.viewings().is_empty() {
let poster_path = history.movie().poster_path().cloned();
ctx.movie_repository.delete_movie(&movie_id).await?;
// best-effort: movie is already deleted, so publish failure is non-fatal
if let Err(e) = ctx
.event_publisher
.publish(&DomainEvent::MovieDeleted {
movie_id,
poster_path,
})
.await
{
tracing::warn!("failed to publish MovieDeleted event: {e}");
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use chrono::Utc;
use domain::{
models::{Movie, Review},
ports::{MovieRepository, ReviewRepository},
testing::{
FakeDiaryRepository, InMemoryMovieRepository, InMemoryReviewRepository,
NoopEventPublisher,
},
value_objects::{MovieId, MovieTitle, Rating, ReleaseYear, UserId},
};
use crate::{
commands::DeleteReviewCommand, test_helpers::TestContextBuilder, use_cases::delete_review,
};
fn make_movie() -> Movie {
Movie::new(
None,
MovieTitle::new("Terminator".into()).unwrap(),
ReleaseYear::new(1984).unwrap(),
None,
None,
)
}
fn make_review(movie_id: MovieId, user_id: UserId) -> Review {
Review::new(
movie_id,
user_id,
Rating::new(4).unwrap(),
None,
Utc::now().naive_utc(),
)
.unwrap()
}
#[tokio::test]
async fn test_delete_review_removes_it() {
let movies = InMemoryMovieRepository::new();
let reviews = InMemoryReviewRepository::new();
let diary = FakeDiaryRepository::new();
let events = NoopEventPublisher::new();
let movie = make_movie();
let user_id = UserId::from_uuid(uuid::Uuid::new_v4());
let review = make_review(movie.id().clone(), user_id.clone());
movies.upsert_movie(&movie).await.unwrap();
reviews.save_review(&review).await.unwrap();
diary.seed_history(movie.clone(), vec![]);
let ctx = TestContextBuilder::new()
.with_movies(Arc::clone(&movies) as _)
.with_reviews(Arc::clone(&reviews) as _)
.with_diary(Arc::clone(&diary) as _)
.with_event_publisher(Arc::clone(&events) as _)
.build();
delete_review::execute(
&ctx,
DeleteReviewCommand {
review_id: review.id().value(),
requesting_user_id: user_id.value(),
},
)
.await
.unwrap();
assert_eq!(reviews.count(), 0, "review should be deleted");
assert!(
movies.get_movie_by_id(movie.id()).await.unwrap().is_none(),
"movie should be deleted when no reviews remain"
);
}
#[tokio::test]
async fn test_delete_review_wrong_user_is_unauthorized() {
let reviews = InMemoryReviewRepository::new();
let movie_id = MovieId::from_uuid(uuid::Uuid::new_v4());
let owner_id = UserId::from_uuid(uuid::Uuid::new_v4());
let other_id = uuid::Uuid::new_v4();
let review = make_review(movie_id, owner_id);
reviews.save_review(&review).await.unwrap();
let ctx = TestContextBuilder::new()
.with_reviews(Arc::clone(&reviews) as _)
.build();
let result = delete_review::execute(
&ctx,
DeleteReviewCommand {
review_id: review.id().value(),
requesting_user_id: other_id,
},
)
.await;
assert!(result.is_err(), "wrong user should not be able to delete");
assert_eq!(reviews.count(), 1, "review should still exist");
}
}