use sqlx::{Executor as _, Row as _};
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
#[must_use]
pub fn base_url() -> Option<String> {
std::env::var("FZ_TEST_POSTGRES_URL")
.ok()
.map(|url| url.trim().to_owned())
.filter(|url| !url.is_empty())
}
#[must_use]
pub fn skip_reason() -> String {
"FZ_TEST_POSTGRES_URL is not set — start a local postgres:16 \
(docker run --rm -e POSTGRES_PASSWORD=postgres -p 5433:5432 postgres:16) \
and set FZ_TEST_POSTGRES_URL=postgres://postgres:postgres@127.0.0.1:5433/postgres"
.to_owned()
}
fn url_with_database(base: &str, database: &str) -> String {
let (path, query) = match base.split_once('?') {
Some((path, query)) => (path, Some(query)),
None => (base, None),
};
let (prefix, _) = path
.rsplit_once('/')
.expect("FZ_TEST_POSTGRES_URL must name a database");
let mut url = format!("{prefix}/{database}");
if let Some(query) = query {
url.push('?');
url.push_str(query);
}
url
}
pub struct TempDb {
pub url: String,
admin: sqlx::PgPool,
database: String,
}
impl TempDb {
pub async fn create(base: &str, tag: &str) -> Option<Self> {
let admin = sqlx::PgPool::connect(base)
.await
.expect("FZ_TEST_POSTGRES_URL server accepts connections");
let database = format!(
"fz_{tag}_{}_{}",
std::process::id(),
COUNTER.fetch_add(1, Ordering::Relaxed)
);
admin
.execute(format!(r#"CREATE DATABASE "{database}""#).as_str())
.await
.expect("can create a throwaway test database");
Some(Self {
url: url_with_database(base, &database),
admin,
database,
})
}
pub async fn assert_postgres_16(&self) {
let version: String = sqlx::query("SELECT version()")
.fetch_one(&self.admin)
.await
.expect("server reports its version")
.get(0);
assert!(
version.starts_with("PostgreSQL 16"),
"contract is about Postgres 16, server is: {version}"
);
}
pub async fn finish(self) {
let Self {
admin,
database,
url: _,
} = self;
admin.close().await;
if let Ok(dropper) =
sqlx::PgPool::connect(&base_url().expect("create was called with a non-empty base url"))
.await
{
dropper
.execute(
format!(r#"DROP DATABASE IF EXISTS "{database}" WITH (FORCE)"#).as_str(),
)
.await
.expect("drop throwaway test database");
dropper.close().await;
}
}
}