feat: worker relays outbox to nats
This commit is contained in:
@@ -131,3 +131,194 @@ fn a_message_round_trips_its_event_id_and_decodes_without_one() {
|
||||
let json = serde_json::to_string(&unit).unwrap();
|
||||
assert_eq!(serde_json::from_str::<EventMessage>(&json).unwrap(), unit);
|
||||
}
|
||||
|
||||
/// One sample per `DomainEvent` variant. `assert_covers_every_variant` is an
|
||||
/// exhaustive match, so adding a variant breaks the build until it is added
|
||||
/// here too.
|
||||
fn every_event() -> Vec<DomainEvent> {
|
||||
use domain::{
|
||||
models::{ExternalPersonId, PersonId},
|
||||
value_objects::{FollowTarget, GoalId, PosterPath, SocialIdentity, WrapUpId},
|
||||
};
|
||||
let user = || UserId::from_uuid(Uuid::new_v4());
|
||||
let movie = || MovieId::from_uuid(Uuid::new_v4());
|
||||
let local = || SocialIdentity::Local(UserId::from_uuid(Uuid::new_v4()));
|
||||
let remote = || SocialIdentity::Remote {
|
||||
actor_url: "https://remote.example/users/a".into(),
|
||||
};
|
||||
let date = |d| chrono::NaiveDate::from_ymd_opt(2026, 1, d).unwrap();
|
||||
vec![
|
||||
review_logged(),
|
||||
review_updated(),
|
||||
movie_discovered(),
|
||||
DomainEvent::MovieDeleted {
|
||||
movie_id: movie(),
|
||||
poster_path: Some(PosterPath::new("p/x.jpg".into()).unwrap()),
|
||||
},
|
||||
DomainEvent::UserUpdated { user_id: user() },
|
||||
DomainEvent::ReviewDeleted {
|
||||
review_id: ReviewId::from_uuid(Uuid::new_v4()),
|
||||
user_id: user(),
|
||||
},
|
||||
DomainEvent::MovieEnrichmentRequested {
|
||||
movie_id: movie(),
|
||||
external_metadata_id: ExternalMetadataId::new("tt7654321".into()).unwrap(),
|
||||
},
|
||||
DomainEvent::PersonEnrichmentRequested {
|
||||
person_id: PersonId::from_uuid(Uuid::new_v4()),
|
||||
external_person_id: ExternalPersonId::new("42"),
|
||||
},
|
||||
DomainEvent::ImageStored { key: "k".into() },
|
||||
DomainEvent::WatchlistEntryAdded {
|
||||
user_id: user(),
|
||||
movie_id: movie(),
|
||||
movie_title: "Heat".into(),
|
||||
release_year: 1995,
|
||||
external_metadata_id: Some("tt0113277".into()),
|
||||
added_at: fixed_dt(),
|
||||
},
|
||||
DomainEvent::WatchlistEntryRemoved {
|
||||
user_id: user(),
|
||||
movie_id: movie(),
|
||||
},
|
||||
DomainEvent::FollowRequested {
|
||||
follower: user(),
|
||||
target: FollowTarget::Handle("a@b.example".into()),
|
||||
},
|
||||
DomainEvent::FollowAccepted {
|
||||
owner: user(),
|
||||
requester: remote(),
|
||||
},
|
||||
DomainEvent::FollowRejected {
|
||||
owner: user(),
|
||||
requester: local(),
|
||||
},
|
||||
DomainEvent::Unfollowed {
|
||||
follower: user(),
|
||||
target: remote(),
|
||||
},
|
||||
DomainEvent::FollowerRemoved {
|
||||
owner: user(),
|
||||
follower: local(),
|
||||
},
|
||||
DomainEvent::ActorBlocked {
|
||||
blocker: user(),
|
||||
target: remote(),
|
||||
},
|
||||
DomainEvent::ActorUnblocked {
|
||||
blocker: user(),
|
||||
target: remote(),
|
||||
},
|
||||
DomainEvent::BackfillFollower {
|
||||
owner_user_id: user(),
|
||||
follower_inbox_url: "https://remote.example/inbox".into(),
|
||||
},
|
||||
DomainEvent::FederationDeliveryRequested {
|
||||
inbox_url: "https://remote.example/inbox".into(),
|
||||
activity_json: serde_json::json!({"type": "Create"}),
|
||||
signing_actor_id: Uuid::new_v4(),
|
||||
},
|
||||
DomainEvent::WatchEventIngested {
|
||||
user_id: user(),
|
||||
title: "Heat".into(),
|
||||
source: "jellyfin".into(),
|
||||
},
|
||||
DomainEvent::WrapUpRequested {
|
||||
wrapup_id: WrapUpId::from_uuid(Uuid::new_v4()),
|
||||
user_id: Some(user()),
|
||||
start_date: date(1),
|
||||
end_date: date(2),
|
||||
},
|
||||
DomainEvent::WrapUpCompleted {
|
||||
wrapup_id: WrapUpId::from_uuid(Uuid::new_v4()),
|
||||
},
|
||||
DomainEvent::SearchReindexRequested,
|
||||
DomainEvent::PosterSynced { movie_id: movie() },
|
||||
DomainEvent::GoalCreated {
|
||||
goal_id: GoalId::from_uuid(Uuid::new_v4()),
|
||||
user_id: user(),
|
||||
year: 2026,
|
||||
target_count: 50,
|
||||
},
|
||||
DomainEvent::GoalUpdated {
|
||||
goal_id: GoalId::from_uuid(Uuid::new_v4()),
|
||||
user_id: user(),
|
||||
year: 2026,
|
||||
target_count: 60,
|
||||
},
|
||||
DomainEvent::GoalDeleted {
|
||||
goal_id: GoalId::from_uuid(Uuid::new_v4()),
|
||||
user_id: user(),
|
||||
year: 2026,
|
||||
},
|
||||
DomainEvent::UserDeleted { user_id: user() },
|
||||
DomainEvent::UserAccountMoved {
|
||||
user_id: user(),
|
||||
new_actor_url: "https://new.example/users/a".into(),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn assert_covers_every_variant(event: &DomainEvent) {
|
||||
use DomainEvent::*;
|
||||
match event {
|
||||
ReviewLogged { .. }
|
||||
| ReviewUpdated { .. }
|
||||
| MovieDiscovered { .. }
|
||||
| MovieDeleted { .. }
|
||||
| UserUpdated { .. }
|
||||
| ReviewDeleted { .. }
|
||||
| MovieEnrichmentRequested { .. }
|
||||
| PersonEnrichmentRequested { .. }
|
||||
| ImageStored { .. }
|
||||
| WatchlistEntryAdded { .. }
|
||||
| WatchlistEntryRemoved { .. }
|
||||
| FollowRequested { .. }
|
||||
| FollowAccepted { .. }
|
||||
| FollowRejected { .. }
|
||||
| Unfollowed { .. }
|
||||
| FollowerRemoved { .. }
|
||||
| ActorBlocked { .. }
|
||||
| ActorUnblocked { .. }
|
||||
| BackfillFollower { .. }
|
||||
| FederationDeliveryRequested { .. }
|
||||
| WatchEventIngested { .. }
|
||||
| WrapUpRequested { .. }
|
||||
| WrapUpCompleted { .. }
|
||||
| SearchReindexRequested
|
||||
| PosterSynced { .. }
|
||||
| GoalCreated { .. }
|
||||
| GoalUpdated { .. }
|
||||
| GoalDeleted { .. }
|
||||
| UserDeleted { .. }
|
||||
| UserAccountMoved { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_event_round_trips_through_an_event_message_with_and_without_an_id() {
|
||||
use event_payload::EventMessage;
|
||||
|
||||
let events = every_event();
|
||||
events.iter().for_each(assert_covers_every_variant);
|
||||
let types: std::collections::HashSet<_> = events
|
||||
.iter()
|
||||
.map(|e| EventPayload::from(e).event_type())
|
||||
.collect();
|
||||
assert_eq!(types.len(), events.len(), "one sample per variant");
|
||||
|
||||
for event in &events {
|
||||
for id in [Some(Uuid::new_v4().to_string()), None] {
|
||||
let message = EventMessage {
|
||||
event_id: id.clone(),
|
||||
payload: EventPayload::from(event),
|
||||
};
|
||||
let json = serde_json::to_string(&message).unwrap();
|
||||
let back: EventMessage = serde_json::from_str(&json)
|
||||
.unwrap_or_else(|e| panic!("{} decode: {e}", message.payload.event_type()));
|
||||
assert_eq!(back.event_id, id);
|
||||
let recovered = DomainEvent::try_from(back.payload).unwrap();
|
||||
assert_eq!(EventPayload::from(event), EventPayload::from(&recovered));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user