use distributed::{
CommitBatch, InboxReceipt, InboxStore, OutboxMessage, OutboxStore, RepositoryError,
TransactionalCommit,
};
use super::outbox_support::find_outbox_by_id;
use super::scenario::unique_id;
fn batch_with(outbox: Vec<OutboxMessage>, receipts: Vec<InboxReceipt>) -> CommitBatch<'static> {
let mut batch = CommitBatch::new(Vec::new());
batch.outbox_messages = outbox;
batch.inbox_receipts = receipts;
batch
}
async fn outbox_present<S: OutboxStore + Send + Sync>(outbox: &S, id: &str) -> bool {
find_outbox_by_id(outbox, id).await.is_some()
}
pub async fn inbox_records_dedupes_and_fences_with_real_effects<R, S>(repo: R, outbox: S)
where
R: InboxStore + TransactionalCommit + Clone + Send + Sync + 'static,
S: OutboxStore + Send + Sync,
{
let consumer = unique_id("consumer");
let m1 = unique_id("msg");
let effect1 = unique_id("effect");
let effect2 = unique_id("effect");
assert!(!repo.inbox_contains(&consumer, &m1).await.unwrap());
repo.commit_batch(batch_with(
vec![OutboxMessage::create(&effect1, "effect.applied", b"{}".to_vec()).unwrap()],
vec![InboxReceipt::new(&consumer, &m1)],
))
.await
.expect("first delivery commits");
assert!(repo.inbox_contains(&consumer, &m1).await.unwrap());
assert!(
outbox_present(&outbox, &effect1).await,
"first effect landed"
);
let err = repo
.commit_batch(batch_with(
vec![OutboxMessage::create(&effect2, "effect.applied", b"{}".to_vec()).unwrap()],
vec![InboxReceipt::new(&consumer, &m1)],
))
.await
.expect_err("replay is rejected");
assert!(
matches!(err, RepositoryError::DuplicateInboxReceipt { ref message_id, .. } if *message_id == m1),
"got {err:?}"
);
assert!(
!outbox_present(&outbox, &effect2).await,
"the duplicate rolled the real effect back — effectively-once fence"
);
let a = unique_id("msg");
let b = unique_id("msg");
repo.commit_batch(batch_with(
Vec::new(),
vec![
InboxReceipt::new(&consumer, &a),
InboxReceipt::new(&consumer, &b),
],
))
.await
.expect("distinct receipts commit");
assert!(repo.inbox_contains(&consumer, &a).await.unwrap());
assert!(repo.inbox_contains(&consumer, &b).await.unwrap());
let other = unique_id("consumer");
repo.commit_batch(batch_with(Vec::new(), vec![InboxReceipt::new(&other, &m1)]))
.await
.expect("a different consumer records the same message id independently");
assert!(repo.inbox_contains(&other, &m1).await.unwrap());
}
pub async fn inbox_rejects_empty_receipt<R>(repo: R)
where
R: InboxStore + TransactionalCommit + Clone + Send + Sync + 'static,
{
for receipt in [
InboxReceipt::new("", unique_id("msg")),
InboxReceipt::new(unique_id("consumer"), ""),
] {
let err = repo
.commit_batch(batch_with(Vec::new(), vec![receipt]))
.await
.expect_err("an empty receipt field is rejected");
assert!(
matches!(err, RepositoryError::InvalidInboxReceipt { .. }),
"got {err:?}"
);
}
}