refactor: test utils
This commit is contained in:
parent
4d04069f48
commit
31b09a1c76
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -688,6 +688,7 @@ dependencies = [
|
||||
"brass-config",
|
||||
"brass-macros",
|
||||
"built",
|
||||
"change-detection",
|
||||
"chrono",
|
||||
"dotenv",
|
||||
"futures-util",
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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(
|
||||
|
@ -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> {
|
||||
|
@ -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) => {
|
||||
|
111
web/src/utils/test_helper/test_context.rs
Normal file
111
web/src/utils/test_helper/test_context.rs
Normal 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()
|
||||
}
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user