feat: idempotent handlers, deterministic activity ids

This commit is contained in:
2026-10-26 01:27:03 +01:00
parent 7f9c252d35
commit 4cb2972f63
11 changed files with 719 additions and 19 deletions

View File

@@ -51,3 +51,75 @@ impl EventHandler for FollowBackfillHandler {
}
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use std::sync::Mutex;
use domain::models::FollowedActorInfo;
use domain::value_objects::UserId;
use uuid::Uuid;
use super::*;
/// Models the real port: imports are keyed upserts and backfill sends carry
/// deterministic activity ids, so a repeat lands on the same key.
#[derive(Default)]
struct KeyedBackfill {
imported: Mutex<HashSet<(String, String)>>,
sent: Mutex<HashSet<(Uuid, String)>>,
}
#[async_trait]
impl ApBackfillPort for KeyedBackfill {
async fn get_following(&self, _: Uuid) -> Result<Vec<FollowedActorInfo>, DomainError> {
Ok(vec![FollowedActorInfo {
url: "https://remote.example/users/a".into(),
outbox_url: Some("https://remote.example/users/a/outbox".into()),
}])
}
async fn import_remote_outbox(&self, outbox: &str, actor: &str) -> Result<(), DomainError> {
self.imported
.lock()
.unwrap()
.insert((outbox.into(), actor.into()));
Ok(())
}
async fn run_backfill_for_follower(
&self,
owner: Uuid,
inbox: String,
) -> Result<(), DomainError> {
self.sent.lock().unwrap().insert((owner, inbox));
Ok(())
}
}
#[tokio::test]
async fn follow_backfill_is_idempotent_when_run_twice() {
let port = Arc::new(KeyedBackfill::default());
let handler = FollowBackfillHandler {
backfill: port.clone(),
};
let owner = UserId::from_uuid(Uuid::new_v4());
let accepted = DomainEvent::FollowAccepted {
owner: owner.clone(),
requester: SocialIdentity::Remote {
actor_url: "https://remote.example/users/a".into(),
},
};
let backfill = DomainEvent::BackfillFollower {
owner_user_id: owner,
follower_inbox_url: "https://remote.example/inbox".into(),
};
for event in [&accepted, &backfill] {
handler.handle(event).await.unwrap();
handler.handle(event).await.unwrap();
}
assert_eq!(port.imported.lock().unwrap().len(), 1);
assert_eq!(port.sent.lock().unwrap().len(), 1);
}
}