From 30169733a0cd44a06ebf7ec4e1d25b8328872a27 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Fri, 2 Jun 2023 17:01:59 -0700 Subject: [PATCH 1/2] More login testing, added reference user factory fn. --- src/db.rs | 9 ++++--- src/login.rs | 73 ++++++++++++++++++++++++++++++++++++++++----------- src/signup.rs | 2 +- 3 files changed, 64 insertions(+), 20 deletions(-) diff --git a/src/db.rs b/src/db.rs index 7358ff1..a6e374c 100644 --- a/src/db.rs +++ b/src/db.rs @@ -15,8 +15,8 @@ use uuid::Uuid; use crate::User; -const MAX_CONNS: u32 = 100; -const MIN_CONNS: u32 = 10; +const MAX_CONNS: u32 = 200; +const MIN_CONNS: u32 = 5; const TIMEOUT: u64 = 11; const SESSION_TTL: Duration = Duration::from_secs((365.2422 * 24. * 3600.0) as u64); @@ -34,6 +34,9 @@ pub async fn get_pool() -> SqlitePool { use rand_core::RngCore; let mut rng = rand_core::OsRng; let id = rng.next_u64(); + // see https://www.sqlite.org/inmemorydb.html for meaning of the string; + // it allows each separate test to have its own dedicated memory-backed db that + // will live as long as the whole process format!("file:testdb-{id}?mode=memory&cache=shared") } }) @@ -51,7 +54,7 @@ pub async fn get_pool() -> SqlitePool { let pool = SqlitePoolOptions::new() .max_connections(MAX_CONNS) .min_connections(MIN_CONNS) - .idle_timeout(Some(Duration::from_secs(10))) + .idle_timeout(Some(Duration::from_secs(30))) .max_lifetime(Some(Duration::from_secs(3600))) .connect_with(conn_opts) .await diff --git a/src/login.rs b/src/login.rs index c89c00b..46522ea 100644 --- a/src/login.rs +++ b/src/login.rs @@ -103,32 +103,40 @@ pub async fn post_logout(mut auth: AuthContext) -> impl IntoResponse { #[cfg(test)] mod test { - use std::time::Duration; - use axum::body::Bytes; use axum_test::TestServer; + use uuid::Uuid; use crate::{ db, signup::create_user, - templates::{LoginGet, LogoutGet, LogoutPost}, + templates::{Index, LoginGet, LogoutGet, LogoutPost}, + User, }; + fn get_user() -> User { + User { + username: "test_user".to_string(), + pwhash: "$argon2id$v=19$m=19456,t=2,p=1$GWsCH1w5RYaP9WWmq+xw0g$hmOEqC+MU+vnEk3bOdkoE+z01mOmmOeX08XyPyjqua8".to_string(), + id: Uuid::nil(), + ..Default::default() + } + } + async fn tserver() -> TestServer { let pool = db::get_pool().await; let secret = [0u8; 64]; - tokio::time::sleep(Duration::from_secs(2)).await; - - let _user = create_user( - "test_user", - &Some("Test User".to_string()), - &Some("mail@email".to_string()), - "aaaa".as_bytes(), - &pool, - ) - .await - .unwrap(); + let user = get_user(); + sqlx::query(crate::signup::CREATE_QUERY) + .bind(user.id) + .bind(&user.username) + .bind(&user.displayname) + .bind(&user.email) + .bind(&user.pwhash) + .execute(&pool) + .await + .unwrap(); let r = sqlx::query("select count(*) from witches") .fetch_one(&pool) @@ -152,7 +160,7 @@ mod test { async fn post_login_success() { let s = tserver().await; - let form = "username=test_user&password=aaaa".to_string(); + let form = "username=test_user&password=a".to_string(); let bytes = form.as_bytes(); let body = Bytes::copy_from_slice(bytes); @@ -182,6 +190,23 @@ mod test { assert_eq!(resp.status_code(), 200); } + #[tokio::test] + async fn post_login_bad_password() { + let s = tserver().await; + + let form = "username=test_user&password=bbbb".to_string(); + let bytes = form.as_bytes(); + let body = Bytes::copy_from_slice(bytes); + + let resp = s + .post("/login") + .expect_success() + .content_type("application/x-www-form-urlencoded") + .bytes(body) + .await; + assert_eq!(resp.status_code(), 200); + } + #[tokio::test] async fn get_logout() { let s = tserver().await; @@ -191,7 +216,7 @@ mod test { } #[tokio::test] - async fn post_logout() { + async fn post_logout_not_logged_in() { let s = tserver().await; let resp = s.post("/logout").await; resp.assert_status_ok(); @@ -199,4 +224,20 @@ mod test { let default = LogoutPost.to_string(); assert_eq!(body, &default); } + + #[tokio::test] + async fn post_logout_logged_in() { + let s = tserver().await; + // let resp = s + // .post("/login") + // .content_type("x-www-form-urlencoded") + // .text("username=test_user&password=a") + // .await; + + let resp = s.post("/logout").await; + resp.assert_status_ok(); + let body = std::str::from_utf8(resp.bytes()).unwrap(); + let default = LogoutPost.to_string(); + assert_eq!(body, &default); + } } diff --git a/src/signup.rs b/src/signup.rs index 0fc696e..eac1918 100644 --- a/src/signup.rs +++ b/src/signup.rs @@ -14,7 +14,7 @@ use uuid::Uuid; use crate::{templates::CreateUser, User}; -const CREATE_QUERY: &str = +pub(crate) const CREATE_QUERY: &str = "insert into witches (id, username, displayname, email, pwhash) values ($1, $2, $3, $4, $5)"; const ID_QUERY: &str = "select * from witches where id = $1"; From 81d35e1661dab7c36c629f22f31771807d8d352f Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Fri, 2 Jun 2023 17:32:48 -0700 Subject: [PATCH 2/2] Fix broken test and move helpers to test util module. --- src/lib.rs | 3 ++ src/login.rs | 72 ++++++++++++++++++----------------------------- src/test_utils.rs | 42 +++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 45 deletions(-) create mode 100644 src/test_utils.rs diff --git a/src/lib.rs b/src/lib.rs index 601098c..01afc36 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,9 @@ pub(crate) mod templates; pub mod users; pub(crate) mod util; +#[cfg(test)] +pub mod test_utils; + pub type AuthContext = axum_login::extractors::AuthContext>; pub async fn app(db_pool: SqlitePool, secret: &[u8]) -> Router { diff --git a/src/login.rs b/src/login.rs index 46522ea..98aae5b 100644 --- a/src/login.rs +++ b/src/login.rs @@ -104,49 +104,13 @@ pub async fn post_logout(mut auth: AuthContext) -> impl IntoResponse { #[cfg(test)] mod test { use axum::body::Bytes; - use axum_test::TestServer; - use uuid::Uuid; use crate::{ - db, - signup::create_user, templates::{Index, LoginGet, LogoutGet, LogoutPost}, - User, + test_utils::{get_user, tserver}, }; - fn get_user() -> User { - User { - username: "test_user".to_string(), - pwhash: "$argon2id$v=19$m=19456,t=2,p=1$GWsCH1w5RYaP9WWmq+xw0g$hmOEqC+MU+vnEk3bOdkoE+z01mOmmOeX08XyPyjqua8".to_string(), - id: Uuid::nil(), - ..Default::default() - } - } - - async fn tserver() -> TestServer { - let pool = db::get_pool().await; - let secret = [0u8; 64]; - - let user = get_user(); - sqlx::query(crate::signup::CREATE_QUERY) - .bind(user.id) - .bind(&user.username) - .bind(&user.displayname) - .bind(&user.email) - .bind(&user.pwhash) - .execute(&pool) - .await - .unwrap(); - - let r = sqlx::query("select count(*) from witches") - .fetch_one(&pool) - .await; - assert!(r.is_ok()); - - let app = crate::app(pool, &secret).await.into_make_service(); - - TestServer::new(app).unwrap() - } + const LOGIN_FORM: &str = "username=test_user&password=a"; #[tokio::test] async fn get_login() { @@ -160,7 +124,7 @@ mod test { async fn post_login_success() { let s = tserver().await; - let form = "username=test_user&password=a".to_string(); + let form = LOGIN_FORM.to_string(); let bytes = form.as_bytes(); let body = Bytes::copy_from_slice(bytes); @@ -228,14 +192,32 @@ mod test { #[tokio::test] async fn post_logout_logged_in() { let s = tserver().await; - // let resp = s - // .post("/login") - // .content_type("x-www-form-urlencoded") - // .text("username=test_user&password=a") - // .await; + + // log in and prove it + { + let form = LOGIN_FORM.to_string(); + let bytes = form.as_bytes(); + let body = Bytes::copy_from_slice(bytes); + + let resp = s + .post("/login") + .expect_failure() + .content_type("application/x-www-form-urlencoded") + .bytes(body) + .await; + assert_eq!(resp.status_code(), 303); + + let logged_in = Index { + user: Some(get_user()), + } + .to_string(); + + let idx = s.get("/").await; + let body = std::str::from_utf8(idx.bytes()).unwrap(); + assert_eq!(&logged_in, body); + } let resp = s.post("/logout").await; - resp.assert_status_ok(); let body = std::str::from_utf8(resp.bytes()).unwrap(); let default = LogoutPost.to_string(); assert_eq!(body, &default); diff --git a/src/test_utils.rs b/src/test_utils.rs new file mode 100644 index 0000000..fc103ec --- /dev/null +++ b/src/test_utils.rs @@ -0,0 +1,42 @@ +use axum_test::{TestServer, TestServerConfig}; +use uuid::Uuid; + +use crate::User; + +pub fn get_user() -> User { + User { + username: "test_user".to_string(), + pwhash: "$argon2id$v=19$m=19456,t=2,p=1$GWsCH1w5RYaP9WWmq+xw0g$hmOEqC+MU+vnEk3bOdkoE+z01mOmmOeX08XyPyjqua8".to_string(), + id: Uuid::nil(), + ..Default::default() + } +} + +pub async fn tserver() -> TestServer { + let pool = crate::db::get_pool().await; + let secret = [0u8; 64]; + + let user = get_user(); + sqlx::query(crate::signup::CREATE_QUERY) + .bind(user.id) + .bind(&user.username) + .bind(&user.displayname) + .bind(&user.email) + .bind(&user.pwhash) + .execute(&pool) + .await + .unwrap(); + + let r = sqlx::query("select count(*) from witches") + .fetch_one(&pool) + .await; + assert!(r.is_ok()); + + let app = crate::app(pool, &secret).await.into_make_service(); + + let config = TestServerConfig { + save_cookies: true, + ..Default::default() + }; + TestServer::new_with_config(app, config).unwrap() +}