feat: wip test helper
This commit is contained in:
parent
b27bdb6daa
commit
00b1b87da4
27
Cargo.lock
generated
27
Cargo.lock
generated
@ -757,10 +757,12 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"idna 1.0.2",
|
||||||
"lettre",
|
"lettre",
|
||||||
"pico-args",
|
"pico-args",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
"rand",
|
"rand",
|
||||||
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
@ -1714,23 +1716,24 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "1.0.3"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
|
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"idna_adapter",
|
"unicode-bidi",
|
||||||
"smallvec",
|
"unicode-normalization",
|
||||||
"utf8_iter",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna_adapter"
|
name = "idna"
|
||||||
version = "1.2.0"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
|
checksum = "bd69211b9b519e98303c015e21a007e293db403b6c85b9b124e133d25e242cdd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"icu_normalizer",
|
"icu_normalizer",
|
||||||
"icu_properties",
|
"icu_properties",
|
||||||
|
"smallvec",
|
||||||
|
"utf8_iter",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1846,7 +1849,7 @@ dependencies = [
|
|||||||
"futures-util",
|
"futures-util",
|
||||||
"hostname",
|
"hostname",
|
||||||
"httpdate",
|
"httpdate",
|
||||||
"idna",
|
"idna 1.0.2",
|
||||||
"mime",
|
"mime",
|
||||||
"native-tls",
|
"native-tls",
|
||||||
"nom",
|
"nom",
|
||||||
@ -3058,12 +3061,12 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.4"
|
version = "2.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
|
checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"idna",
|
"idna 0.5.0",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -30,6 +30,8 @@ actix-web-static-files = "4.0"
|
|||||||
static-files = "0.2.1"
|
static-files = "0.2.1"
|
||||||
zxcvbn = "3.1.0"
|
zxcvbn = "3.1.0"
|
||||||
thiserror = "1.0.63"
|
thiserror = "1.0.63"
|
||||||
|
idna = "=1.0.2"
|
||||||
|
regex = "1.11.1"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
built = "0.7.4"
|
built = "0.7.4"
|
||||||
|
@ -4,6 +4,7 @@ pub mod password_help;
|
|||||||
pub mod token_generation;
|
pub mod token_generation;
|
||||||
pub mod event_planning_template;
|
pub mod event_planning_template;
|
||||||
mod application_error;
|
mod application_error;
|
||||||
|
pub mod test_helper;
|
||||||
|
|
||||||
pub use application_error::ApplicationError;
|
pub use application_error::ApplicationError;
|
||||||
use chrono::{NaiveDate, Utc};
|
use chrono::{NaiveDate, Utc};
|
||||||
|
128
src/utils/test_helper/mod.rs
Normal file
128
src/utils/test_helper/mod.rs
Normal 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()
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user