purge: skip broadcasts of deleted authors, purge outbox log, guards, 410 inbox test

This commit is contained in:
2026-10-26 02:39:06 +01:00
parent 51a70b5260
commit 80dbdec107
9 changed files with 318 additions and 27 deletions

View File

@@ -16,7 +16,9 @@ use domain::{
errors::DomainError,
events::DomainEvent,
models::{DiaryEntry, WatchlistWithMovie},
ports::{EventHandler, LocalApContentQuery, MovieCommand},
ports::{
EventHandler, FederationDeletionNotifier, LocalApContentQuery, MovieCommand, UserRepository,
},
testing::{
InMemoryStore, PanicGoalQuery, PanicReviewRepository, PanicStatsRepository, test_movie,
},
@@ -71,6 +73,8 @@ struct Harness {
delivered: mpsc::UnboundedReceiver<serde_json::Value>,
user: UserId,
movie_id: MovieId,
store: Arc<InMemoryStore>,
service: Arc<ActivityPubService>,
}
async fn harness() -> Harness {
@@ -119,14 +123,23 @@ async fn harness() -> Harness {
let store = InMemoryStore::new();
let movie = test_movie("Heat");
store.upsert_movie(&movie, &[]).await.unwrap();
let service = Arc::new(service);
store
.save(&domain::testing::test_user_with_role(
user.clone(),
domain::models::UserRole::Standard,
))
.await
.unwrap();
let handler = ActivityPubEventHandler::new(
Arc::new(service),
Arc::clone(&service),
Arc::new(NoContent),
Arc::new(PanicReviewRepository),
store.clone(),
Arc::new(PanicGoalQuery),
Arc::new(PanicStatsRepository),
store,
store.clone(),
store.clone(),
InstanceIdentity::new("https://diary.example"),
);
Harness {
@@ -134,6 +147,8 @@ async fn harness() -> Harness {
delivered: rx,
user,
movie_id: movie.id().clone(),
store,
service,
}
}
@@ -191,3 +206,61 @@ async fn redelivered_delete_gets_a_fresh_activity_id_known_k_ap_limitation() {
assert_ne!(first, second);
}
/// Queued events of a deleted account must not sign and send from its
/// tombstone; the account's own Delete still goes out.
#[tokio::test]
async fn a_deleted_account_sends_nothing_but_its_delete() {
let mut h = harness().await;
h.store.mark_deleted(&h.user, &[]).await.unwrap();
let logged = DomainEvent::ReviewLogged {
review_id: domain::value_objects::ReviewId::generate(),
user_id: h.user.clone(),
movie_id: h.movie_id.clone(),
rating: domain::value_objects::Rating::new(4).unwrap(),
watched_at: NaiveDate::from_ymd_opt(2026, 1, 1)
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap(),
};
let added = DomainEvent::WatchlistEntryAdded {
user_id: h.user.clone(),
movie_id: h.movie_id.clone(),
movie_title: "Heat".into(),
release_year: 1995,
external_metadata_id: None,
added_at: NaiveDate::from_ymd_opt(2026, 1, 1)
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap(),
};
let queued = DomainEvent::FederationDeliveryRequested {
inbox_url: "https://remote.example/inbox".into(),
activity_json: serde_json::json!({"type": "Create"}),
signing_actor_id: h.user.value(),
};
// The review repository panics if the handler gets past the guard.
for event in [&logged, &added, &queued] {
h.handler.handle(event).await.unwrap();
}
assert!(
tokio::time::timeout(Duration::from_millis(300), h.delivered.recv())
.await
.is_err(),
"nothing is delivered for a deleted account"
);
activitypub::ApDeletionNotifier::new(
Arc::clone(&h.service),
InstanceIdentity::new("https://diary.example"),
)
.notify_deleted(&h.user)
.await
.unwrap();
let delete = tokio::time::timeout(Duration::from_secs(5), h.delivered.recv())
.await
.expect("the Delete is delivered")
.unwrap();
assert_eq!(delete["type"], "Delete");
}