feat: atomic wrapup, movie, integration writes

Ride writes: WrapUpRequested/Completed (wrapup create/set_complete),
MovieDiscovered (resolve upsert, new+ext id only), PosterSynced (poster
handler upsert), MovieDeleted (delete_movie), WatchEventIngested (save).
Deps drop event_publisher: wrapup, ingest, delete_review, watchlist add,
review logger.

Remaining .publish( sites:
announce-only: users/delete_account UserDeleted, person/get +
get_credits PersonEnrichmentRequested, jobs/enrichment_staleness,
http-axum search, movies/sync_poster + poster_sync_handler +
image-converter backfill + users/update_profile ImageStored (object
storage), activitypub federation_event_bridge (reacting to events).
rides write, deferred: social/execute (see report),
activitypub review_handler MovieEnrichmentRequested (after
save_remote_review).
This commit is contained in:
2026-10-26 01:02:34 +01:00
parent 2f133afb96
commit 6c295866a0
51 changed files with 1281 additions and 329 deletions

View File

@@ -8,7 +8,7 @@ use domain::{
models::WatchlistEntry,
models::{MetadataSearchCriteria, Movie},
ports::{MetadataClient, MovieCommand, WatchlistRepository},
testing::{FakeMetadataClient, InMemoryStore, NoopEventPublisher},
testing::{FakeMetadataClient, InMemoryStore},
value_objects::{ExternalMetadataId, MovieId, PosterUrl, UserId},
};
use uuid::Uuid;
@@ -21,7 +21,6 @@ fn make_logger(
movies: &Arc<InMemoryStore>,
reviews: &Arc<InMemoryStore>,
watchlist: &Arc<InMemoryStore>,
events: &Arc<NoopEventPublisher>,
) -> DefaultReviewLogger {
DefaultReviewLogger::new(
Arc::clone(movies) as _,
@@ -29,7 +28,6 @@ fn make_logger(
Arc::clone(reviews) as _,
Arc::clone(watchlist) as _,
Arc::new(FakeMetadataClient) as _,
Arc::clone(events) as _,
)
}
@@ -38,8 +36,7 @@ async fn logs_review_with_manual_movie() {
let movies = InMemoryStore::new();
let reviews = Arc::clone(&movies);
let watchlist = Arc::clone(&movies);
let events = NoopEventPublisher::new();
let logger = make_logger(&movies, &reviews, &watchlist, &events);
let logger = make_logger(&movies, &reviews, &watchlist);
let uid = Uuid::new_v4();
let cmd = log_review_command_manual(uid, "Test Film", 2024);
@@ -55,8 +52,7 @@ async fn removes_from_watchlist_on_review() {
let movies = InMemoryStore::new();
let reviews = Arc::clone(&movies);
let watchlist = Arc::clone(&movies);
let events = NoopEventPublisher::new();
let logger = make_logger(&movies, &reviews, &watchlist, &events);
let logger = make_logger(&movies, &reviews, &watchlist);
let uid = Uuid::new_v4();
let user_id = UserId::from_uuid(uid);
@@ -64,7 +60,7 @@ async fn removes_from_watchlist_on_review() {
// Create and store movie
let movie = test_movie_with_year("Watchlisted Film", 2024);
let movie_id = movie.id().value();
movies.upsert_movie(&movie).await.unwrap();
movies.upsert_movie(&movie, &[]).await.unwrap();
// Add to watchlist
let entry = WatchlistEntry::new(user_id.clone(), MovieId::from_uuid(movie_id));
@@ -87,13 +83,6 @@ async fn removes_from_watchlist_on_review() {
.any(|e| matches!(e, domain::events::DomainEvent::WatchlistEntryRemoved { .. })),
"the removal event rides the watchlist write"
);
assert!(
!events
.published()
.iter()
.any(|e| matches!(e, domain::events::DomainEvent::WatchlistEntryRemoved { .. })),
"no longer published out of band"
);
}
#[tokio::test]
@@ -101,12 +90,11 @@ async fn logs_review_with_existing_movie_by_id() {
let movies = InMemoryStore::new();
let reviews = Arc::clone(&movies);
let watchlist = Arc::clone(&movies);
let events = NoopEventPublisher::new();
let logger = make_logger(&movies, &reviews, &watchlist, &events);
let logger = make_logger(&movies, &reviews, &watchlist);
let movie = test_movie_with_year("Existing Film", 2020);
let movie_uuid = movie.id().value();
movies.upsert_movie(&movie).await.unwrap();
movies.upsert_movie(&movie, &[]).await.unwrap();
let cmd = LogReviewCommand {
rating: 3,
@@ -124,8 +112,7 @@ async fn existing_movie_not_found_returns_error() {
let movies = InMemoryStore::new();
let reviews = Arc::clone(&movies);
let watchlist = Arc::clone(&movies);
let events = NoopEventPublisher::new();
let logger = make_logger(&movies, &reviews, &watchlist, &events);
let logger = make_logger(&movies, &reviews, &watchlist);
let cmd = log_review_command(Uuid::new_v4(), Uuid::new_v4());
@@ -137,8 +124,7 @@ async fn invalid_rating_returns_error() {
let movies = InMemoryStore::new();
let reviews = Arc::clone(&movies);
let watchlist = Arc::clone(&movies);
let events = NoopEventPublisher::new();
let logger = make_logger(&movies, &reviews, &watchlist, &events);
let logger = make_logger(&movies, &reviews, &watchlist);
let cmd = LogReviewCommand {
rating: 6,
@@ -157,8 +143,7 @@ async fn watchlist_not_present_does_not_publish_removed() {
let movies = InMemoryStore::new();
let reviews = Arc::clone(&movies);
let watchlist = Arc::clone(&movies);
let events = NoopEventPublisher::new();
let logger = make_logger(&movies, &reviews, &watchlist, &events);
let logger = make_logger(&movies, &reviews, &watchlist);
let cmd = log_review_command_manual(Uuid::new_v4(), "No Watchlist Film", 2024);
@@ -197,11 +182,10 @@ impl MetadataClient for MetadataClientWithExternalId {
}
#[tokio::test]
async fn publishes_movie_discovered_for_new_movie_with_external_id() {
async fn movie_discovered_rides_the_movie_write_for_a_new_movie_with_external_id() {
let movies = InMemoryStore::new();
let reviews = Arc::clone(&movies);
let watchlist = Arc::clone(&movies);
let events = NoopEventPublisher::new();
let logger = DefaultReviewLogger::new(
Arc::clone(&movies) as _,
@@ -209,7 +193,6 @@ async fn publishes_movie_discovered_for_new_movie_with_external_id() {
Arc::clone(&reviews) as _,
Arc::clone(&watchlist) as _,
Arc::new(MetadataClientWithExternalId) as _,
Arc::clone(&events) as _,
);
let cmd = LogReviewCommand {
@@ -219,12 +202,12 @@ async fn publishes_movie_discovered_for_new_movie_with_external_id() {
logger.log_review(cmd).await.unwrap();
let published = events.published();
let published = movies.recorded_events();
assert!(
published
.iter()
.any(|e| matches!(e, domain::events::DomainEvent::MovieDiscovered { .. })),
"should publish MovieDiscovered for new movie with external_metadata_id"
"MovieDiscovered must be recorded with the movie write"
);
}
@@ -237,7 +220,6 @@ async fn logging_a_review_fails_when_the_review_store_refuses() {
Arc::new(domain::testing::RefusingReviewRepository),
Arc::clone(&movies) as _,
Arc::new(FakeMetadataClient),
NoopEventPublisher::new(),
);
let result = logger
@@ -256,7 +238,6 @@ async fn logging_a_review_saves_the_review_and_its_event_together() {
Arc::clone(&movies) as _,
Arc::clone(&movies) as _,
Arc::new(FakeMetadataClient),
Arc::new(domain::testing::RefusingEventPublisher),
);
logger
@@ -282,7 +263,6 @@ async fn a_refused_save_propagates_and_records_nothing() {
Arc::new(domain::testing::RefusingReviewRepository),
Arc::clone(&movies) as _,
Arc::new(FakeMetadataClient),
NoopEventPublisher::new(),
);
let result = logger
@@ -303,7 +283,6 @@ async fn enrichment_request_rides_the_review_save() {
Arc::clone(&movies) as _,
Arc::clone(&movies) as _,
Arc::new(MetadataClientWithExternalId),
NoopEventPublisher::new(),
);
logger