reliar-store-postgres 0.6.0

PostgreSQL provider for the Reliar transactional outbox: migrations, migrate(), enqueue and the SKIP LOCKED claim.
Documentation
//! ADR 0041 (human decision #47) — the PostgreSQL 18 floor is *enforced*, not merely documented.
//! Boots this crate's only sub-18 container — pinned to `postgres:17-alpine`, a **local**, never
//! stored in a `static`, so it is dropped (and removed) at the end of this scenario exactly like
//! `outbox_pgdog`'s pooler containers, leaving the shared PG 18 container `tests/postgres/main.rs`
//! starts untouched (ADR 0021's one-container-per-binary rule still holds per binary) — and proves
//! both entry points refuse it: [`migrate`] fails with [`MigrateError::UnsupportedServerVersion`]
//! **before** creating the schema or anything in it, and [`PostgresOutboxStore::new`] fails with
//! the store's own [`PostgresStoreError::UnsupportedServerVersion`]. Both name
//! [`MIN_SERVER_VERSION_NUM`] and a `detected` value below it; neither `Display` names a
//! connection string, host, or credential.

use reliar_core::{Classify, FailureKind};
use reliar_store_postgres::{
    MIN_SERVER_VERSION_NUM, MigrateError, MigrateOptions, PostgresOutboxStore, PostgresStoreError,
    migrate,
};
use sqlx::PgPool;
use testcontainers::ImageExt;
use testcontainers::runners::AsyncRunner;
use testcontainers_modules::postgres::Postgres;

async fn migrate_and_connect_refuse_a_server_below_the_floor() {
    let pg17 = Postgres::default()
        .with_tag("17-alpine") // ci: below-floor-by-design (ADR 0041) — the drift guard exempts this line
        .with_container_name(format!("reliar-pg17-{}", uuid::Uuid::now_v7().simple()))
        .with_label("reliar.test", "true")
        .start()
        .await
        .expect("start postgres 17");
    let port = pg17
        .get_host_port_ipv4(5432)
        .await
        .expect("mapped port for postgres 17");
    let url = format!("postgres://postgres:postgres@127.0.0.1:{port}/postgres");
    let pool = PgPool::connect(&url)
        .await
        .expect("connect to the postgres 17 container");

    // --- migrate() refuses before creating anything. ---
    let err = migrate(&pool, MigrateOptions::default())
        .await
        .expect_err("migrate() must refuse a server below MIN_SERVER_VERSION_NUM");
    let text = err.to_string();

    assert!(
        !text.contains("postgres://") && !text.contains("127.0.0.1"),
        "MigrateError::Display must carry no DSN/host, got: {text}"
    );
    let MigrateError::UnsupportedServerVersion { required, detected } = err else {
        panic!("expected MigrateError::UnsupportedServerVersion, got {err:?}");
    };
    assert_eq!(required, MIN_SERVER_VERSION_NUM);
    assert!(
        (170_000..180_000).contains(&detected),
        "expected a PostgreSQL 17.x server_version_num below the floor, got {detected}"
    );
    assert!(
        text.contains("180000") && text.contains(&detected.to_string()),
        "Display must name both the floor and the detected version, got: {text}"
    );

    let schema_exists: bool = sqlx::query_scalar(
        "SELECT EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = 'reliar')",
    )
    .fetch_one(&pool)
    .await
    .expect("query information_schema.schemata");
    assert!(
        !schema_exists,
        "a refused migrate() must leave the database exactly as it found it — no reliar schema"
    );

    // --- Construction refuses the same way, independent of migrate(). ---
    let err = PostgresOutboxStore::new(pool.clone())
        .await
        .expect_err("construction must also refuse a server below the floor");
    let text = err.to_string();
    assert!(
        !text.contains("postgres://") && !text.contains("127.0.0.1"),
        "PostgresStoreError::Display must carry no DSN/host, got: {text}"
    );

    assert_eq!(
        err.kind(),
        FailureKind::Permanent,
        "a server below the floor never becomes supported on retry"
    );

    match err {
        PostgresStoreError::UnsupportedServerVersion { required, detected } => {
            assert_eq!(required, MIN_SERVER_VERSION_NUM);
            assert!(
                (170_000..180_000).contains(&detected),
                "expected a PostgreSQL 17.x server_version_num below the floor, got {detected}"
            );
            assert!(
                text.contains("180000") && text.contains(&detected.to_string()),
                "Display must name both the floor and the detected version, got: {text}"
            );
        }
        other => panic!("expected PostgresStoreError::UnsupportedServerVersion, got {other:?}"),
    }
}

pub(crate) fn trials(rt: &'static tokio::runtime::Runtime) -> Vec<libtest_mimic::Trial> {
    vec![libtest_mimic::Trial::test(
        "server_version_floor::migrate_and_connect_refuse_a_server_below_the_floor",
        move || {
            rt.block_on(migrate_and_connect_refuse_a_server_below_the_floor());
            Ok(())
        },
    )]
}