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

@@ -92,7 +92,9 @@ async fn generates_wrapup_and_marks_complete() {
}
#[tokio::test]
async fn skips_if_already_generating() {
async fn a_wrapup_left_generating_is_completed_on_redelivery() {
// A crash after `Generating` was written (or a failed `set_complete`) leaves
// the record stuck; the redelivered event must finish it, not skip it.
let repo = InMemoryWrapUpRepository::new();
let wrapup_id = WrapUpId::generate();
@@ -114,21 +116,62 @@ async fn skips_if_already_generating() {
wrapup_stats: InMemoryWrapUpStatsQuery::new(),
};
let result = handle_requested::execute(
handle_requested::execute(
&deps,
wrapup_id.clone(),
None,
NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
NaiveDate::from_ymd_opt(2024, 12, 31).unwrap(),
)
.await;
.await
.unwrap();
assert!(result.is_ok());
// Status should still be Generating (not changed to Ready)
let final_rec = repo.get_by_id(&wrapup_id).await.unwrap().unwrap();
assert_eq!(final_rec.status, WrapUpStatus::Generating);
assert!(repo.recorded_events().is_empty());
assert_eq!(final_rec.status, WrapUpStatus::Ready);
assert!(final_rec.report.is_some());
assert_eq!(repo.recorded_events().len(), 1);
}
#[tokio::test]
async fn wrapup_is_idempotent_when_run_twice() {
let repo = InMemoryWrapUpRepository::new();
let wrapup_id = WrapUpId::generate();
let record = WrapUpRecord {
id: wrapup_id.clone(),
user_id: None,
start_date: NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
end_date: NaiveDate::from_ymd_opt(2024, 12, 31).unwrap(),
status: WrapUpStatus::Pending,
report: None,
error_message: None,
created_at: Utc::now().naive_utc(),
completed_at: None,
};
repo.create(&record, &[]).await.unwrap();
let deps = HandleWrapUpRequestedDeps {
wrapup_repo: repo.clone(),
wrapup_stats: InMemoryWrapUpStatsQuery::new(),
};
for _ in 0..2 {
handle_requested::execute(
&deps,
wrapup_id.clone(),
None,
NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
NaiveDate::from_ymd_opt(2024, 12, 31).unwrap(),
)
.await
.unwrap();
}
let rec = repo.get_by_id(&wrapup_id).await.unwrap().unwrap();
assert_eq!(rec.status, WrapUpStatus::Ready);
assert_eq!(
repo.recorded_events().len(),
1,
"WrapUpCompleted emitted once"
);
}
#[tokio::test]