feat: atomic review writes with events

This commit is contained in:
2026-10-26 00:40:43 +01:00
parent 0236a488fa
commit c8a1e8bfe4
19 changed files with 440 additions and 117 deletions

View File

@@ -0,0 +1,82 @@
use std::str::FromStr;
use domain::{
events::DomainEvent,
ports::{MovieCommand, ReviewRepository, UserRepository},
testing::{test_movie, test_review, test_user},
};
use sqlite::{SqliteMovieRepository, SqliteReviewRepository, SqliteUserRepository};
use sqlx::{
SqlitePool,
sqlite::{SqliteConnectOptions, SqlitePoolOptions},
};
async fn migrated_pool() -> SqlitePool {
let opts = SqliteConnectOptions::from_str("sqlite::memory:").unwrap();
let pool = SqlitePoolOptions::new()
.max_connections(1)
.connect_with(opts)
.await
.unwrap();
sqlite::migrate(&pool).await.unwrap();
pool
}
async fn count(pool: &SqlitePool, table: &str) -> i64 {
sqlx::query_scalar(&format!("SELECT COUNT(*) FROM {table}"))
.fetch_one(pool)
.await
.unwrap()
}
fn logged(review: &domain::models::Review) -> DomainEvent {
DomainEvent::ReviewLogged {
review_id: review.id().clone(),
movie_id: review.movie_id().clone(),
user_id: review.user_id().clone(),
rating: review.rating().clone(),
watched_at: *review.watched_at(),
}
}
#[tokio::test]
async fn saving_a_review_writes_the_review_and_its_event_in_one_commit() {
let pool = migrated_pool().await;
let movie = test_movie("Heat");
let user = test_user("alice");
SqliteUserRepository::new(pool.clone())
.save(&user)
.await
.unwrap();
SqliteMovieRepository::new(pool.clone())
.upsert_movie(&movie)
.await
.unwrap();
let review = test_review(movie.id(), user.id(), 4);
SqliteReviewRepository::new(pool.clone())
.save_review(&review, &[logged(&review)])
.await
.unwrap();
assert_eq!(count(&pool, "reviews").await, 1);
assert_eq!(count(&pool, "event_queue").await, 1);
}
#[tokio::test]
async fn a_failed_review_save_leaves_no_event_row() {
let pool = migrated_pool().await;
let review = test_review(
&domain::value_objects::MovieId::generate(),
&domain::value_objects::UserId::generate(),
4,
);
let result = SqliteReviewRepository::new(pool.clone())
.save_review(&review, &[logged(&review)])
.await;
assert!(result.is_err(), "FK violation must fail the save");
assert_eq!(count(&pool, "reviews").await, 0);
assert_eq!(count(&pool, "event_queue").await, 0);
}