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-config",
|
||||||
"brass-macros",
|
"brass-macros",
|
||||||
"built",
|
"built",
|
||||||
|
"change-detection",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
@ -2,3 +2,9 @@
|
|||||||
members = [ "config", "macros", "web", ]
|
members = [ "config", "macros", "web", ]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
default-members = ["web"]
|
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::env;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// the ip the server will bind to, e.g. 127.0.0.1 or ::1
|
/// the ip the server will bind to, e.g. 127.0.0.1 or ::1
|
||||||
pub server_address: IpAddr,
|
pub server_address: IpAddr,
|
||||||
@ -19,7 +19,7 @@ pub struct Config {
|
|||||||
pub smtp_tlstype: SmtpTlsType,
|
pub smtp_tlstype: SmtpTlsType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
pub enum SmtpTlsType {
|
pub enum SmtpTlsType {
|
||||||
TLS,
|
TLS,
|
||||||
StartTLS,
|
StartTLS,
|
||||||
|
@ -10,8 +10,6 @@ publish = false
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
sqlx = { version = "^0.8", features = ["runtime-async-std-rustls", "postgres", "chrono"] }
|
sqlx = { version = "^0.8", features = ["runtime-async-std-rustls", "postgres", "chrono"] }
|
||||||
actix-web = { version = "4" }
|
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"] }
|
serde = { version = "1.0.164", features = ["derive"] }
|
||||||
argon2 = { version = "0.5.0", features = [ "std"]}
|
argon2 = { version = "0.5.0", features = [ "std"]}
|
||||||
anyhow = "1.0.71"
|
anyhow = "1.0.71"
|
||||||
@ -41,9 +39,7 @@ rinja = "0.3.5"
|
|||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
built = "0.7.4"
|
built = "0.7.4"
|
||||||
static-files = "0.2.1"
|
static-files = "0.2.1"
|
||||||
|
change-detection = "1.2.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
insta = "1.41.1"
|
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 static_files::{resource_dir, NpmBuild};
|
||||||
use std::{
|
use std::{
|
||||||
fs::{self, copy},
|
fs::{self, copy},
|
||||||
@ -5,9 +6,15 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn main() -> std::io::Result<()> {
|
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");
|
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 dist_path = Path::new("./static/dist");
|
||||||
let nm_path = Path::new("./static/node_modules");
|
let nm_path = Path::new("./static/node_modules");
|
||||||
|
@ -9,9 +9,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::utils::test_helper::{test_delete, DbTestContext, RequestConfig};
|
use crate::utils::test_helper::{test_delete, DbTestContext, RequestConfig, StatusCode};
|
||||||
#[cfg(test)]
|
|
||||||
use actix_http::StatusCode;
|
|
||||||
|
|
||||||
#[actix_web::delete("/area/delete/{id}")]
|
#[actix_web::delete("/area/delete/{id}")]
|
||||||
pub async fn delete(
|
pub async fn delete(
|
||||||
|
@ -9,10 +9,8 @@ use rinja::Template;
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::utils::test_helper::{
|
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")]
|
#[actix_web::get("/area/new")]
|
||||||
async fn get(user: web::ReqData<User>) -> Result<impl Responder, ApplicationError> {
|
async fn get(user: web::ReqData<User>) -> Result<impl Responder, ApplicationError> {
|
||||||
|
@ -1,131 +1,10 @@
|
|||||||
use std::{cell::OnceCell, str::FromStr, sync::Arc};
|
mod test_context;
|
||||||
|
|
||||||
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_requests;
|
mod test_requests;
|
||||||
pub use test_requests::test_delete;
|
pub use test_context::{setup, teardown, DbTestContext};
|
||||||
pub use test_requests::test_get;
|
|
||||||
pub use test_requests::test_post;
|
|
||||||
pub use test_requests::RequestConfig;
|
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;
|
pub use actix_http::StatusCode as StatusCode;
|
||||||
|
|
||||||
#[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()
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! assert_snapshot {
|
macro_rules! assert_snapshot {
|
||||||
($x:expr) => {
|
($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>,
|
T: Service<Request, Response = ServiceResponse<R>, Error = Error>,
|
||||||
R: MessageBody,
|
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()
|
let get_request = test::TestRequest::get()
|
||||||
.uri(&config.uri)
|
.uri(&config.uri)
|
||||||
@ -107,7 +107,7 @@ where
|
|||||||
R: MessageBody,
|
R: MessageBody,
|
||||||
F: Serialize,
|
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()
|
let post_request = test::TestRequest::post()
|
||||||
.uri(&config.uri)
|
.uri(&config.uri)
|
||||||
@ -127,7 +127,7 @@ where
|
|||||||
T: Service<Request, Response = ServiceResponse<R>, Error = Error>,
|
T: Service<Request, Response = ServiceResponse<R>, Error = Error>,
|
||||||
R: MessageBody,
|
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()
|
let delete_request = test::TestRequest::delete()
|
||||||
.uri(&config.uri)
|
.uri(&config.uri)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user