feat: wip test helper

This commit is contained in:
Max Hohlfeld 2024-12-09 16:06:31 +01:00
parent b27bdb6daa
commit 00b1b87da4
4 changed files with 146 additions and 12 deletions

27
Cargo.lock generated
View File

@ -757,10 +757,12 @@ dependencies = [
"chrono",
"dotenv",
"futures-util",
"idna 1.0.2",
"lettre",
"pico-args",
"quick-xml",
"rand",
"regex",
"serde",
"serde_json",
"sqlx",
@ -1714,23 +1716,24 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "1.0.3"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
dependencies = [
"idna_adapter",
"smallvec",
"utf8_iter",
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "idna_adapter"
version = "1.2.0"
name = "idna"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
checksum = "bd69211b9b519e98303c015e21a007e293db403b6c85b9b124e133d25e242cdd"
dependencies = [
"icu_normalizer",
"icu_properties",
"smallvec",
"utf8_iter",
]
[[package]]
@ -1846,7 +1849,7 @@ dependencies = [
"futures-util",
"hostname",
"httpdate",
"idna",
"idna 1.0.2",
"mime",
"native-tls",
"nom",
@ -3058,12 +3061,12 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.4"
version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
dependencies = [
"form_urlencoded",
"idna",
"idna 0.5.0",
"percent-encoding",
]

View File

@ -30,6 +30,8 @@ actix-web-static-files = "4.0"
static-files = "0.2.1"
zxcvbn = "3.1.0"
thiserror = "1.0.63"
idna = "=1.0.2"
regex = "1.11.1"
[build-dependencies]
built = "0.7.4"

View File

@ -4,6 +4,7 @@ pub mod password_help;
pub mod token_generation;
pub mod event_planning_template;
mod application_error;
pub mod test_helper;
pub use application_error::ApplicationError;
use chrono::{NaiveDate, Utc};

View File

@ -0,0 +1,128 @@
use std::{cell::OnceCell, env, str::FromStr, sync::Arc};
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use regex::{Captures, Regex};
use sqlx::{postgres::{PgConnectOptions, PgPoolOptions}, Connection, Executor, PgConnection, PgPool};
pub struct DbTestContext {
//pub app: Router,
pub db_pool: PgPool,
}
#[proc_macro_attribute]
pub fn db_test(_: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as ItemFn);
let test_name = input.sig.ident.clone();
let test_arguments = input.sig.inputs;
let test_block = input.block;
let inner_test_name = syn::Ident::new(
format!("inner_{}", test_name).as_str(),
input.sig.ident.span(),
);
let setup = quote! {
let context = abc_web::test_helpers::setup().await;
};
let teardown = quote! {
abc_web::test_helpers::teardown(context).await;
};
let output = quote!(
#[::tokio::test]
async fn #test_name() {
#setup
async fn #inner_test_name(#test_arguments) #test_block
#inner_test_name(&context).await;
#teardown
}
);
TokenStream::from(output)
}
#[allow(unused)]
pub async fn setup() -> DbTestContext {
//let init_config: OnceCell<Config> = OnceCell::new();
//let config = init_config.get_or_init(|| load_config(&Environment::Test).unwrap());
let test_db_pool = setup_db(&env::var("DATABASE_URL").unwrap()).await;
//let app = init_routes(AppState {
// db_pool: test_db_pool.clone(),
//});
DbTestContext {
//app,
db_pool: test_db_pool,
}
}
#[allow(unused)]
pub async fn teardown(context: DbTestContext) {
//drop(context.app);
teardown_db(context.db_pool);
}
#[allow(unused)]
pub async fn setup_db(url: &str) -> PgPool {
let test_db_config = prepare_db(url).await;
let pool = PgPoolOptions::new()
.connect(url)
.await
.unwrap();
pool
}
/// Drops a dedicated database for a test case.
///
/// This function is automatically called by the [`abc-macros::db_test`] macro. It ensures test-specific database are cleaned up after each test run so we don't end up with large numbers of unused databases.
pub async fn teardown_db(pool: PgPool) {
let cloned_pool = pool.clone();
//let mut connect_options = pool.connect_options();
//let db_config = Arc::make_mut(&mut connect_options);
let db_config = cloned_pool.connect_options();
drop(pool);
let root_db_config = db_config.clone().database("postgres");
let mut connection: PgConnection = Connection::connect_with(&root_db_config).await.unwrap();
let test_db_name = db_config.get_database().unwrap();
let query = format!("DROP DATABASE IF EXISTS {}", test_db_name);
connection.execute(query.as_str()).await.unwrap();
}
async fn prepare_db(url: &str) -> String {
let db_config = PgConnectOptions::from_str(url).expect("Invalid DATABASE_URL!");
let db_name = db_config.get_database().unwrap();
let root_db_config = db_config.clone().database("postgres");
let mut connection: PgConnection = Connection::connect_with(&root_db_config).await.unwrap();
let test_db_name = build_test_db_name(db_name);
let query = format!("CREATE DATABASE {} TEMPLATE {}", test_db_name, db_name);
connection.execute(query.as_str()).await.unwrap();
let regex = Regex::new(r"(.+)\/(.+$)").unwrap();
let test_db_url = regex.replace(url, |caps: &Captures| {
format!("{}/{}", &caps[1], test_db_name)
});
test_db_url.to_string()
}
fn build_test_db_name(base_name: &str) -> String {
let test_db_suffix: String = thread_rng()
.sample_iter(&Alphanumeric)
.take(30)
.map(char::from)
.collect();
format!("{}_{}", base_name, test_db_suffix).to_lowercase()
}