refactor: test utils

This commit is contained in:
Max Hohlfeld 2024-12-16 21:55:19 +01:00
parent 4d04069f48
commit 31b09a1c76
10 changed files with 138 additions and 142 deletions

1
Cargo.lock generated
View File

@ -688,6 +688,7 @@ dependencies = [
"brass-config",
"brass-macros",
"built",
"change-detection",
"chrono",
"dotenv",
"futures-util",

View File

@ -2,3 +2,9 @@
members = [ "config", "macros", "web", ]
resolver = "2"
default-members = ["web"]
[profile.dev.package.rinja_derive]
opt-level = 3
[profile.dev.package.sqlx-macros]
opt-level = 3

View File

@ -2,7 +2,7 @@ use anyhow::anyhow;
use std::env;
use std::net::IpAddr;
#[derive(Clone, Debug)]
#[derive(Clone)]
pub struct Config {
/// the ip the server will bind to, e.g. 127.0.0.1 or ::1
pub server_address: IpAddr,
@ -19,7 +19,7 @@ pub struct Config {
pub smtp_tlstype: SmtpTlsType,
}
#[derive(Clone, Debug)]
#[derive(Clone)]
pub enum SmtpTlsType {
TLS,
StartTLS,

View File

@ -10,8 +10,6 @@ publish = false
[dependencies]
sqlx = { version = "^0.8", features = ["runtime-async-std-rustls", "postgres", "chrono"] }
actix-web = { version = "4" }
# askama = { version = "0.13.0", git = "https://github.com/rinja-rs/askama", branch = "main", features = ["with-actix-web"] }
# askama_actix = { git = "https://github.com/rinja-rs/askama", branch = "main" }
serde = { version = "1.0.164", features = ["derive"] }
argon2 = { version = "0.5.0", features = [ "std"]}
anyhow = "1.0.71"
@ -41,9 +39,7 @@ rinja = "0.3.5"
[build-dependencies]
built = "0.7.4"
static-files = "0.2.1"
change-detection = "1.2.0"
[dev-dependencies]
insta = "1.41.1"
# [profile.dev.package.askama_derive]
# opt-level = 3

View File

@ -1,3 +1,4 @@
use change_detection::ChangeDetection;
use static_files::{resource_dir, NpmBuild};
use std::{
fs::{self, copy},
@ -5,9 +6,15 @@ use std::{
};
fn main() -> std::io::Result<()> {
ChangeDetection::path("static/utils.js")
.path("static/style.scss")
.path("static/brass.jpeg")
.path("static/package.json")
.generate();
built::write_built_file().expect("Failed to acquire build-time information");
NpmBuild::new("./static").change_detection().install()?.run("build-bulma")?;
NpmBuild::new("./static").install()?.run("build-bulma")?;
let dist_path = Path::new("./static/dist");
let nm_path = Path::new("./static/node_modules");

View File

@ -9,9 +9,7 @@ use crate::{
};
#[cfg(test)]
use crate::utils::test_helper::{test_delete, DbTestContext, RequestConfig};
#[cfg(test)]
use actix_http::StatusCode;
use crate::utils::test_helper::{test_delete, DbTestContext, RequestConfig, StatusCode};
#[actix_web::delete("/area/delete/{id}")]
pub async fn delete(

View File

@ -9,10 +9,8 @@ use rinja::Template;
#[cfg(test)]
use crate::utils::test_helper::{
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig,
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig, StatusCode
};
#[cfg(test)]
use actix_http::StatusCode;
#[actix_web::get("/area/new")]
async fn get(user: web::ReqData<User>) -> Result<impl Responder, ApplicationError> {

View File

@ -1,131 +1,10 @@
use std::{cell::OnceCell, str::FromStr, sync::Arc};
use actix_http::Request;
use actix_web::{
body::MessageBody,
dev::{Service, ServiceResponse},
test::init_service,
};
use brass_config::{load_config, Config, Environment};
use lettre::transport::stub::StubTransport;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use regex::{Captures, Regex};
use sqlx::{
postgres::{PgConnectOptions, PgPoolOptions},
Connection, Executor, PgConnection, PgPool, Pool, Postgres,
};
mod test_context;
mod test_requests;
pub use test_requests::test_delete;
pub use test_requests::test_get;
pub use test_requests::test_post;
pub use test_context::{setup, teardown, DbTestContext};
pub use test_requests::RequestConfig;
pub use test_requests::read_body;
pub use test_requests::{read_body, test_delete, test_get, test_post};
use crate::create_app;
#[derive(Debug)]
pub struct DbTestContext {
pub db_pool: PgPool,
config: Config,
}
impl DbTestContext {
pub async fn app(
&self,
) -> impl Service<
Request,
Response = ServiceResponse<impl MessageBody>,
Error = actix_web::error::Error,
> {
init_service(create_app(
self.config.clone(),
self.db_pool.clone(),
StubTransport::new_ok(),
))
.await
}
}
#[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(&config.database_url).await;
let context = DbTestContext {
config: config.clone(),
db_pool: test_db_pool,
};
context
}
#[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;
println!("{test_db_config}");
let pool = PgPoolOptions::new().connect(&test_db_config).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 mut connect_options = pool.connect_options();
let db_config = Arc::make_mut(&mut 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)
});
println!("{test_db_url}");
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()
}
pub use actix_http::StatusCode as StatusCode;
macro_rules! assert_snapshot {
($x:expr) => {

View File

@ -0,0 +1,111 @@
use std::{cell::OnceCell, str::FromStr, sync::Arc, time::Duration};
use actix_http::Request;
use actix_web::{
body::MessageBody,
dev::{Service, ServiceResponse},
test::init_service,
};
use lettre::transport::stub::StubTransport;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use crate::create_app;
use brass_config::{load_config, Config, Environment};
use regex::{Captures, Regex};
use sqlx::{
postgres::{PgConnectOptions, PgPoolOptions},
Connection, Executor, PgConnection, PgPool,
};
pub struct DbTestContext {
pub db_pool: PgPool,
pub(crate) config: Config,
}
impl DbTestContext {
pub async fn app(
&self,
) -> impl Service<
Request,
Response = ServiceResponse<impl MessageBody>,
Error = actix_web::error::Error,
> {
init_service(create_app(
self.config.clone(),
self.db_pool.clone(),
StubTransport::new_ok(),
))
.await
}
}
#[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_config = prepare_db(&config.database_url).await;
let test_db_pool = PgPoolOptions::new()
.max_lifetime(Some(Duration::from_secs(1))) // workaround for idle connections
.connect(&test_db_config)
.await
.unwrap();
let context = DbTestContext {
config: config.clone(),
db_pool: test_db_pool.clone(),
};
context
}
#[allow(unused)]
pub async fn teardown(context: DbTestContext) {
let mut connect_options = context.db_pool.connect_options();
let db_config = Arc::make_mut(&mut connect_options);
drop(context.db_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();
// workaround
//let drop_query = format!("SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE pid <> pg_backend_pid( ) AND datname = {}", test_db_name);
//connection.execute(drop_query.as_str()).await.unwrap();
let query = format!("DROP DATABASE IF EXISTS {}", test_db_name);
//println!("{query}");
connection.execute(query.as_str()).await.unwrap();
}
pub(crate) 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)
});
//println!("{test_db_url}");
test_db_url.to_string()
}
pub(crate) 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()
}

View File

@ -86,7 +86,7 @@ where
T: Service<Request, Response = ServiceResponse<R>, Error = Error>,
R: MessageBody,
{
let cookie = create_user_and_get_login_cookie(&pool, &app, &config).await;
let cookie = create_user_and_get_login_cookie(pool, &app, &config).await;
let get_request = test::TestRequest::get()
.uri(&config.uri)
@ -107,7 +107,7 @@ where
R: MessageBody,
F: Serialize,
{
let cookie = create_user_and_get_login_cookie(&pool, &app, config).await;
let cookie = create_user_and_get_login_cookie(pool, &app, config).await;
let post_request = test::TestRequest::post()
.uri(&config.uri)
@ -127,7 +127,7 @@ where
T: Service<Request, Response = ServiceResponse<R>, Error = Error>,
R: MessageBody,
{
let cookie = create_user_and_get_login_cookie(&pool, &app, config).await;
let cookie = create_user_and_get_login_cookie(pool, &app, config).await;
let delete_request = test::TestRequest::delete()
.uri(&config.uri)