use crate::common;
use crate::common::OrderCreated;
use reliar_core::Envelope;
use reliar_core::Serializer as _;
use reliar_outbox::OutboxEnqueue;
use reliar_store_postgres::PostgresOutboxStore;
async fn count_business_rows(pool: &sqlx::PgPool) -> i64 {
sqlx::query_scalar("SELECT count(*) FROM widgets")
.fetch_one(pool)
.await
.unwrap()
}
async fn count_outbox_rows(pool: &sqlx::PgPool) -> i64 {
sqlx::query_scalar("SELECT count(*) FROM outbox")
.fetch_one(pool)
.await
.unwrap()
}
async fn create_widgets_table(pool: &sqlx::PgPool) {
sqlx::query("CREATE TABLE widgets (id bigserial PRIMARY KEY)")
.execute(pool)
.await
.unwrap();
}
async fn commit_makes_both_rows_visible() {
let pool = common::fresh_db().await;
create_widgets_table(&pool).await;
let store = PostgresOutboxStore::new(pool.clone()).await.unwrap();
let mut tx = pool.begin().await.unwrap();
sqlx::query("INSERT INTO widgets DEFAULT VALUES")
.execute(&mut *tx)
.await
.unwrap();
let envelope = Envelope::builder(OrderCreated { order_id: 1 }).build();
let envelope_id = envelope.id;
let expected_payload = reliar_core::JsonSerializer
.serialize(&envelope.body)
.unwrap();
store.enqueue_envelope(&mut tx, envelope).await.unwrap();
tx.commit().await.unwrap();
assert_eq!(count_business_rows(&pool).await, 1);
assert_eq!(count_outbox_rows(&pool).await, 1);
let content_type: String = sqlx::query_scalar("SELECT content_type FROM outbox WHERE id = $1")
.bind(envelope_id.as_uuid())
.fetch_one(&pool)
.await
.unwrap();
let payload: Vec<u8> = sqlx::query_scalar("SELECT payload FROM outbox WHERE id = $1")
.bind(envelope_id.as_uuid())
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(content_type, store.content_type().to_string());
assert_eq!(payload, expected_payload.as_ref());
}
async fn rollback_makes_neither_row_visible() {
let pool = common::fresh_db().await;
create_widgets_table(&pool).await;
let store = PostgresOutboxStore::new(pool.clone()).await.unwrap();
let mut tx = pool.begin().await.unwrap();
sqlx::query("INSERT INTO widgets DEFAULT VALUES")
.execute(&mut *tx)
.await
.unwrap();
let envelope = Envelope::builder(OrderCreated { order_id: 1 }).build();
store.enqueue_envelope(&mut tx, envelope).await.unwrap();
tx.rollback().await.unwrap();
assert_eq!(count_business_rows(&pool).await, 0);
assert_eq!(count_outbox_rows(&pool).await, 0);
}
async fn duplicate_message_id_aborts_the_transaction_and_discards_the_earlier_business_write() {
let pool = common::fresh_db().await;
create_widgets_table(&pool).await;
let store = PostgresOutboxStore::new(pool.clone()).await.unwrap();
let envelope = Envelope::builder(OrderCreated { order_id: 1 }).build();
let envelope_id = envelope.id;
let mut seed_tx = pool.begin().await.unwrap();
store
.enqueue_envelope(&mut seed_tx, envelope.clone())
.await
.unwrap();
seed_tx.commit().await.unwrap();
let mut tx = pool.begin().await.unwrap();
sqlx::query("INSERT INTO widgets DEFAULT VALUES")
.execute(&mut *tx)
.await
.unwrap();
let result = store.enqueue_envelope(&mut tx, envelope).await;
match result.expect_err("a reused MessageId must be rejected") {
reliar_store_postgres::EnqueueError::Duplicate { id } => {
assert_eq!(id, envelope_id);
}
other => panic!("expected EnqueueError::Duplicate, got {other:?}"),
}
let next_statement = sqlx::query("INSERT INTO widgets DEFAULT VALUES")
.execute(&mut *tx)
.await;
assert!(
next_statement.is_err(),
"a statement on an aborted transaction must fail"
);
tx.rollback().await.unwrap();
assert_eq!(
count_business_rows(&pool).await,
0,
"an earlier write in the aborted transaction must not be durable"
);
}
async fn enqueue_is_send_through_tokio_spawn() {
let pool = common::fresh_db().await;
let store = PostgresOutboxStore::new(pool.clone()).await.unwrap();
let envelope = Envelope::builder(OrderCreated { order_id: 7 }).build();
let envelope_id = envelope.id;
let mut tx = pool.begin().await.unwrap();
tokio::spawn(async move {
store.enqueue_envelope(&mut tx, envelope).await.unwrap();
tx.commit().await.unwrap();
})
.await
.unwrap();
let count: i64 = sqlx::query_scalar("SELECT count(*) FROM outbox WHERE id = $1")
.bind(envelope_id.as_uuid())
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(count, 1);
}
pub(crate) fn trials(rt: &'static tokio::runtime::Runtime) -> Vec<libtest_mimic::Trial> {
vec![
libtest_mimic::Trial::test(
"outbox_enqueue::commit_makes_both_rows_visible",
move || {
rt.block_on(commit_makes_both_rows_visible());
Ok(())
},
),
libtest_mimic::Trial::test(
"outbox_enqueue::rollback_makes_neither_row_visible",
move || {
rt.block_on(rollback_makes_neither_row_visible());
Ok(())
},
),
libtest_mimic::Trial::test(
"outbox_enqueue::duplicate_message_id_aborts_the_transaction_and_discards_the_earlier_business_write",
move || {
rt.block_on(
duplicate_message_id_aborts_the_transaction_and_discards_the_earlier_business_write(),
);
Ok(())
},
),
libtest_mimic::Trial::test(
"outbox_enqueue::enqueue_is_send_through_tokio_spawn",
move || {
rt.block_on(enqueue_is_send_through_tokio_spawn());
Ok(())
},
),
]
}