From ed7ce149901457b010d3d0de883433a2a3457ada Mon Sep 17 00:00:00 2001 From: Max Hohlfeld Date: Wed, 11 Dec 2024 23:52:56 +0100 Subject: [PATCH] feat: fully working integration test --- Cargo.lock | 66 ++++++++++++++++++++-------- config/src/lib.rs | 2 + web/Cargo.toml | 5 ++- web/src/endpoints/mod.rs | 2 +- web/src/endpoints/user/post_login.rs | 10 ++--- web/src/main.rs | 66 ++++++++++++++++++---------- web/src/models/location.rs | 64 ++++++++++++++++++++++++--- web/src/postgres_session_store.rs | 1 - web/src/utils/test_helper/mod.rs | 47 ++++++++++++++------ 9 files changed, 192 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63769ded..9f347455 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,7 +31,7 @@ dependencies = [ "actix-web", "bitflags 2.6.0", "bytes", - "derive_more", + "derive_more 0.99.18", "futures-core", "http-range", "log", @@ -58,7 +58,7 @@ dependencies = [ "brotli", "bytes", "bytestring", - "derive_more", + "derive_more 0.99.18", "encoding_rs", "flate2", "futures-core", @@ -83,15 +83,15 @@ dependencies = [ [[package]] name = "actix-identity" -version = "0.5.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1224c9f9593dc27c9077b233ce04adedc1d7febcfc35ee9f53ea3c24df180bec" +checksum = "23b8ddc6f6a8b19c4016aaa13519968da9969bc3bc1c1c883cdb0f25dd6c8cf7" dependencies = [ "actix-service", "actix-session", "actix-utils", "actix-web", - "anyhow", + "derive_more 1.0.0", "futures-core", "serde", "tracing", @@ -162,16 +162,16 @@ dependencies = [ [[package]] name = "actix-session" -version = "0.7.2" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da8b818ae1f11049a4d218975345fe8e56ce5a5f92c11f972abcff5ff80e87" +checksum = "efe6976a74f34f1b6d07a6c05aadc0ed0359304a7781c367fa5b4029418db08f" dependencies = [ "actix-service", "actix-utils", "actix-web", "anyhow", - "async-trait", - "derive_more", + "derive_more 1.0.0", + "rand", "serde", "serde_json", "tracing", @@ -207,7 +207,7 @@ dependencies = [ "bytestring", "cfg-if", "cookie", - "derive_more", + "derive_more 0.99.18", "encoding_rs", "futures-core", "futures-util", @@ -248,7 +248,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adf6d1ef6d7a60e084f9e0595e2a5234abda14e76c105ecf8e2d0e8800c41a1f" dependencies = [ "actix-web", - "derive_more", + "derive_more 0.99.18", "futures-util", "static-files", ] @@ -731,6 +731,7 @@ name = "brass-web" version = "0.1.0" dependencies = [ "actix-files", + "actix-http", "actix-identity", "actix-session", "actix-web", @@ -1098,6 +1099,27 @@ dependencies = [ "syn", ] +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + [[package]] name = "digest" version = "0.10.7" @@ -2452,9 +2474,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags 2.6.0", ] @@ -2573,9 +2595,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.19" +version = "0.23.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" +checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" dependencies = [ "once_cell", "ring", @@ -2663,18 +2685,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", @@ -3285,6 +3307,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unicode_categories" version = "0.1.1" diff --git a/config/src/lib.rs b/config/src/lib.rs index cebfcce9..2936e403 100644 --- a/config/src/lib.rs +++ b/config/src/lib.rs @@ -2,6 +2,7 @@ use anyhow::anyhow; use std::env; use std::net::IpAddr; +#[derive(Clone, Debug)] pub struct Config { /// the ip the server will bind to, e.g. 127.0.0.1 or ::1 pub server_address: IpAddr, @@ -18,6 +19,7 @@ pub struct Config { pub smtp_tlstype: SmtpTlsType, } +#[derive(Clone, Debug)] pub enum SmtpTlsType { TLS, StartTLS, diff --git a/web/Cargo.toml b/web/Cargo.toml index 10c11718..76125ca5 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -15,8 +15,8 @@ serde = { version = "1.0.164", features = ["derive"] } argon2 = { version = "0.5.0", features = [ "std"]} anyhow = "1.0.71" dotenv = "0.15.0" -actix-session = { version = "0.7.2", features = ["cookie-session"] } -actix-identity = "0.5.2" +actix-session = { version = "0.10.1", features = ["cookie-session"] } +actix-identity = "0.8.0" chrono = { version = "0.4.33", features = ["serde", "now"] } actix-files = "0.6.5" askama_actix = "0.14.0" @@ -35,6 +35,7 @@ idna = "=1.0.2" regex = "1.11.1" brass-macros = { path = "../macros" } brass-config = { path = "../config" } +actix-http = "3.9.0" [build-dependencies] built = "0.7.4" diff --git a/web/src/endpoints/mod.rs b/web/src/endpoints/mod.rs index f4c6b645..e3e4e494 100644 --- a/web/src/endpoints/mod.rs +++ b/web/src/endpoints/mod.rs @@ -8,7 +8,7 @@ mod availability; mod events; mod export; mod location; -mod user; +pub mod user; mod imprint; mod vehicle; diff --git a/web/src/endpoints/user/post_login.rs b/web/src/endpoints/user/post_login.rs index 24361a45..8a6ae7cc 100644 --- a/web/src/endpoints/user/post_login.rs +++ b/web/src/endpoints/user/post_login.rs @@ -1,14 +1,14 @@ use actix_identity::Identity; use actix_web::{web, HttpMessage, HttpRequest, HttpResponse, Responder}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use sqlx::PgPool; use crate::{auth::utils::hash_plain_password_with_salt, models::User}; -#[derive(Deserialize)] -struct LoginForm { - email: String, - password: String, +#[derive(Deserialize, Serialize)] +pub struct LoginForm { + pub email: String, + pub password: String, } #[actix_web::post("/login")] diff --git a/web/src/main.rs b/web/src/main.rs index 5d596bbe..9ca10275 100644 --- a/web/src/main.rs +++ b/web/src/main.rs @@ -3,11 +3,15 @@ use std::time::Duration; use actix_identity::IdentityMiddleware; use actix_session::SessionMiddleware; +use actix_web::body::MessageBody; use actix_web::cookie::Key; +use actix_web::dev::{ServiceFactory, ServiceRequest, ServiceResponse}; use actix_web::{web, App, HttpServer}; use actix_web_static_files::ResourceFiles; use brass_config::{get_env, load_config, Config}; +use lettre::Transport; use sqlx::postgres::PgPool; +use sqlx::{Pool, Postgres}; use crate::postgres_session_store::SqlxPostgresqlSessionStore; use crate::utils::manage_commands::{handle_command, parse_args}; @@ -33,33 +37,49 @@ async fn main() -> anyhow::Result<()> { let pool = PgPool::connect(&config.database_url).await?; let mailer = utils::email::get_mailer()?; - let secret_key = Key::from(config.secret_key.as_bytes()); - let store = SqlxPostgresqlSessionStore::from_pool(pool.clone().into()); handle_command(args.command, &pool).await?; - println!("Starting server on http://{}:{}.", config.server_address, config.server_port); + let address = config.server_address; + let port = config.server_port; + println!("Starting server on http://{address}:{port}.",); - HttpServer::new(move || { - let generated = generate(); - - App::new() - .app_data(web::Data::new(pool.clone())) - .app_data(web::Data::new(mailer.clone())) - .configure(endpoints::init) - .wrap(middleware::RedirectToLogin) - .wrap(middleware::LoadCurrentUser) - .wrap( - IdentityMiddleware::builder() - .visit_deadline(Some(Duration::from_secs(300))) - .build(), - ) - .wrap(SessionMiddleware::new(store.clone(), secret_key.clone())) - .service(ResourceFiles::new("/static", generated)) - }) - .bind((config.server_address, config.server_port))? - .run() - .await?; + HttpServer::new(move || create_app(config.clone(), pool.clone(), mailer.clone())) + .bind((address, port))? + .run() + .await?; Ok(()) } + +pub fn create_app( + config: Config, + pool: Pool, + mailer: T, +) -> App< + impl ServiceFactory< + ServiceRequest, + Response = ServiceResponse, + Config = (), + InitError = (), + Error = actix_web::error::Error, + >, +> where T: Transport + 'static { + let generated = generate(); + let secret_key = Key::from(config.secret_key.as_bytes()); + let store = SqlxPostgresqlSessionStore::from_pool(pool.clone().into()); + + App::new() + .app_data(web::Data::new(pool)) + .app_data(web::Data::new(mailer)) + .configure(endpoints::init) + .wrap(middleware::RedirectToLogin) + .wrap(middleware::LoadCurrentUser) + .wrap( + IdentityMiddleware::builder() + .visit_deadline(Some(Duration::from_secs(300))) + .build(), + ) + .wrap(SessionMiddleware::new(store.clone(), secret_key.clone())) + .service(ResourceFiles::new("/static", generated)) +} diff --git a/web/src/models/location.rs b/web/src/models/location.rs index 52da2976..16d8b125 100644 --- a/web/src/models/location.rs +++ b/web/src/models/location.rs @@ -1,14 +1,24 @@ +use actix_http::StatusCode; +use actix_identity::Identity; +use actix_web::test; +use actix_web::test::call_service; +use actix_web::test::TestRequest; +use actix_web::HttpMessage; use brass_macros::db_test; use sqlx::{query, PgPool}; +use crate::auth::utils::generate_salt_and_hash_plain_password; +use crate::auth::utils::hash_plain_password_with_salt; +use crate::endpoints::user::post_login::LoginForm; +use crate::models::Function; +use crate::models::Role; +use crate::models::User; use crate::utils::test_helper::DbTestContext; use super::Area; use super::Result; -//use macros::db_test; - #[derive(Debug)] pub struct Location { pub id: i32, @@ -126,10 +136,52 @@ impl Location { #[db_test] async fn test_read_all(context: &DbTestContext) { - println!("im test {:#?}", context.db_pool.connect_options()); - Location::create(&context.db_pool, "abc", 1).await.unwrap(); + let app = context.app().await; - let locations = Location::read_all(&context.db_pool).await.unwrap(); + let (hash, salt) = generate_salt_and_hash_plain_password("abc").unwrap(); + User::create_with_password(&context.db_pool, "abc", "abc", &hash, &salt, Role::Admin, Function::Wachhabender, 1).await.unwrap(); - assert_eq!(1, locations.len()); + Location::create(&context.db_pool, "wundervolle location", 1).await.unwrap(); + + //let locations = Location::read_all(&context.db_pool).await.unwrap(); + // + //assert_eq!(1, locations.len()); + + let login_form = LoginForm { + email: "abc".to_string(), + password: "abc".to_string() + }; + + let login_req = TestRequest::post() + .uri("/login") + .set_form(login_form) + .to_request(); + + let login_resp = call_service(&app, login_req).await; + //println!("{:?}", log); + let cookie = login_resp.response().cookies().next().unwrap().to_owned(); + + let req = TestRequest::get() + .uri("/locations") + .cookie(cookie) + .to_request(); + + //let http_req = TestRequest::get() + // .uri("/locations") + // .to_http_request(); + //let service_req = TestRequest::get() + // .uri("/locations") + // .to_srv_request(); + + //Identity::login(&req.extensions(), "1".to_string()).unwrap(); + let resp = call_service(&context.app().await, req).await; + //let resp = test::call_and_read_body(&context.app().await, http_req).await; + + println!("{:?}", resp.headers()); + assert_eq!(StatusCode::OK, resp.status()); + + + let body = test::read_body(resp).await; + let body_string = String::from_utf8(body.to_vec()).unwrap(); + assert!(body_string.contains("wundervolle location")); } diff --git a/web/src/postgres_session_store.rs b/web/src/postgres_session_store.rs index 63ce425e..daff0cba 100644 --- a/web/src/postgres_session_store.rs +++ b/web/src/postgres_session_store.rs @@ -49,7 +49,6 @@ impl SqlxPostgresqlSessionStore { pub(crate) type SessionState = HashMap; -#[async_trait::async_trait(?Send)] impl SessionStore for SqlxPostgresqlSessionStore { async fn load(&self, session_key: &SessionKey) -> Result, LoadError> { let key = (self.configuration.cache_keygen)(session_key.as_ref()); diff --git a/web/src/utils/test_helper/mod.rs b/web/src/utils/test_helper/mod.rs index 1094ef91..0ee23450 100644 --- a/web/src/utils/test_helper/mod.rs +++ b/web/src/utils/test_helper/mod.rs @@ -1,17 +1,44 @@ 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, Transport}; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use regex::{Captures, Regex}; -use sqlx::{postgres::{PgConnectOptions, PgPoolOptions}, Connection, Executor, PgConnection, PgPool}; +use sqlx::{ + postgres::{PgConnectOptions, PgPoolOptions}, + Connection, Executor, PgConnection, PgPool, +}; +use crate::create_app; #[derive(Debug)] pub struct DbTestContext { - //pub app: Router, pub db_pool: PgPool, + config: Config, } +impl DbTestContext { + pub async fn app( + &self, + ) -> impl Service< + Request, + Response = ServiceResponse, + 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 { @@ -20,17 +47,12 @@ pub async fn setup() -> DbTestContext { let test_db_pool = setup_db(&config.database_url).await; - //let app = init_routes(AppState { - // db_pool: test_db_pool.clone(), - //}); - - let abc = DbTestContext { - //app, + let context = DbTestContext { + config: config.clone(), db_pool: test_db_pool, }; - println!("{abc:#?}"); - abc + context } #[allow(unused)] @@ -45,10 +67,7 @@ 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(); + let pool = PgPoolOptions::new().connect(&test_db_config).await.unwrap(); pool }