use std::time::Duration;
use ruststream::prelude::*;
use ruststream::testing::TestApp;
use ruststream_sqlx::{Inbox, InboxQueue, SqlxBroker};
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Pool};
use tokio::time::timeout;
use crate::live;
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Letter {
to: String,
}
fn letter(to: &str) -> Vec<u8> {
serde_json::to_vec(&Letter { to: to.to_owned() }).expect("json")
}
const SETTLED: Duration = Duration::from_secs(10);
const SHORT_ONLY: &str =
"ALTER TABLE plain_jobs_dead ADD CONSTRAINT short_payload CHECK (length(payload) < 20)";
fn letters() -> [Vec<u8>; 2] {
[
letter("ada"),
letter("someone.with.a.long.address@example.com"),
]
}
live::row_lock_stands! {
use crate::live::rows::row_lock::Plain;
#[derive(Debug, Inbox, FromRow)]
#[inbox(table = "plain_jobs_dead")]
struct PlainDead {
#[field(id)]
id: i64,
#[field(attempt, generated)]
attempt: i16,
#[field(payload)]
payload: Vec<u8>,
}
#[subscriber(InboxQueue::<Plain>::new("plain"))]
async fn unanswered(letters: &[Letter]) -> HandlerOutcome {
let _ = letters;
HandlerOutcome::retry()
}
#[subscriber(InboxQueue::<PlainDead>::new("plain.dead"))]
async fn review(_letter: &Letter) -> HandlerOutcome {
HandlerOutcome::ack()
}
fn mailer(pool: &Pool<Db>) -> RustStream {
let broker = SqlxBroker::new(pool.clone()).poll_interval(Duration::from_secs(5));
RustStream::new(AppInfo::new("mailer", "0.0.0")).with_broker(broker, |b| {
b.include(review);
b.include(unanswered.batch(nonzero!(3)))
.max_attempts(nonzero!(1u32))
.dead_letter("plain_jobs_dead");
})
}
#[tokio::test]
async fn in_process_a_move_the_batch_rolled_back_is_not_waited_for() {
let Some(db) = database().await else { return };
sqlx::query(SHORT_ONLY)
.execute(&db.pool)
.await
.expect("the dead-letter table takes short payloads alone");
db.plain(&letters()).await;
let tb = TestApp::start(mailer(&db.pool)).await.expect("the app starts");
timeout(SETTLED, tb.settle())
.await
.expect("the settle does not wait for a move the rollback undid")
.expect("the batch settles");
tb.broker::<SqlxBroker<Db>>()
.subscriber("plain.dead")
.assert_not_called();
assert_eq!(db.count("plain_jobs").await, 2, "both rows stay in their queue");
assert_eq!(db.count("plain_jobs_dead").await, 0, "no row reached the dead-letter table");
tb.shutdown().await.expect("the app stops");
db.finish().await;
}
#[tokio::test]
async fn in_process_the_moves_of_a_committed_batch_are_waited_for() {
let Some(db) = database().await else { return };
db.plain(&letters()).await;
let tb = TestApp::start(mailer(&db.pool)).await.expect("the app starts");
tb.settle().await.expect("the batch and the reviews settle");
tb.broker::<SqlxBroker<Db>>()
.subscriber("plain.dead")
.assert_called(2);
assert_eq!(db.count("plain_jobs").await, 0, "both rows moved");
tb.shutdown().await.expect("the app stops");
db.finish().await;
}
}