use crate::common;
use reliar_outbox::{AcquireRequest, OutboxStore, WorkerId};
use reliar_store_postgres::{MigrateOptions, PostgresOutboxStore, migrate};
use sqlx::migrate::Migrator;
use sqlx::postgres::PgConnection;
use sqlx::{Connection, Executor};
static ALL_MIGRATIONS: Migrator = sqlx::migrate!("./migrations");
async fn apply_only_migration_0001(pool: &sqlx::PgPool) {
let mut conn = PgConnection::connect_with(&pool.connect_options())
.await
.expect("dedicated connection for the partial migration");
conn.execute("SET search_path = reliar, public")
.await
.expect("set search_path for the unqualified 0001 migration");
let mut only_0001 = Migrator {
migrations: std::borrow::Cow::Owned(ALL_MIGRATIONS.migrations[..1].to_vec()),
ignore_missing: ALL_MIGRATIONS.ignore_missing,
locking: ALL_MIGRATIONS.locking,
no_tx: ALL_MIGRATIONS.no_tx,
table_name: ALL_MIGRATIONS.table_name.clone(),
create_schemas: ALL_MIGRATIONS.create_schemas.clone(),
};
only_0001.create_schema("reliar".to_owned());
only_0001.dangerous_set_table_name("reliar._migrations");
only_0001.set_locking(false);
only_0001
.run(&mut conn)
.await
.expect("apply migration 0001 alone");
conn.close().await.expect("close the dedicated connection");
}
async fn migrate_upgrades_a_populated_0001_only_database_and_claim_still_works() {
let pool = common::fresh_unmigrated_db().await;
apply_only_migration_0001(&pool).await;
sqlx::query(
"INSERT INTO reliar.outbox (id, message_type, message_version, conversation_id, content_type, \
payload, available_at, created_at) \
SELECT uuidv7(), 'orders.created', 1, uuidv7(), 'application/json', '{}'::bytea, \
now(), now() \
FROM generate_series(1, 900)",
)
.execute(&pool)
.await
.expect("seed 900 pending rows");
sqlx::query(
"INSERT INTO reliar.outbox (id, message_type, message_version, conversation_id, content_type, \
payload, available_at, created_at, locked_by, locked_until) \
SELECT uuidv7(), 'orders.created', 1, uuidv7(), 'application/json', '{}'::bytea, \
now() + interval '30 seconds', now(), 'legacy-worker', now() + interval '30 seconds' \
FROM generate_series(1, 50)",
)
.execute(&pool)
.await
.expect("seed 50 leased rows");
sqlx::query(
"INSERT INTO reliar.outbox (id, message_type, message_version, conversation_id, content_type, \
payload, available_at, created_at, dead_at, dead_reason) \
SELECT uuidv7(), 'orders.created', 1, uuidv7(), 'application/json', '{}'::bytea, \
now() - interval '1 day', now() - interval '1 day', now() - interval '12 hours', \
'permanent_error' \
FROM generate_series(1, 50)",
)
.execute(&pool)
.await
.expect("seed 50 dead rows");
migrate(&pool, MigrateOptions::default())
.await
.expect("migrate() upgrades a populated 0001-only database to 0002/0003");
let applied_count: i64 = sqlx::query_scalar("SELECT count(*) FROM reliar._migrations")
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(
usize::try_from(applied_count).expect("applied_count is never negative"),
ALL_MIGRATIONS.migrations.len(),
"every migration after 0001 must now be recorded too"
);
let claimable_valid: Option<bool> = sqlx::query_scalar(
"SELECT indisvalid FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid \
WHERE c.relname = 'ix_outbox_claimable'",
)
.fetch_optional(&pool)
.await
.unwrap();
assert_eq!(
claimable_valid,
Some(true),
"ix_outbox_claimable must exist and be valid after upgrading a populated database"
);
let pending_exists: bool =
sqlx::query_scalar("SELECT to_regclass('reliar.ix_outbox_pending') IS NOT NULL")
.fetch_one(&pool)
.await
.unwrap();
assert!(
!pending_exists,
"ix_outbox_pending must be dropped once ix_outbox_claimable is confirmed valid"
);
let store_pool = sqlx::PgPool::connect_with(
(*pool.connect_options())
.clone()
.options([("search_path", "reliar,public")]),
)
.await
.expect("reconnect with search_path set");
let store = PostgresOutboxStore::new(store_pool);
let batch = store
.acquire(AcquireRequest::new(WorkerId::generate()).batch_size(50))
.await
.expect("claim still works after the upgrade");
assert_eq!(
batch.records.len(),
50,
"acquire must return exactly batch_size claimable rows after the upgrade \
(weaker than !is_empty(): this also catches an index the claim can no longer use)"
);
}
async fn migrate_refuses_0003_when_claimable_index_is_missing_and_recovers() {
let pool = common::fresh_db().await;
sqlx::query("DROP INDEX CONCURRENTLY ix_outbox_claimable")
.execute(&pool)
.await
.expect("drop ix_outbox_claimable to simulate an interrupted 0002");
sqlx::query("DELETE FROM reliar._migrations WHERE version = 3")
.execute(&pool)
.await
.expect("roll back 0003's bookkeeping row");
let err = migrate(&pool, MigrateOptions::default())
.await
.expect_err("0003 must refuse to run without a valid ix_outbox_claimable");
assert!(
err.to_string().contains("ix_outbox_claimable"),
"the error must name the missing index, got: {err}"
);
sqlx::query(
"CREATE INDEX CONCURRENTLY ix_outbox_claimable ON outbox (available_at, id) \
INCLUDE (expires_at) \
WHERE published_at IS NULL AND dead_at IS NULL",
)
.execute(&pool)
.await
.expect("rebuild ix_outbox_claimable");
migrate(&pool, MigrateOptions::default())
.await
.expect("migrate() recovers once ix_outbox_claimable is valid again");
let pending_exists: bool =
sqlx::query_scalar("SELECT to_regclass('reliar.ix_outbox_pending') IS NOT NULL")
.fetch_one(&pool)
.await
.unwrap();
assert!(!pending_exists, "recovery must still finish 0003");
}
async fn migrate_refuses_0003_when_claimable_index_is_invalid_and_recovers() {
let pool = common::fresh_db().await;
sqlx::query(
"UPDATE pg_index SET indisvalid = false \
WHERE indexrelid = 'ix_outbox_claimable'::regclass",
)
.execute(&pool)
.await
.expect(
"mark ix_outbox_claimable invalid, simulating a CONCURRENTLY build that failed partway",
);
sqlx::query("DELETE FROM reliar._migrations WHERE version = 3")
.execute(&pool)
.await
.expect("roll back 0003's bookkeeping row");
let err = migrate(&pool, MigrateOptions::default())
.await
.expect_err("0003 must refuse to run over an invalid ix_outbox_claimable");
assert!(
err.to_string().contains("ix_outbox_claimable"),
"the error must name the invalid index, got: {err}"
);
sqlx::query("DROP INDEX CONCURRENTLY ix_outbox_claimable")
.execute(&pool)
.await
.expect("drop the invalid index");
sqlx::query(
"CREATE INDEX CONCURRENTLY ix_outbox_claimable ON outbox (available_at, id) \
INCLUDE (expires_at) \
WHERE published_at IS NULL AND dead_at IS NULL",
)
.execute(&pool)
.await
.expect("rebuild ix_outbox_claimable");
migrate(&pool, MigrateOptions::default())
.await
.expect("migrate() recovers once ix_outbox_claimable is valid again");
let pending_exists: bool =
sqlx::query_scalar("SELECT to_regclass('reliar.ix_outbox_pending') IS NOT NULL")
.fetch_one(&pool)
.await
.unwrap();
assert!(!pending_exists, "recovery must still finish 0003");
}
async fn sequence_column_exists(pool: &sqlx::PgPool) -> bool {
sqlx::query_scalar(
"SELECT EXISTS (SELECT 1 FROM information_schema.columns \
WHERE table_schema = 'reliar' AND table_name = 'outbox' AND column_name = 'sequence')",
)
.fetch_one(pool)
.await
.unwrap()
}
async fn migrate_refuses_0014_when_a_transient_index_is_missing_and_recovers() {
let pool = common::fresh_unmigrated_db().await;
common::apply_migration_prefix(&pool, 13).await;
sqlx::query("DROP INDEX CONCURRENTLY reliar.ix_outbox_claimable_id")
.execute(&pool)
.await
.expect("drop ix_outbox_claimable_id to simulate it never having been (re)built");
let err = migrate(&pool, MigrateOptions::default())
.await
.expect_err("0014 must refuse to run without a valid ix_outbox_claimable_id");
assert!(
err.to_string().contains("ix_outbox_claimable_id")
&& err.to_string().contains("ix_outbox_ordering_key_id"),
"the error must name both transient indexes, got: {err}"
);
assert!(
sequence_column_exists(&pool).await,
"0014 must refuse before dropping the sequence column, not after"
);
sqlx::query(
"CREATE INDEX CONCURRENTLY ix_outbox_claimable_id ON reliar.outbox (available_at, id) \
INCLUDE (locked_until, expires_at) \
WHERE published_at IS NULL AND dead_at IS NULL",
)
.execute(&pool)
.await
.expect("rebuild ix_outbox_claimable_id");
migrate(&pool, MigrateOptions::default())
.await
.expect("migrate() recovers once both transient indexes are valid again");
assert!(
!sequence_column_exists(&pool).await,
"recovery must still finish 0014 and drop the sequence column"
);
let claimable_valid: Option<bool> = sqlx::query_scalar(
"SELECT indisvalid FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid \
WHERE c.relname = 'ix_outbox_claimable'",
)
.fetch_optional(&pool)
.await
.unwrap();
assert_eq!(
claimable_valid,
Some(true),
"ix_outbox_claimable must exist under its permanent name and be valid"
);
let ordering_key_valid: Option<bool> = sqlx::query_scalar(
"SELECT indisvalid FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid \
WHERE c.relname = 'ix_outbox_ordering_key'",
)
.fetch_optional(&pool)
.await
.unwrap();
assert_eq!(
ordering_key_valid,
Some(true),
"ix_outbox_ordering_key must exist under its permanent name and be valid"
);
}
async fn migrate_refuses_0014_when_a_transient_index_is_invalid_and_recovers() {
let pool = common::fresh_unmigrated_db().await;
common::apply_migration_prefix(&pool, 13).await;
sqlx::query(
"UPDATE pg_index SET indisvalid = false \
WHERE indexrelid = 'reliar.ix_outbox_ordering_key_id'::regclass",
)
.execute(&pool)
.await
.expect(
"mark ix_outbox_ordering_key_id invalid, simulating a CONCURRENTLY build that failed \
partway",
);
let err = migrate(&pool, MigrateOptions::default())
.await
.expect_err("0014 must refuse to run over an invalid ix_outbox_ordering_key_id");
assert!(
err.to_string().contains("ix_outbox_claimable_id")
&& err.to_string().contains("ix_outbox_ordering_key_id"),
"the error must name both transient indexes, got: {err}"
);
assert!(
sequence_column_exists(&pool).await,
"0014 must refuse before dropping the sequence column, not after"
);
sqlx::query("DROP INDEX CONCURRENTLY reliar.ix_outbox_ordering_key_id")
.execute(&pool)
.await
.expect("drop the invalid index");
sqlx::query(
"CREATE INDEX CONCURRENTLY ix_outbox_ordering_key_id ON reliar.outbox (ordering_key, id) \
WHERE ordering_key IS NOT NULL AND published_at IS NULL AND dead_at IS NULL",
)
.execute(&pool)
.await
.expect("rebuild ix_outbox_ordering_key_id");
migrate(&pool, MigrateOptions::default())
.await
.expect("migrate() recovers once both transient indexes are valid again");
assert!(
!sequence_column_exists(&pool).await,
"recovery must still finish 0014 and drop the sequence column"
);
let ordering_key_valid: Option<bool> = sqlx::query_scalar(
"SELECT indisvalid FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid \
WHERE c.relname = 'ix_outbox_ordering_key'",
)
.fetch_optional(&pool)
.await
.unwrap();
assert_eq!(
ordering_key_valid,
Some(true),
"ix_outbox_ordering_key must exist under its permanent name and be valid"
);
}
async fn migrate_refuses_0016_when_the_transient_index_is_missing_and_recovers() {
let pool = common::fresh_unmigrated_db().await;
common::apply_migration_prefix(&pool, 15).await;
sqlx::query("DROP INDEX CONCURRENTLY reliar.ix_outbox_claimable_v2")
.execute(&pool)
.await
.expect("drop ix_outbox_claimable_v2 to simulate it never having been (re)built");
let err = migrate(&pool, MigrateOptions::default())
.await
.expect_err("0016 must refuse to run without a valid ix_outbox_claimable_v2");
assert!(
err.to_string().contains("ix_outbox_claimable_v2"),
"the error must name the missing transient index, got: {err}"
);
assert!(
locked_until_column_exists(&pool).await,
"0016 must refuse before dropping the locked_until column, not after"
);
sqlx::query(
"CREATE INDEX CONCURRENTLY ix_outbox_claimable_v2 ON reliar.outbox (available_at, id) \
INCLUDE (expires_at) \
WHERE published_at IS NULL AND dead_at IS NULL",
)
.execute(&pool)
.await
.expect("rebuild ix_outbox_claimable_v2");
migrate(&pool, MigrateOptions::default())
.await
.expect("migrate() recovers once ix_outbox_claimable_v2 is valid again");
assert!(
!locked_until_column_exists(&pool).await,
"recovery must still finish 0016 and drop the locked_until column"
);
let claimable_valid: Option<bool> = sqlx::query_scalar(
"SELECT indisvalid FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid \
WHERE c.relname = 'ix_outbox_claimable'",
)
.fetch_optional(&pool)
.await
.unwrap();
assert_eq!(
claimable_valid,
Some(true),
"ix_outbox_claimable must exist under its permanent name and be valid"
);
}
async fn locked_until_column_exists(pool: &sqlx::PgPool) -> bool {
sqlx::query_scalar(
"SELECT EXISTS (SELECT 1 FROM information_schema.columns \
WHERE table_schema = 'reliar' AND table_name = 'outbox' \
AND column_name = 'locked_until')",
)
.fetch_one(pool)
.await
.unwrap()
}
async fn ix_outbox_claimable_survives_dropping_locked_until() {
let pool = common::fresh_db().await;
let (claimable_valid, claimable_def): (Option<bool>, Option<String>) = {
let valid: Option<bool> = sqlx::query_scalar(
"SELECT indisvalid FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid \
WHERE c.relname = 'ix_outbox_claimable'",
)
.fetch_optional(&pool)
.await
.unwrap();
let def: Option<String> =
sqlx::query_scalar("SELECT pg_get_indexdef('ix_outbox_claimable'::regclass)")
.fetch_optional(&pool)
.await
.unwrap();
(valid, def)
};
assert_eq!(
claimable_valid,
Some(true),
"ix_outbox_claimable must exist under its permanent name and be valid"
);
let claimable_def = claimable_def.expect("ix_outbox_claimable must exist");
assert!(
claimable_def.contains("INCLUDE (expires_at)"),
"expected INCLUDE (expires_at) only, got: {claimable_def}"
);
assert!(
!claimable_def.contains("locked_until"),
"ix_outbox_claimable must no longer INCLUDE locked_until, got: {claimable_def}"
);
assert!(
claimable_def.contains("published_at IS NULL") && claimable_def.contains("dead_at IS NULL"),
"the predicate must be unchanged, got: {claimable_def}"
);
}
async fn migrate_0003_guard_is_schema_scoped_and_ignores_another_schemas_valid_index() {
let pool = common::fresh_unmigrated_db().await;
migrate(&pool, MigrateOptions::default().schema("tenant_a"))
.await
.expect("tenant_a migrates cleanly");
migrate(&pool, MigrateOptions::default().schema("tenant_b"))
.await
.expect("tenant_b migrates cleanly");
sqlx::query(
"UPDATE pg_index SET indisvalid = false \
WHERE indexrelid = 'tenant_b.ix_outbox_claimable'::regclass",
)
.execute(&pool)
.await
.expect("invalidate tenant_b's claimable index only");
sqlx::query("DELETE FROM tenant_b._migrations WHERE version = 3")
.execute(&pool)
.await
.expect("roll back tenant_b's 0003 bookkeeping row");
let err = migrate(&pool, MigrateOptions::default().schema("tenant_b"))
.await
.expect_err("tenant_b's 0003 must refuse despite tenant_a's valid index of the same name");
assert!(
err.to_string().contains("ix_outbox_claimable"),
"the error must name the missing/invalid index, got: {err}"
);
let tenant_a_valid: Option<bool> = sqlx::query_scalar(
"SELECT indisvalid FROM pg_index i \
JOIN pg_class c ON c.oid = i.indexrelid \
JOIN pg_namespace n ON n.oid = c.relnamespace \
WHERE n.nspname = 'tenant_a' AND c.relname = 'ix_outbox_claimable'",
)
.fetch_optional(&pool)
.await
.unwrap();
assert_eq!(
tenant_a_valid,
Some(true),
"tenant_a's own index must be left untouched throughout"
);
}
async fn migrate_creates_schema_and_bookkeeping_table() {
let pool = common::fresh_unmigrated_db().await;
migrate(&pool, MigrateOptions::default())
.await
.expect("first migrate succeeds");
let outbox_exists: bool = sqlx::query_scalar("SELECT to_regclass('reliar.outbox') IS NOT NULL")
.fetch_one(&pool)
.await
.unwrap();
assert!(outbox_exists, "migrate() must create reliar.outbox");
let bookkeeping_exists: bool =
sqlx::query_scalar("SELECT to_regclass('reliar._migrations') IS NOT NULL")
.fetch_one(&pool)
.await
.unwrap();
assert!(
bookkeeping_exists,
"migrate() must record versions in reliar._migrations, never _sqlx_migrations"
);
let host_migrations_exists: bool =
sqlx::query_scalar("SELECT to_regclass('public._sqlx_migrations') IS NOT NULL")
.fetch_one(&pool)
.await
.unwrap();
assert!(
!host_migrations_exists,
"migrate() must never write to the shared, one-per-database _sqlx_migrations table"
);
let sequence_column_exists: bool = sqlx::query_scalar(
"SELECT EXISTS (SELECT 1 FROM information_schema.columns \
WHERE table_schema = 'reliar' AND table_name = 'outbox' AND column_name = 'sequence')",
)
.fetch_one(&pool)
.await
.unwrap();
assert!(
!sequence_column_exists,
"outbox must have no sequence column after migrate()"
);
}
async fn migrate_is_idempotent() {
let pool = common::fresh_unmigrated_db().await;
let options = MigrateOptions::default();
migrate(&pool, options).await.expect("first call");
migrate(&pool, options)
.await
.expect("second call observes Ok(())");
}
async fn migrate_serializes_concurrent_callers() {
let pool = common::fresh_unmigrated_db().await;
let options = MigrateOptions::default();
let (first, second) = tokio::join!(migrate(&pool, options), migrate(&pool, options));
first.expect("first concurrent caller succeeds");
second.expect("second concurrent caller succeeds, serialized by Reliar's own advisory lock");
let outbox_exists: bool = sqlx::query_scalar("SELECT to_regclass('reliar.outbox') IS NOT NULL")
.fetch_one(&pool)
.await
.unwrap();
assert!(outbox_exists);
let applied_count: i64 = sqlx::query_scalar("SELECT count(*) FROM reliar._migrations")
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(
usize::try_from(applied_count).expect("applied_count is never negative"),
ALL_MIGRATIONS.migrations.len(),
"concurrent callers must not double-apply or partially apply the migration set"
);
let claimable_valid: Option<bool> = sqlx::query_scalar(
"SELECT indisvalid FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid \
WHERE c.relname = 'ix_outbox_claimable'",
)
.fetch_optional(&pool)
.await
.unwrap();
assert_eq!(
claimable_valid,
Some(true),
"ix_outbox_claimable must exist and be valid after two concurrent migrate() calls"
);
}
async fn four_concurrent_callers_on_the_same_schema_all_succeed() {
let pool = common::fresh_unmigrated_db().await;
let options = MigrateOptions::default();
let (a, b, c, d) = tokio::join!(
migrate(&pool, options),
migrate(&pool, options),
migrate(&pool, options),
migrate(&pool, options),
);
a.expect("caller a");
b.expect("caller b");
c.expect("caller c");
d.expect("caller d");
let applied_count: i64 = sqlx::query_scalar("SELECT count(*) FROM reliar._migrations")
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(
usize::try_from(applied_count).expect("applied_count is never negative"),
ALL_MIGRATIONS.migrations.len()
);
}
async fn two_concurrent_callers_into_different_schemas_both_succeed() {
let pool = common::fresh_unmigrated_db().await;
let (a, b) = tokio::join!(
migrate(&pool, MigrateOptions::default().schema("tenant_a")),
migrate(&pool, MigrateOptions::default().schema("tenant_b")),
);
a.expect("tenant_a's migrate() succeeds");
b.expect("tenant_b's migrate() succeeds");
for schema in ["tenant_a", "tenant_b"] {
let outbox_exists: bool = sqlx::query_scalar(
"SELECT EXISTS (SELECT 1 FROM information_schema.tables \
WHERE table_schema = $1 AND table_name = 'outbox')",
)
.bind(schema)
.fetch_one(&pool)
.await
.unwrap();
assert!(outbox_exists, "{schema} must have its own outbox table");
let claimable_valid: Option<bool> = sqlx::query_scalar(
"SELECT indisvalid FROM pg_index i \
JOIN pg_class c ON c.oid = i.indexrelid \
JOIN pg_namespace n ON n.oid = c.relnamespace \
WHERE n.nspname = $1 AND c.relname = 'ix_outbox_claimable'",
)
.bind(schema)
.fetch_optional(&pool)
.await
.unwrap();
assert_eq!(
claimable_valid,
Some(true),
"{schema}'s ix_outbox_claimable must exist and be valid"
);
}
}
async fn migrate_does_not_leak_search_path_into_the_callers_pool_connections() {
let pool = common::fresh_unmigrated_db().await;
let original: String = sqlx::query_scalar("SELECT current_setting('search_path')")
.fetch_one(&pool)
.await
.unwrap();
migrate(&pool, MigrateOptions::default().schema("tenant_a"))
.await
.expect("migrate into a non-default schema");
let mut held = Vec::new();
for _ in 0..5 {
held.push(pool.acquire().await.unwrap());
}
for mut conn in held {
let observed: String = sqlx::query_scalar("SELECT current_setting('search_path')")
.fetch_one(&mut *conn)
.await
.unwrap();
assert_eq!(
observed, original,
"a pool connection's search_path must be untouched by migrate()"
);
}
}
async fn unmigrated_pool_has_no_outbox_table() {
let pool = common::fresh_unmigrated_db().await;
let outbox_exists: bool = sqlx::query_scalar("SELECT to_regclass('reliar.outbox') IS NOT NULL")
.fetch_one(&pool)
.await
.unwrap();
assert!(
!outbox_exists,
"a pool nobody has called migrate() on must have no outbox table"
);
}
#[allow(
clippy::too_many_lines,
reason = "one flat Trial-registration list, one line per scenario — splitting it would scatter \
the file's own table of contents across several functions with no reuse to justify it"
)]
pub(crate) fn trials(rt: &'static tokio::runtime::Runtime) -> Vec<libtest_mimic::Trial> {
vec![
libtest_mimic::Trial::test(
"migrate::migrate_creates_schema_and_bookkeeping_table",
move || {
rt.block_on(migrate_creates_schema_and_bookkeeping_table());
Ok(())
},
),
libtest_mimic::Trial::test("migrate::migrate_is_idempotent", move || {
rt.block_on(migrate_is_idempotent());
Ok(())
}),
libtest_mimic::Trial::test(
"migrate::migrate_serializes_concurrent_callers",
move || {
rt.block_on(migrate_serializes_concurrent_callers());
Ok(())
},
),
libtest_mimic::Trial::test(
"migrate::four_concurrent_callers_on_the_same_schema_all_succeed",
move || {
rt.block_on(four_concurrent_callers_on_the_same_schema_all_succeed());
Ok(())
},
),
libtest_mimic::Trial::test(
"migrate::two_concurrent_callers_into_different_schemas_both_succeed",
move || {
rt.block_on(two_concurrent_callers_into_different_schemas_both_succeed());
Ok(())
},
),
libtest_mimic::Trial::test("migrate::unmigrated_pool_has_no_outbox_table", move || {
rt.block_on(unmigrated_pool_has_no_outbox_table());
Ok(())
}),
libtest_mimic::Trial::test(
"migrate::migrate_does_not_leak_search_path_into_the_callers_pool_connections",
move || {
rt.block_on(migrate_does_not_leak_search_path_into_the_callers_pool_connections());
Ok(())
},
),
libtest_mimic::Trial::test(
"migrate::migrate_upgrades_a_populated_0001_only_database_and_claim_still_works",
move || {
rt.block_on(
migrate_upgrades_a_populated_0001_only_database_and_claim_still_works(),
);
Ok(())
},
),
libtest_mimic::Trial::test(
"migrate::migrate_refuses_0003_when_claimable_index_is_missing_and_recovers",
move || {
rt.block_on(migrate_refuses_0003_when_claimable_index_is_missing_and_recovers());
Ok(())
},
),
libtest_mimic::Trial::test(
"migrate::migrate_refuses_0003_when_claimable_index_is_invalid_and_recovers",
move || {
rt.block_on(migrate_refuses_0003_when_claimable_index_is_invalid_and_recovers());
Ok(())
},
),
libtest_mimic::Trial::test(
"migrate::migrate_0003_guard_is_schema_scoped_and_ignores_another_schemas_valid_index",
move || {
rt.block_on(
migrate_0003_guard_is_schema_scoped_and_ignores_another_schemas_valid_index(),
);
Ok(())
},
),
libtest_mimic::Trial::test(
"migrate::migrate_refuses_0014_when_a_transient_index_is_missing_and_recovers",
move || {
rt.block_on(migrate_refuses_0014_when_a_transient_index_is_missing_and_recovers());
Ok(())
},
),
libtest_mimic::Trial::test(
"migrate::migrate_refuses_0014_when_a_transient_index_is_invalid_and_recovers",
move || {
rt.block_on(migrate_refuses_0014_when_a_transient_index_is_invalid_and_recovers());
Ok(())
},
),
libtest_mimic::Trial::test(
"migrate::migrate_refuses_0016_when_the_transient_index_is_missing_and_recovers",
move || {
rt.block_on(
migrate_refuses_0016_when_the_transient_index_is_missing_and_recovers(),
);
Ok(())
},
),
libtest_mimic::Trial::test(
"migrate::ix_outbox_claimable_survives_dropping_locked_until",
move || {
rt.block_on(ix_outbox_claimable_survives_dropping_locked_until());
Ok(())
},
),
]
}