use crate::config::config;
use core::fmt;
use rand::RngExt;
use sqlx::{AssertSqlSafe, Connection, Executor, PgConnection, migrate::Migrator};
use std::{path::Path, thread};
use tokio::runtime::Runtime;
use tracing::warn;
#[derive(Clone, Default, Debug)]
pub(crate) struct MockDB(u32);
impl MockDB {
fn postgres() -> String {
format!("{}/postgres", config().db_server_url)
}
pub(crate) fn name(&self) -> String {
format!("mdb_{}", self.0)
}
fn url(&self) -> String {
format!("{}/{}", config().db_server_url, self.name())
}
pub(crate) fn new() -> Self {
let id = rand::rng().random_range(1_000..10_000);
let result = MockDB(id);
let db_name = result.name();
let db_url = result.url();
thread::spawn(move || {
let rt = Runtime::new().unwrap();
rt.block_on(async move {
let migrations = Path::new("./migrations");
let m = Migrator::new(migrations)
.await
.expect("Failed finding 'migrations' directory");
let mut conn = PgConnection::connect(&MockDB::postgres())
.await
.expect("Failed getting connection to create mock DB");
let sql = format!("CREATE DATABASE {db_name}");
let safe_sql = AssertSqlSafe(sql);
conn.execute(safe_sql)
.await
.expect("Failed creating mock DB");
conn = PgConnection::connect(&db_url)
.await
.expect("Failed getting connection to migrate mock DB");
m.run(&mut conn)
.await
.expect("Failed applying migrations to mock DB");
});
})
.join()
.expect("Failed setting up mock DB");
result
}
#[cfg(test)]
pub(crate) async fn pool(&self) -> sqlx::PgPool {
sqlx::postgres::PgPoolOptions::new()
.connect(&self.url())
.await
.expect("Failed creating mock DB connections pool")
}
}
impl Drop for MockDB {
fn drop(&mut self) {
let db_name = self.name();
thread::spawn(move || {
let rt = Runtime::new().unwrap();
rt.block_on(async move {
let mut conn = PgConnection::connect(&MockDB::postgres())
.await
.expect("Failed getting connection to drop mock DB");
let sql = format!(
r#"SELECT pg_terminate_backend(pid, 500)
FROM pg_catalog.pg_stat_activity
WHERE pid <> pg_backend_pid() AND datname = '{db_name}'"#
);
let safe_sql = AssertSqlSafe(sql);
if let Err(x) = sqlx::query(safe_sql).execute(&mut conn).await {
warn!(
"Failed terminating mock DB connections process. Ignore + continue: {}",
x
);
}
let sql = format!("DROP DATABASE IF EXISTS {db_name} WITH (FORCE)");
let safe_sql = AssertSqlSafe(sql);
conn.execute(safe_sql)
.await
.expect("Failed dropping mock DB. You need to delete it manually :(");
});
})
.join()
.expect("Failed tearing down mock DB");
}
}
impl fmt::Display for MockDB {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.url())
}
}