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:
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
|
||||
use domain::models::{WatchEvent, WatchEventSource};
|
||||
use domain::ports::{MovieCommand, WatchEventCommand};
|
||||
use domain::testing::{InMemoryStore, NoopEventPublisher};
|
||||
use domain::testing::InMemoryStore;
|
||||
use domain::value_objects::UserId;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -42,7 +42,7 @@ async fn confirms_watch_event_via_review_logger() {
|
||||
None,
|
||||
);
|
||||
let event_id = event.id().value();
|
||||
watch_events.save(&event).await.unwrap();
|
||||
watch_events.save(&event, &[]).await.unwrap();
|
||||
|
||||
let result = confirm::execute(
|
||||
&deps(&watch_events, noop_logger()),
|
||||
@@ -86,7 +86,7 @@ async fn confirms_event_with_external_metadata_id_and_no_movie_id() {
|
||||
None,
|
||||
);
|
||||
let event_id = event.id().value();
|
||||
watch_events.save(&event).await.unwrap();
|
||||
watch_events.save(&event, &[]).await.unwrap();
|
||||
|
||||
let result = confirm::execute(
|
||||
&deps(&watch_events, noop_logger()),
|
||||
@@ -121,7 +121,7 @@ async fn rejects_other_users_event() {
|
||||
None,
|
||||
);
|
||||
let event_id = event.id().value();
|
||||
watch_events.save(&event).await.unwrap();
|
||||
watch_events.save(&event, &[]).await.unwrap();
|
||||
|
||||
let result = confirm::execute(
|
||||
&deps(&watch_events, noop_logger()),
|
||||
@@ -148,7 +148,6 @@ async fn fails_when_event_not_found() {
|
||||
#[tokio::test]
|
||||
async fn confirms_event_with_movie_id() {
|
||||
let watch_events = InMemoryStore::new();
|
||||
let events = NoopEventPublisher::new();
|
||||
let uid = Uuid::new_v4();
|
||||
let movie_uuid = Uuid::new_v4();
|
||||
|
||||
@@ -162,7 +161,7 @@ async fn confirms_event_with_movie_id() {
|
||||
Some(domain::value_objects::MovieId::from_uuid(movie_uuid)),
|
||||
);
|
||||
let event_id = event.id().value();
|
||||
watch_events.save(&event).await.unwrap();
|
||||
watch_events.save(&event, &[]).await.unwrap();
|
||||
|
||||
// Also seed movie repo so review_logger can find it
|
||||
let movies = domain::testing::InMemoryStore::new();
|
||||
@@ -174,7 +173,7 @@ async fn confirms_event_with_movie_id() {
|
||||
None,
|
||||
None,
|
||||
);
|
||||
movies.upsert_movie(&movie).await.unwrap();
|
||||
movies.upsert_movie(&movie, &[]).await.unwrap();
|
||||
|
||||
// Build a real review logger
|
||||
let reviews = domain::testing::InMemoryStore::new();
|
||||
@@ -186,7 +185,6 @@ async fn confirms_event_with_movie_id() {
|
||||
Arc::clone(&reviews) as _,
|
||||
Arc::clone(&watchlist) as _,
|
||||
Arc::new(domain::testing::FakeMetadataClient) as _,
|
||||
Arc::clone(&events) as _,
|
||||
));
|
||||
|
||||
let result = confirm::execute(
|
||||
@@ -214,7 +212,7 @@ async fn confirms_event_without_movie_id_and_without_external_metadata_id() {
|
||||
None,
|
||||
);
|
||||
let event_id = event.id().value();
|
||||
watch_events.save(&event).await.unwrap();
|
||||
watch_events.save(&event, &[]).await.unwrap();
|
||||
|
||||
let result = confirm::execute(
|
||||
&deps(&watch_events, noop_logger()),
|
||||
@@ -260,8 +258,8 @@ async fn confirms_multiple_events() {
|
||||
);
|
||||
let id2 = event2.id().value();
|
||||
|
||||
watch_events.save(&event1).await.unwrap();
|
||||
watch_events.save(&event2).await.unwrap();
|
||||
watch_events.save(&event1, &[]).await.unwrap();
|
||||
watch_events.save(&event2, &[]).await.unwrap();
|
||||
|
||||
let result = confirm::execute(
|
||||
&deps(&watch_events, noop_logger()),
|
||||
@@ -302,7 +300,7 @@ async fn confirms_event_without_year() {
|
||||
None,
|
||||
);
|
||||
let event_id = event.id().value();
|
||||
watch_events.save(&event).await.unwrap();
|
||||
watch_events.save(&event, &[]).await.unwrap();
|
||||
|
||||
let result = confirm::execute(
|
||||
&deps(&watch_events, noop_logger()),
|
||||
|
||||
@@ -70,8 +70,8 @@ async fn dismisses_existing_events() {
|
||||
);
|
||||
let id1 = e1.id().value();
|
||||
let id2 = e2.id().value();
|
||||
watch_events.save(&e1).await.unwrap();
|
||||
watch_events.save(&e2).await.unwrap();
|
||||
watch_events.save(&e1, &[]).await.unwrap();
|
||||
watch_events.save(&e2, &[]).await.unwrap();
|
||||
|
||||
let result = dismiss::execute(
|
||||
&deps(&watch_events),
|
||||
|
||||
@@ -46,7 +46,7 @@ async fn returns_pending_events() {
|
||||
Utc::now().naive_utc(),
|
||||
None,
|
||||
);
|
||||
events.save(&event).await.unwrap();
|
||||
events.save(&event, &[]).await.unwrap();
|
||||
|
||||
let result = get_queue::execute(&deps(&events), GetWatchQueueQuery { user_id })
|
||||
.await
|
||||
|
||||
@@ -2,8 +2,7 @@ use crate::support::generate_webhook_token_command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::models::WatchEventSource;
|
||||
use domain::ports::EventPublisher;
|
||||
use domain::testing::{InMemoryStore, NoopEventPublisher};
|
||||
use domain::testing::{InMemoryStore, RefusingWatchEventCommand};
|
||||
use uuid::Uuid;
|
||||
|
||||
use application::integrations::commands::IngestWatchEventCommand;
|
||||
@@ -27,10 +26,9 @@ impl domain::ports::MediaServerParser for FakeParser {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn ingests_watch_event() {
|
||||
async fn ingesting_saves_the_watch_event_and_records_its_event_together() {
|
||||
let tokens = InMemoryStore::new();
|
||||
let watch_events = Arc::clone(&tokens);
|
||||
let event_publisher: Arc<dyn EventPublisher> = NoopEventPublisher::new();
|
||||
|
||||
let user_id = Uuid::new_v4();
|
||||
let generated = generate_token::execute(
|
||||
@@ -46,7 +44,6 @@ async fn ingests_watch_event() {
|
||||
webhook_token: Arc::clone(&tokens) as _,
|
||||
watch_event_command: Arc::clone(&watch_events) as _,
|
||||
watch_event_query: Arc::clone(&watch_events) as _,
|
||||
event_publisher: Arc::clone(&event_publisher),
|
||||
};
|
||||
|
||||
let result = ingest::execute(
|
||||
@@ -61,19 +58,58 @@ async fn ingests_watch_event() {
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(watch_events.rows_in("watch_events"), 1);
|
||||
let recorded = tokens.recorded_events();
|
||||
assert_eq!(recorded.len(), 1);
|
||||
assert!(matches!(
|
||||
&recorded[0],
|
||||
domain::events::DomainEvent::WatchEventIngested { title, .. } if title == "Test"
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_refused_save_propagates_and_records_nothing() {
|
||||
let tokens = InMemoryStore::new();
|
||||
let user_id = Uuid::new_v4();
|
||||
let generated = generate_token::execute(
|
||||
&GenerateWebhookTokenDeps {
|
||||
webhook_token: Arc::clone(&tokens) as _,
|
||||
},
|
||||
generate_webhook_token_command(user_id),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let deps = IngestWatchEventDeps {
|
||||
webhook_token: Arc::clone(&tokens) as _,
|
||||
watch_event_command: Arc::new(RefusingWatchEventCommand),
|
||||
watch_event_query: Arc::clone(&tokens) as _,
|
||||
};
|
||||
|
||||
let result = ingest::execute(
|
||||
&deps,
|
||||
IngestWatchEventCommand {
|
||||
token: generated.token_plaintext,
|
||||
raw_payload: vec![],
|
||||
source: WatchEventSource::Jellyfin,
|
||||
},
|
||||
&FakeParser,
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_eq!(tokens.rows_in("watch_events"), 0);
|
||||
assert!(tokens.recorded_events().is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rejects_invalid_token() {
|
||||
let tokens = InMemoryStore::new();
|
||||
let watch_events = Arc::clone(&tokens);
|
||||
let event_publisher: Arc<dyn EventPublisher> = NoopEventPublisher::new();
|
||||
|
||||
let deps = IngestWatchEventDeps {
|
||||
webhook_token: Arc::clone(&tokens) as _,
|
||||
watch_event_command: Arc::clone(&watch_events) as _,
|
||||
watch_event_query: Arc::clone(&watch_events) as _,
|
||||
event_publisher: Arc::clone(&event_publisher),
|
||||
};
|
||||
|
||||
let result = ingest::execute(
|
||||
|
||||
Reference in New Issue
Block a user