cratefield_adapter_postgres/
testing.rs1use sqlx::{Executor as _, Row as _};
11use std::sync::atomic::{AtomicU64, Ordering};
12
13static COUNTER: AtomicU64 = AtomicU64::new(0);
14
15#[must_use]
18pub fn base_url() -> Option<String> {
19 std::env::var("FZ_TEST_POSTGRES_URL")
20 .ok()
21 .map(|url| url.trim().to_owned())
22 .filter(|url| !url.is_empty())
23}
24
25#[must_use]
27pub fn skip_reason() -> String {
28 "FZ_TEST_POSTGRES_URL is not set — start a local postgres:16 \
29 (docker run --rm -e POSTGRES_PASSWORD=postgres -p 5433:5432 postgres:16) \
30 and set FZ_TEST_POSTGRES_URL=postgres://postgres:postgres@127.0.0.1:5433/postgres"
31 .to_owned()
32}
33
34fn url_with_database(base: &str, database: &str) -> String {
35 let (path, query) = match base.split_once('?') {
36 Some((path, query)) => (path, Some(query)),
37 None => (base, None),
38 };
39 let (prefix, _) = path
40 .rsplit_once('/')
41 .expect("FZ_TEST_POSTGRES_URL must name a database");
42 let mut url = format!("{prefix}/{database}");
43 if let Some(query) = query {
44 url.push('?');
45 url.push_str(query);
46 }
47 url
48}
49
50pub struct TempDb {
53 pub url: String,
55 admin: sqlx::PgPool,
56 database: String,
57}
58
59impl TempDb {
60 pub async fn create(base: &str, tag: &str) -> Option<Self> {
71 let admin = sqlx::PgPool::connect(base)
72 .await
73 .expect("FZ_TEST_POSTGRES_URL server accepts connections");
74 let database = format!(
75 "fz_{tag}_{}_{}",
76 std::process::id(),
77 COUNTER.fetch_add(1, Ordering::Relaxed)
78 );
79 admin
80 .execute(format!(r#"CREATE DATABASE "{database}""#).as_str())
81 .await
82 .expect("can create a throwaway test database");
83 Some(Self {
84 url: url_with_database(base, &database),
85 admin,
86 database,
87 })
88 }
89
90 pub async fn assert_postgres_16(&self) {
96 let version: String = sqlx::query("SELECT version()")
97 .fetch_one(&self.admin)
98 .await
99 .expect("server reports its version")
100 .get(0);
101 assert!(
102 version.starts_with("PostgreSQL 16"),
103 "contract is about Postgres 16, server is: {version}"
104 );
105 }
106
107 pub async fn finish(self) {
113 let Self {
114 admin,
115 database,
116 url: _,
117 } = self;
118 admin.close().await;
119 if let Ok(dropper) =
122 sqlx::PgPool::connect(&base_url().expect("create was called with a non-empty base url"))
123 .await
124 {
125 dropper
126 .execute(
127 format!(r#"DROP DATABASE IF EXISTS "{database}" WITH (FORCE)"#).as_str(),
130 )
131 .await
132 .expect("drop throwaway test database");
133 dropper.close().await;
134 }
135 }
136}