fix: relay shutdown, claim lease, dead-letter undecodable, dedup window
This commit is contained in:
@@ -190,3 +190,89 @@ mod identity {
|
||||
assert!(envelope.id().is_none());
|
||||
}
|
||||
}
|
||||
|
||||
mod lease {
|
||||
use domain::ports::EventConsumer;
|
||||
use futures::StreamExt;
|
||||
|
||||
use super::{claim_batch, config};
|
||||
use crate::event_queue::SqliteEventQueue;
|
||||
|
||||
async fn pool() -> sqlx::SqlitePool {
|
||||
let pool = sqlx::sqlite::SqlitePoolOptions::new()
|
||||
.max_connections(1)
|
||||
.connect("sqlite::memory:")
|
||||
.await
|
||||
.unwrap();
|
||||
crate::migrate(&pool).await.unwrap();
|
||||
pool
|
||||
}
|
||||
|
||||
async fn claim_then_age(pool: &sqlx::SqlitePool, claimed_at: Option<&str>) {
|
||||
super::insert(pool, 1).await;
|
||||
assert_eq!(claim_batch(pool, &config(10)).await.unwrap().len(), 1);
|
||||
sqlx::query("UPDATE event_queue SET claimed_at = ?")
|
||||
.bind(claimed_at)
|
||||
.execute(pool)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn ago(seconds: i64) -> String {
|
||||
(chrono::Utc::now() - chrono::Duration::seconds(seconds))
|
||||
.format("%Y-%m-%dT%H:%M:%SZ")
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn an_expired_claim_is_reclaimed() {
|
||||
let pool = pool().await;
|
||||
claim_then_age(&pool, Some(&ago(super::super::CLAIM_LEASE_SECONDS + 60))).await;
|
||||
|
||||
let again = claim_batch(&pool, &config(10)).await.unwrap();
|
||||
|
||||
assert_eq!(again.len(), 1, "stranded row is claimed again");
|
||||
let claimed: String = sqlx::query_scalar("SELECT claimed_at FROM event_queue")
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(claimed > ago(60), "lease restarted: {claimed}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_legacy_processing_row_without_claimed_at_counts_as_expired() {
|
||||
let pool = pool().await;
|
||||
claim_then_age(&pool, None).await;
|
||||
|
||||
assert_eq!(claim_batch(&pool, &config(10)).await.unwrap().len(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_fresh_claim_is_not_reclaimed() {
|
||||
let pool = pool().await;
|
||||
claim_then_age(&pool, Some(&ago(30))).await;
|
||||
|
||||
assert!(claim_batch(&pool, &config(10)).await.unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn an_undecodable_row_is_dead_lettered_not_left_processing() {
|
||||
let pool = pool().await;
|
||||
sqlx::query("INSERT INTO event_queue (event_type, payload) VALUES ('t', 'not json')")
|
||||
.execute(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
let q = SqliteEventQueue::create(pool.clone(), config(10));
|
||||
|
||||
let first = q.consume().next().await.unwrap();
|
||||
assert!(first.is_err());
|
||||
|
||||
let (status, error): (String, Option<String>) =
|
||||
sqlx::query_as("SELECT status, last_error FROM event_queue")
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(status, "dead_lettered");
|
||||
assert!(error.unwrap().contains("undecodable"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user