Fix the tests.
The test server had to be created in an async context or Hyper wouldn't run.
This commit is contained in:
parent
48308aa169
commit
359a732a84
8 changed files with 419 additions and 454 deletions
283
src/db.rs
283
src/db.rs
|
@ -126,14 +126,18 @@ pub async fn auth_layer(
|
||||||
//-************************************************************************
|
//-************************************************************************
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
#[tokio::test]
|
#[test]
|
||||||
async fn it_migrates_the_db() {
|
fn it_migrates_the_db() {
|
||||||
|
let rt = Runtime::new().unwrap();
|
||||||
let db = super::get_db_pool();
|
let db = super::get_db_pool();
|
||||||
let r = sqlx::query("select count(*) from users")
|
rt.block_on(async {
|
||||||
.fetch_one(&db)
|
let r = sqlx::query("select count(*) from users")
|
||||||
.await;
|
.fetch_one(&db)
|
||||||
assert!(r.is_ok());
|
.await;
|
||||||
|
assert!(r.is_ok());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,223 +386,172 @@ mod session_store {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn test_store() -> SqliteSessionStore {
|
async fn test_store() -> SqliteSessionStore {
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let store = SqliteSessionStore::new("sqlite::memory:")
|
||||||
.enable_all()
|
.await
|
||||||
.build()
|
.expect("building a sqlite :memory: SqliteSessionStore");
|
||||||
.unwrap();
|
store
|
||||||
|
.migrate()
|
||||||
let store = rt.block_on(async {
|
.await
|
||||||
let store = SqliteSessionStore::new("sqlite::memory:")
|
.expect("migrating a brand new :memory: SqliteSessionStore");
|
||||||
.await
|
|
||||||
.expect("building a sqlite :memory: SqliteSessionStore");
|
|
||||||
store
|
|
||||||
.migrate()
|
|
||||||
.await
|
|
||||||
.expect("migrating a brand new :memory: SqliteSessionStore");
|
|
||||||
store
|
|
||||||
});
|
|
||||||
dbg!("got the store");
|
|
||||||
store
|
store
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn creating_a_new_session_with_no_expiry() -> Result {
|
async fn creating_a_new_session_with_no_expiry() -> Result {
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let store = test_store().await;
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let store = test_store();
|
|
||||||
let mut session = Session::new();
|
let mut session = Session::new();
|
||||||
dbg!("new session");
|
|
||||||
session.insert("key", "value")?;
|
session.insert("key", "value")?;
|
||||||
let cloned = session.clone();
|
let cloned = session.clone();
|
||||||
|
|
||||||
rt.block_on(async {
|
let cookie_value = store.store_session(session).await?.unwrap();
|
||||||
let cookie_value = store.store_session(session).await?.unwrap();
|
|
||||||
|
|
||||||
let (id, expires, serialized, count): (String, Option<i64>, String, i64) =
|
let (id, expires, serialized, count): (String, Option<i64>, String, i64) =
|
||||||
sqlx::query_as("select id, expires, session, count(*) from async_sessions")
|
sqlx::query_as("select id, expires, session, count(*) from async_sessions")
|
||||||
.fetch_one(&mut store.connection().await?)
|
.fetch_one(&mut store.connection().await?)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
assert_eq!(1, count);
|
assert_eq!(1, count);
|
||||||
assert_eq!(id, cloned.id());
|
assert_eq!(id, cloned.id());
|
||||||
assert_eq!(expires, None);
|
assert_eq!(expires, None);
|
||||||
|
|
||||||
let deserialized_session: Session = serde_json::from_str(&serialized)?;
|
let deserialized_session: Session = serde_json::from_str(&serialized)?;
|
||||||
assert_eq!(cloned.id(), deserialized_session.id());
|
assert_eq!(cloned.id(), deserialized_session.id());
|
||||||
assert_eq!("value", &deserialized_session.get::<String>("key").unwrap());
|
assert_eq!("value", &deserialized_session.get::<String>("key").unwrap());
|
||||||
|
|
||||||
let loaded_session = store.load_session(cookie_value).await?.unwrap();
|
let loaded_session = store.load_session(cookie_value).await?.unwrap();
|
||||||
assert_eq!(cloned.id(), loaded_session.id());
|
assert_eq!(cloned.id(), loaded_session.id());
|
||||||
assert_eq!("value", &loaded_session.get::<String>("key").unwrap());
|
assert_eq!("value", &loaded_session.get::<String>("key").unwrap());
|
||||||
|
|
||||||
assert!(!loaded_session.is_expired());
|
assert!(!loaded_session.is_expired());
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn updating_a_session() -> Result {
|
async fn updating_a_session() -> Result {
|
||||||
let store = test_store();
|
let store = test_store().await;
|
||||||
let mut session = Session::new();
|
let mut session = Session::new();
|
||||||
let original_id = session.id().to_owned();
|
let original_id = session.id().to_owned();
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
session.insert("key", "value")?;
|
session.insert("key", "value")?;
|
||||||
|
|
||||||
rt.block_on(async {
|
let cookie_value = store.store_session(session).await?.unwrap();
|
||||||
let cookie_value = store.store_session(session).await?.unwrap();
|
|
||||||
|
|
||||||
let mut session = store.load_session(cookie_value.clone()).await?.unwrap();
|
let mut session = store.load_session(cookie_value.clone()).await?.unwrap();
|
||||||
session.insert("key", "other value")?;
|
session.insert("key", "other value")?;
|
||||||
assert_eq!(None, store.store_session(session).await?);
|
assert_eq!(None, store.store_session(session).await?);
|
||||||
|
|
||||||
let session = store.load_session(cookie_value.clone()).await?.unwrap();
|
let session = store.load_session(cookie_value.clone()).await?.unwrap();
|
||||||
assert_eq!(session.get::<String>("key").unwrap(), "other value");
|
assert_eq!(session.get::<String>("key").unwrap(), "other value");
|
||||||
|
|
||||||
let (id, count): (String, i64) =
|
let (id, count): (String, i64) =
|
||||||
sqlx::query_as("select id, count(*) from async_sessions")
|
sqlx::query_as("select id, count(*) from async_sessions")
|
||||||
.fetch_one(&mut store.connection().await?)
|
.fetch_one(&mut store.connection().await?)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
assert_eq!(1, count);
|
assert_eq!(1, count);
|
||||||
assert_eq!(original_id, id);
|
assert_eq!(original_id, id);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn updating_a_session_extending_expiry() -> Result {
|
async fn updating_a_session_extending_expiry() -> Result {
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let store = test_store().await;
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let store = test_store();
|
|
||||||
let mut session = Session::new();
|
let mut session = Session::new();
|
||||||
session.expire_in(Duration::from_secs(10));
|
session.expire_in(Duration::from_secs(10));
|
||||||
let original_id = session.id().to_owned();
|
let original_id = session.id().to_owned();
|
||||||
let original_expires = session.expiry().unwrap().clone();
|
let original_expires = session.expiry().unwrap().clone();
|
||||||
|
|
||||||
rt.block_on(async {
|
let cookie_value = store.store_session(session).await?.unwrap();
|
||||||
let cookie_value = store.store_session(session).await?.unwrap();
|
|
||||||
|
|
||||||
let mut session = store.load_session(cookie_value.clone()).await?.unwrap();
|
let mut session = store.load_session(cookie_value.clone()).await?.unwrap();
|
||||||
assert_eq!(session.expiry().unwrap(), &original_expires);
|
assert_eq!(session.expiry().unwrap(), &original_expires);
|
||||||
session.expire_in(Duration::from_secs(20));
|
session.expire_in(Duration::from_secs(20));
|
||||||
let new_expires = session.expiry().unwrap().clone();
|
let new_expires = session.expiry().unwrap().clone();
|
||||||
store.store_session(session).await?;
|
store.store_session(session).await?;
|
||||||
|
|
||||||
let session = store.load_session(cookie_value.clone()).await?.unwrap();
|
let session = store.load_session(cookie_value.clone()).await?.unwrap();
|
||||||
assert_eq!(session.expiry().unwrap(), &new_expires);
|
assert_eq!(session.expiry().unwrap(), &new_expires);
|
||||||
|
|
||||||
let (id, expires, count): (String, i64, i64) =
|
let (id, expires, count): (String, i64, i64) =
|
||||||
sqlx::query_as("select id, expires, count(*) from async_sessions")
|
sqlx::query_as("select id, expires, count(*) from async_sessions")
|
||||||
.fetch_one(&mut store.connection().await?)
|
.fetch_one(&mut store.connection().await?)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
assert_eq!(1, count);
|
assert_eq!(1, count);
|
||||||
assert_eq!(expires, new_expires.timestamp());
|
assert_eq!(expires, new_expires.timestamp());
|
||||||
assert_eq!(original_id, id);
|
assert_eq!(original_id, id);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn creating_a_new_session_with_expiry() -> Result {
|
async fn creating_a_new_session_with_expiry() -> Result {
|
||||||
let store = test_store();
|
let store = test_store().await;
|
||||||
let mut session = Session::new();
|
let mut session = Session::new();
|
||||||
session.expire_in(Duration::from_secs(1));
|
session.expire_in(Duration::from_secs(1));
|
||||||
session.insert("key", "value")?;
|
session.insert("key", "value")?;
|
||||||
let cloned = session.clone();
|
let cloned = session.clone();
|
||||||
|
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let cookie_value = store.store_session(session).await?.unwrap();
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
rt.block_on(async {
|
let (id, expires, serialized, count): (String, Option<i64>, String, i64) =
|
||||||
let cookie_value = store.store_session(session).await?.unwrap();
|
sqlx::query_as("select id, expires, session, count(*) from async_sessions")
|
||||||
|
.fetch_one(&mut store.connection().await?)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let (id, expires, serialized, count): (String, Option<i64>, String, i64) =
|
assert_eq!(1, count);
|
||||||
sqlx::query_as("select id, expires, session, count(*) from async_sessions")
|
assert_eq!(id, cloned.id());
|
||||||
.fetch_one(&mut store.connection().await?)
|
assert!(expires.unwrap() > Utc::now().timestamp());
|
||||||
.await?;
|
|
||||||
|
|
||||||
assert_eq!(1, count);
|
let deserialized_session: Session = serde_json::from_str(&serialized)?;
|
||||||
assert_eq!(id, cloned.id());
|
assert_eq!(cloned.id(), deserialized_session.id());
|
||||||
assert!(expires.unwrap() > Utc::now().timestamp());
|
assert_eq!("value", &deserialized_session.get::<String>("key").unwrap());
|
||||||
|
|
||||||
let deserialized_session: Session = serde_json::from_str(&serialized)?;
|
let loaded_session = store.load_session(cookie_value.clone()).await?.unwrap();
|
||||||
assert_eq!(cloned.id(), deserialized_session.id());
|
assert_eq!(cloned.id(), loaded_session.id());
|
||||||
assert_eq!("value", &deserialized_session.get::<String>("key").unwrap());
|
assert_eq!("value", &loaded_session.get::<String>("key").unwrap());
|
||||||
|
|
||||||
let loaded_session = store.load_session(cookie_value.clone()).await?.unwrap();
|
assert!(!loaded_session.is_expired());
|
||||||
assert_eq!(cloned.id(), loaded_session.id());
|
|
||||||
assert_eq!("value", &loaded_session.get::<String>("key").unwrap());
|
|
||||||
|
|
||||||
assert!(!loaded_session.is_expired());
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
|
assert_eq!(None, store.load_session(cookie_value).await?);
|
||||||
|
|
||||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
Ok(())
|
||||||
assert_eq!(None, store.load_session(cookie_value).await?);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn destroying_a_single_session() -> Result {
|
async fn destroying_a_single_session() -> Result {
|
||||||
let store = test_store();
|
let store = test_store().await;
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
for _ in 0..3i8 {
|
||||||
.enable_all()
|
store.store_session(Session::new()).await?;
|
||||||
.build()
|
}
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
rt.block_on(async {
|
let cookie = store.store_session(Session::new()).await?.unwrap();
|
||||||
for _ in 0..3i8 {
|
assert_eq!(4, store.count().await?);
|
||||||
store.store_session(Session::new()).await?;
|
let session = store.load_session(cookie.clone()).await?.unwrap();
|
||||||
}
|
store.destroy_session(session.clone()).await.unwrap();
|
||||||
|
assert_eq!(None, store.load_session(cookie).await?);
|
||||||
|
assert_eq!(3, store.count().await?);
|
||||||
|
|
||||||
let cookie = store.store_session(Session::new()).await?.unwrap();
|
// // attempting to destroy the session again is not an error
|
||||||
assert_eq!(4, store.count().await?);
|
// assert!(store.destroy_session(session).await.is_ok());
|
||||||
let session = store.load_session(cookie.clone()).await?.unwrap();
|
Ok(())
|
||||||
store.destroy_session(session.clone()).await.unwrap();
|
|
||||||
assert_eq!(None, store.load_session(cookie).await?);
|
|
||||||
assert_eq!(3, store.count().await?);
|
|
||||||
|
|
||||||
// // attempting to destroy the session again is not an error
|
|
||||||
// assert!(store.destroy_session(session).await.is_ok());
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn clearing_the_whole_store() -> Result {
|
async fn clearing_the_whole_store() -> Result {
|
||||||
let store = test_store();
|
let store = test_store().await;
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
for _ in 0..3i8 {
|
||||||
.enable_all()
|
store.store_session(Session::new()).await?;
|
||||||
.build()
|
}
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
rt.block_on(async {
|
assert_eq!(3, store.count().await?);
|
||||||
for _ in 0..3i8 {
|
store.clear_store().await.unwrap();
|
||||||
store.store_session(Session::new()).await?;
|
assert_eq!(0, store.count().await?);
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(3, store.count().await?);
|
Ok(())
|
||||||
store.clear_store().await.unwrap();
|
|
||||||
assert_eq!(0, store.count().await?);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,46 +20,33 @@ pub async fn handle_slash(auth: AuthContext) -> impl IntoResponse {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::future::IntoFuture;
|
|
||||||
|
|
||||||
use axum_test::TestServer;
|
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
use crate::db;
|
use crate::{get_db_pool, test_utils::server_with_pool};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn slash_is_ok() {
|
fn slash_is_ok() {
|
||||||
let pool = db::get_db_pool();
|
let db = get_db_pool();
|
||||||
let secret = [0u8; 64];
|
|
||||||
let rt = Runtime::new().unwrap();
|
let rt = Runtime::new().unwrap();
|
||||||
let app = rt
|
rt.block_on(async {
|
||||||
.block_on(crate::app(pool.clone(), &secret))
|
let server = server_with_pool(&db).await;
|
||||||
.into_make_service();
|
server.get("/").await
|
||||||
|
})
|
||||||
let server = TestServer::new(app).unwrap();
|
.assert_status_ok();
|
||||||
|
|
||||||
rt.block_on(server.get("/").into_future())
|
|
||||||
.assert_status_ok();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn not_found_is_303() {
|
fn not_found_is_303() {
|
||||||
let pool = db::get_db_pool();
|
|
||||||
let secret = [0u8; 64];
|
|
||||||
|
|
||||||
let rt = Runtime::new().unwrap();
|
let rt = Runtime::new().unwrap();
|
||||||
let app = rt
|
|
||||||
.block_on(crate::app(pool.clone(), &secret))
|
|
||||||
.into_make_service();
|
|
||||||
|
|
||||||
let server = TestServer::new(app).unwrap();
|
let db = get_db_pool();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
rt.block_on(
|
rt.block_on(async {
|
||||||
server
|
let server = server_with_pool(&db).await;
|
||||||
.get("/no-actual-route")
|
server.get("/no-actual-route").expect_failure().await
|
||||||
.expect_failure()
|
})
|
||||||
.into_future()
|
|
||||||
)
|
|
||||||
.status_code(),
|
.status_code(),
|
||||||
303
|
303
|
||||||
);
|
);
|
||||||
|
|
|
@ -154,13 +154,18 @@ async fn check_omega_exists(db_pool: &SqlitePool) -> bool {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[tokio::test]
|
#[test]
|
||||||
async fn ensure_omega_user() {
|
fn ensure_omega_user() {
|
||||||
let p = crate::db::get_db_pool();
|
let p = crate::db::get_db_pool();
|
||||||
assert!(!check_omega_exists(&p).await);
|
let rt = Runtime::new().unwrap();
|
||||||
ensure_omega(&p).await;
|
rt.block_on(async {
|
||||||
assert!(check_omega_exists(&p).await);
|
assert!(!check_omega_exists(&p).await);
|
||||||
|
ensure_omega(&p).await;
|
||||||
|
assert!(check_omega_exists(&p).await);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
17
src/lib.rs
17
src/lib.rs
|
@ -1,3 +1,5 @@
|
||||||
|
use axum::routing::IntoMakeService;
|
||||||
|
use sqlx::SqlitePool;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate justerror;
|
extern crate justerror;
|
||||||
|
|
||||||
|
@ -9,10 +11,11 @@ pub mod test_utils;
|
||||||
pub use db::get_db_pool;
|
pub use db::get_db_pool;
|
||||||
pub use db_id::DbId;
|
pub use db_id::DbId;
|
||||||
pub mod import_utils;
|
pub mod import_utils;
|
||||||
|
|
||||||
pub use users::User;
|
pub use users::User;
|
||||||
pub use watches::{ShowKind, Watch, WatchQuest};
|
pub use watches::{ShowKind, Watch, WatchQuest};
|
||||||
|
|
||||||
|
pub type WWRouter = axum::Router<SqlitePool>;
|
||||||
|
|
||||||
// everything else is private to the crate
|
// everything else is private to the crate
|
||||||
mod db;
|
mod db;
|
||||||
mod db_id;
|
mod db_id;
|
||||||
|
@ -32,11 +35,8 @@ use watches::templates::*;
|
||||||
type AuthContext = axum_login::extractors::AuthContext<DbId, User, axum_login::SqliteStore<User>>;
|
type AuthContext = axum_login::extractors::AuthContext<DbId, User, axum_login::SqliteStore<User>>;
|
||||||
|
|
||||||
/// Returns the router to be used as a service or test object, you do you.
|
/// Returns the router to be used as a service or test object, you do you.
|
||||||
pub async fn app(db_pool: sqlx::SqlitePool, session_secret: &[u8]) -> axum::Router {
|
pub async fn app(db_pool: sqlx::SqlitePool, secret: &[u8]) -> IntoMakeService<axum::Router> {
|
||||||
use axum::{middleware, routing::get};
|
use axum::{middleware, routing::get};
|
||||||
let session_layer = db::session_layer(db_pool.clone(), session_secret).await;
|
|
||||||
let auth_layer = db::auth_layer(db_pool.clone(), session_secret).await;
|
|
||||||
|
|
||||||
// don't bother bringing handlers into the whole crate namespace
|
// don't bother bringing handlers into the whole crate namespace
|
||||||
use generic_handlers::{handle_slash, handle_slash_redir};
|
use generic_handlers::{handle_slash, handle_slash_redir};
|
||||||
use login::{get_login, get_logout, post_login, post_logout};
|
use login::{get_login, get_logout, post_login, post_logout};
|
||||||
|
@ -46,6 +46,12 @@ pub async fn app(db_pool: sqlx::SqlitePool, session_secret: &[u8]) -> axum::Rout
|
||||||
post_add_watch_quest,
|
post_add_watch_quest,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (session_layer, auth_layer) = {
|
||||||
|
let session_layer = db::session_layer(db_pool.clone(), secret).await;
|
||||||
|
let auth_layer = db::auth_layer(db_pool.clone(), secret).await;
|
||||||
|
(session_layer, auth_layer)
|
||||||
|
};
|
||||||
|
|
||||||
axum::Router::new()
|
axum::Router::new()
|
||||||
.route("/", get(handle_slash).post(handle_slash))
|
.route("/", get(handle_slash).post(handle_slash))
|
||||||
.route("/signup", get(get_create_user).post(post_create_user))
|
.route("/signup", get(get_create_user).post(post_create_user))
|
||||||
|
@ -69,6 +75,7 @@ pub async fn app(db_pool: sqlx::SqlitePool, session_secret: &[u8]) -> axum::Rout
|
||||||
.layer(auth_layer)
|
.layer(auth_layer)
|
||||||
.layer(session_layer)
|
.layer(session_layer)
|
||||||
.with_state(db_pool)
|
.with_state(db_pool)
|
||||||
|
.into_make_service()
|
||||||
}
|
}
|
||||||
|
|
||||||
//-************************************************************************
|
//-************************************************************************
|
||||||
|
|
24
src/login.rs
24
src/login.rs
|
@ -112,8 +112,9 @@ pub async fn post_logout(mut auth: AuthContext) -> impl IntoResponse {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{
|
use crate::{
|
||||||
|
get_db_pool,
|
||||||
templates::{LoginPage, LogoutPage, LogoutSuccessPage, MainPage},
|
templates::{LoginPage, LogoutPage, LogoutSuccessPage, MainPage},
|
||||||
test_utils::{get_test_user, massage, server, FORM_CONTENT_TYPE},
|
test_utils::{get_test_user, massage, server_with_pool, FORM_CONTENT_TYPE},
|
||||||
};
|
};
|
||||||
|
|
||||||
const LOGIN_FORM: &str = "username=test_user&password=a";
|
const LOGIN_FORM: &str = "username=test_user&password=a";
|
||||||
|
@ -125,8 +126,9 @@ mod test {
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let s = server();
|
let db = get_db_pool();
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
|
let s = server_with_pool(&db).await;
|
||||||
let resp = s.get("/login").await;
|
let resp = s.get("/login").await;
|
||||||
let body = std::str::from_utf8(resp.bytes()).unwrap().to_string();
|
let body = std::str::from_utf8(resp.bytes()).unwrap().to_string();
|
||||||
assert_eq!(body, LoginPage::default().to_string());
|
assert_eq!(body, LoginPage::default().to_string());
|
||||||
|
@ -135,7 +137,6 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn post_login_success() {
|
fn post_login_success() {
|
||||||
let s = server();
|
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
|
@ -143,7 +144,9 @@ mod test {
|
||||||
|
|
||||||
let body = massage(LOGIN_FORM);
|
let body = massage(LOGIN_FORM);
|
||||||
|
|
||||||
|
let db = get_db_pool();
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
|
let s = server_with_pool(&db).await;
|
||||||
let resp = s
|
let resp = s
|
||||||
.post("/login")
|
.post("/login")
|
||||||
.expect_failure()
|
.expect_failure()
|
||||||
|
@ -156,7 +159,6 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn post_login_bad_user() {
|
fn post_login_bad_user() {
|
||||||
let s = server();
|
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
|
@ -165,7 +167,9 @@ mod test {
|
||||||
let form = "username=test_LOSER&password=aaaa";
|
let form = "username=test_LOSER&password=aaaa";
|
||||||
let body = massage(form);
|
let body = massage(form);
|
||||||
|
|
||||||
|
let db = get_db_pool();
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
|
let s = server_with_pool(&db).await;
|
||||||
let resp = s
|
let resp = s
|
||||||
.post("/login")
|
.post("/login")
|
||||||
.expect_success()
|
.expect_success()
|
||||||
|
@ -178,7 +182,6 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn post_login_bad_password() {
|
fn post_login_bad_password() {
|
||||||
let s = server();
|
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
|
@ -187,7 +190,9 @@ mod test {
|
||||||
let form = "username=test_user&password=bbbb";
|
let form = "username=test_user&password=bbbb";
|
||||||
let body = massage(form);
|
let body = massage(form);
|
||||||
|
|
||||||
|
let db = get_db_pool();
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
|
let s = server_with_pool(&db).await;
|
||||||
let resp = s
|
let resp = s
|
||||||
.post("/login")
|
.post("/login")
|
||||||
.expect_success()
|
.expect_success()
|
||||||
|
@ -200,13 +205,14 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_logout() {
|
fn get_logout() {
|
||||||
let s = server();
|
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let db = get_db_pool();
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
|
let s = server_with_pool(&db).await;
|
||||||
let resp = s.get("/logout").await;
|
let resp = s.get("/logout").await;
|
||||||
let body = std::str::from_utf8(resp.bytes()).unwrap().to_string();
|
let body = std::str::from_utf8(resp.bytes()).unwrap().to_string();
|
||||||
assert_eq!(body, LogoutPage.to_string());
|
assert_eq!(body, LogoutPage.to_string());
|
||||||
|
@ -215,13 +221,14 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn post_logout_not_logged_in() {
|
fn post_logout_not_logged_in() {
|
||||||
let s = server();
|
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let db = get_db_pool();
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
|
let s = server_with_pool(&db).await;
|
||||||
let resp = s.post("/logout").await;
|
let resp = s.post("/logout").await;
|
||||||
resp.assert_status_ok();
|
resp.assert_status_ok();
|
||||||
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
||||||
|
@ -232,14 +239,15 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn post_logout_logged_in() {
|
fn post_logout_logged_in() {
|
||||||
let s = server();
|
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// log in and prove it
|
// log in and prove it
|
||||||
|
let db = get_db_pool();
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
|
let s = server_with_pool(&db).await;
|
||||||
let body = massage(LOGIN_FORM);
|
let body = massage(LOGIN_FORM);
|
||||||
let resp = s
|
let resp = s
|
||||||
.post("/login")
|
.post("/login")
|
||||||
|
|
28
src/main.rs
28
src/main.rs
|
@ -1,7 +1,6 @@
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
use rand::{thread_rng, RngCore};
|
use rand::{thread_rng, RngCore};
|
||||||
use sqlx::SqlitePool;
|
|
||||||
use tokio::signal;
|
use tokio::signal;
|
||||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
use what2watch::get_db_pool;
|
use what2watch::get_db_pool;
|
||||||
|
@ -22,11 +21,6 @@ fn main() {
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
rt.block_on(runner(&pool));
|
|
||||||
rt.block_on(pool.close());
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn runner(pool: &SqlitePool) {
|
|
||||||
let secret = {
|
let secret = {
|
||||||
let mut bytes = [0u8; 64];
|
let mut bytes = [0u8; 64];
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
|
@ -34,16 +28,20 @@ async fn runner(pool: &SqlitePool) {
|
||||||
bytes
|
bytes
|
||||||
};
|
};
|
||||||
|
|
||||||
let app = what2watch::app(pool.clone(), &secret).await;
|
let app = rt.block_on(what2watch::app(pool.clone(), &secret));
|
||||||
|
|
||||||
let addr: SocketAddr = ([0, 0, 0, 0], 3000).into();
|
rt.block_on(async {
|
||||||
tracing::debug!("binding to {addr:?}");
|
let addr: SocketAddr = ([0, 0, 0, 0], 3000).into();
|
||||||
|
tracing::debug!("binding to {addr:?}");
|
||||||
|
|
||||||
axum::Server::bind(&addr)
|
axum::Server::bind(&addr)
|
||||||
.serve(app.into_make_service())
|
.serve(app)
|
||||||
.with_graceful_shutdown(shutdown_signal())
|
.with_graceful_shutdown(shutdown_signal())
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
});
|
||||||
|
|
||||||
|
rt.block_on(pool.close());
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn shutdown_signal() {
|
async fn shutdown_signal() {
|
||||||
|
@ -66,5 +64,5 @@ async fn shutdown_signal() {
|
||||||
_ = terminate => {},
|
_ = terminate => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("signal received, starting graceful shutdown");
|
println!(" signal received, starting graceful shutdown");
|
||||||
}
|
}
|
||||||
|
|
417
src/signup.rs
417
src/signup.rs
|
@ -211,62 +211,71 @@ pub(crate) async fn create_user(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::get_db_pool,
|
db::get_db_pool,
|
||||||
templates::{SignupPage, SignupSuccessPage},
|
templates::{SignupPage, SignupSuccessPage},
|
||||||
test_utils::{get_test_user, insert_user, massage, server_with_pool, FORM_CONTENT_TYPE},
|
test_utils::{get_test_user, massage, server_with_pool, FORM_CONTENT_TYPE},
|
||||||
User,
|
User,
|
||||||
};
|
};
|
||||||
|
|
||||||
const GOOD_FORM: &str = "username=test_user&displayname=Test+User&password=aaaa&pw_verify=aaaa";
|
const GOOD_FORM: &str = "username=good_user&displayname=Test+User&password=aaaa&pw_verify=aaaa";
|
||||||
|
|
||||||
#[tokio::test]
|
#[test]
|
||||||
async fn post_create_user() {
|
fn post_create_user() {
|
||||||
let pool = get_db_pool();
|
let pool = get_db_pool();
|
||||||
let server = server_with_pool(&pool).await;
|
let rt = Runtime::new().unwrap();
|
||||||
let body = massage(GOOD_FORM);
|
rt.block_on(async {
|
||||||
|
let server = server_with_pool(&pool).await;
|
||||||
|
let body = massage(GOOD_FORM);
|
||||||
|
|
||||||
let resp = server
|
let resp = server
|
||||||
.post("/signup")
|
.post("/signup")
|
||||||
.expect_failure() // 303 is "failure"
|
.expect_failure() // 303 is "failure"
|
||||||
.bytes(body)
|
.bytes(body)
|
||||||
.content_type(FORM_CONTENT_TYPE)
|
.content_type(FORM_CONTENT_TYPE)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_eq!(StatusCode::SEE_OTHER, resp.status_code());
|
assert_eq!(StatusCode::SEE_OTHER, resp.status_code());
|
||||||
|
|
||||||
// get the new user from the db
|
// get the new user from the db
|
||||||
let user = User::try_get("test_user", &pool).await;
|
let user = User::try_get("good_user", &pool).await;
|
||||||
assert!(user.is_ok());
|
assert!(user.is_ok());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[test]
|
||||||
async fn get_create_user() {
|
fn get_create_user() {
|
||||||
let pool = get_db_pool();
|
let pool = get_db_pool();
|
||||||
let server = server_with_pool(&pool).await;
|
let rt = Runtime::new().unwrap();
|
||||||
|
rt.block_on(async {
|
||||||
|
let server = server_with_pool(&pool).await;
|
||||||
|
|
||||||
let resp = server.get("/signup").await;
|
let resp = server.get("/signup").await;
|
||||||
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
||||||
let expected = SignupPage::default().to_string();
|
let expected = SignupPage::default().to_string();
|
||||||
assert_eq!(&expected, body);
|
assert_eq!(&expected, body);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[test]
|
||||||
async fn handle_signup_success() {
|
fn handle_signup_success() {
|
||||||
let pool = get_db_pool();
|
let pool = get_db_pool();
|
||||||
let server = server_with_pool(&pool).await;
|
let rt = Runtime::new().unwrap();
|
||||||
|
rt.block_on(async {
|
||||||
|
let server = server_with_pool(&pool).await;
|
||||||
|
|
||||||
let user = get_test_user();
|
let user = get_test_user();
|
||||||
insert_user(&user, &pool).await;
|
let id = user.id.0.to_string();
|
||||||
let id = user.id.0.to_string();
|
|
||||||
|
|
||||||
let path = format!("/signup_success/{id}");
|
let path = format!("/signup_success/{id}");
|
||||||
|
|
||||||
let resp = server.get(&path).expect_success().await;
|
let resp = server.get(&path).expect_success().await;
|
||||||
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
||||||
let expected = SignupSuccessPage(user).to_string();
|
let expected = SignupSuccessPage(user).to_string();
|
||||||
assert_eq!(&expected, body);
|
assert_eq!(&expected, body);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//-************************************************************************
|
//-************************************************************************
|
||||||
|
@ -278,223 +287,253 @@ mod test {
|
||||||
|
|
||||||
// various ways to fuck up signup
|
// various ways to fuck up signup
|
||||||
const PASSWORD_MISMATCH_FORM: &str =
|
const PASSWORD_MISMATCH_FORM: &str =
|
||||||
"username=test_user&displayname=Test+User&password=aaaa&pw_verify=bbbb";
|
"username=bad_user&displayname=Test+User&password=aaaa&pw_verify=bbbb";
|
||||||
const PASSWORD_SHORT_FORM: &str =
|
const PASSWORD_SHORT_FORM: &str =
|
||||||
"username=test_user&displayname=Test+User&password=a&pw_verify=a";
|
"username=bad_user&displayname=Test+User&password=a&pw_verify=a";
|
||||||
const PASSWORD_LONG_FORM: &str = "username=test_user&displayname=Test+User&password=sphinx+of+black+qwartz+judge+my+vow+etc+etc+yadd+yadda&pw_verify=sphinx+of+black+qwartz+judge+my+vow+etc+etc+yadd+yadda";
|
const PASSWORD_LONG_FORM: &str = "username=bad_user&displayname=Test+User&password=sphinx+of+black+qwartz+judge+my+vow+etc+etc+yadd+yadda&pw_verify=sphinx+of+black+qwartz+judge+my+vow+etc+etc+yadd+yadda";
|
||||||
const USERNAME_SHORT_FORM: &str =
|
const USERNAME_SHORT_FORM: &str =
|
||||||
"username=&displayname=Test+User&password=aaaa&pw_verify=aaaa";
|
"username=&displayname=Test+User&password=aaaa&pw_verify=aaaa";
|
||||||
const USERNAME_LONG_FORM: &str =
|
const USERNAME_LONG_FORM: &str =
|
||||||
"username=test_user12345678901234567890&displayname=Test+User&password=aaaa&pw_verify=aaaa";
|
"username=bad_user12345678901234567890&displayname=Test+User&password=aaaa&pw_verify=aaaa";
|
||||||
const DISPLAYNAME_LONG_FORM: &str = "username=test_user&displayname=Since+time+immemorial%2C+display+names+have+been+subject+to+a+number+of+conventions%2C+restrictions%2C+usages%2C+and+even+incentives.+Have+we+finally+gone+too+far%3F+In+this+essay%2C+&password=aaaa&pw_verify=aaaa";
|
const DISPLAYNAME_LONG_FORM: &str = "username=test_user&displayname=Since+time+immemorial%2C+display+names+have+been+subject+to+a+number+of+conventions%2C+restrictions%2C+usages%2C+and+even+incentives.+Have+we+finally+gone+too+far%3F+In+this+essay%2C+&password=aaaa&pw_verify=aaaa";
|
||||||
|
|
||||||
#[tokio::test]
|
#[test]
|
||||||
async fn password_mismatch() {
|
fn password_mismatch() {
|
||||||
let pool = get_db_pool();
|
let pool = get_db_pool();
|
||||||
let server = server_with_pool(&pool).await;
|
let rt = Runtime::new().unwrap();
|
||||||
let body = massage(PASSWORD_MISMATCH_FORM);
|
rt.block_on(async {
|
||||||
|
let server = server_with_pool(&pool).await;
|
||||||
|
let body = massage(PASSWORD_MISMATCH_FORM);
|
||||||
|
|
||||||
let resp = server
|
let resp = server
|
||||||
.post("/signup")
|
.post("/signup")
|
||||||
// failure to sign up is not failure to submit the request
|
// failure to sign up is not failure to submit the request
|
||||||
.expect_success()
|
.expect_success()
|
||||||
.bytes(body)
|
.bytes(body)
|
||||||
.content_type(FORM_CONTENT_TYPE)
|
.content_type(FORM_CONTENT_TYPE)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// no user in db
|
// no user in db
|
||||||
let user = User::try_get("test_user", &pool).await;
|
let user = User::try_get("bad_user", &pool).await;
|
||||||
assert!(user.is_err());
|
assert!(user.is_err());
|
||||||
|
|
||||||
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
||||||
let expected = CreateUserError(CreateUserErrorKind::PasswordMismatch).to_string();
|
let expected = CreateUserError(CreateUserErrorKind::PasswordMismatch).to_string();
|
||||||
assert_eq!(&expected, body);
|
assert_eq!(&expected, body);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[test]
|
||||||
async fn password_short() {
|
fn password_short() {
|
||||||
let pool = get_db_pool();
|
let pool = get_db_pool();
|
||||||
let server = server_with_pool(&pool).await;
|
let rt = Runtime::new().unwrap();
|
||||||
let body = massage(PASSWORD_SHORT_FORM);
|
rt.block_on(async {
|
||||||
|
let server = server_with_pool(&pool).await;
|
||||||
|
let body = massage(PASSWORD_SHORT_FORM);
|
||||||
|
|
||||||
let resp = server
|
let resp = server
|
||||||
.post("/signup")
|
.post("/signup")
|
||||||
// failure to sign up is not failure to submit the request
|
// failure to sign up is not failure to submit the request
|
||||||
.expect_success()
|
.expect_success()
|
||||||
.bytes(body)
|
.bytes(body)
|
||||||
.content_type(FORM_CONTENT_TYPE)
|
.content_type(FORM_CONTENT_TYPE)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// no user in db
|
// no user in db
|
||||||
let user = User::try_get("test_user", &pool).await;
|
let user = User::try_get("bad_user", &pool).await;
|
||||||
assert!(user.is_err());
|
assert!(user.is_err());
|
||||||
|
|
||||||
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
||||||
let expected = CreateUserError(CreateUserErrorKind::BadPassword).to_string();
|
let expected = CreateUserError(CreateUserErrorKind::BadPassword).to_string();
|
||||||
assert_eq!(&expected, body);
|
assert_eq!(&expected, body);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[test]
|
||||||
async fn password_long() {
|
fn password_long() {
|
||||||
let pool = get_db_pool();
|
let pool = get_db_pool();
|
||||||
let server = server_with_pool(&pool).await;
|
let rt = Runtime::new().unwrap();
|
||||||
let body = massage(PASSWORD_LONG_FORM);
|
rt.block_on(async {
|
||||||
|
let server = server_with_pool(&pool).await;
|
||||||
|
let body = massage(PASSWORD_LONG_FORM);
|
||||||
|
|
||||||
let resp = server
|
let resp = server
|
||||||
.post("/signup")
|
.post("/signup")
|
||||||
// failure to sign up is not failure to submit the request
|
// failure to sign up is not failure to submit the request
|
||||||
.expect_success()
|
.expect_success()
|
||||||
.bytes(body)
|
.bytes(body)
|
||||||
.content_type(FORM_CONTENT_TYPE)
|
.content_type(FORM_CONTENT_TYPE)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// no user in db
|
// no user in db
|
||||||
let user = User::try_get("test_user", &pool).await;
|
let user = User::try_get("bad_user", &pool).await;
|
||||||
assert!(user.is_err());
|
assert!(user.is_err());
|
||||||
|
|
||||||
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
||||||
let expected = CreateUserError(CreateUserErrorKind::BadPassword).to_string();
|
let expected = CreateUserError(CreateUserErrorKind::BadPassword).to_string();
|
||||||
assert_eq!(&expected, body);
|
assert_eq!(&expected, body);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[test]
|
||||||
async fn multibyte_password_too_short() {
|
fn multibyte_password_too_short() {
|
||||||
let pw = "🤡";
|
let pw = "🤡";
|
||||||
// min length is 4
|
// min length is 4
|
||||||
assert_eq!(pw.len(), 4);
|
assert_eq!(pw.len(), 4);
|
||||||
|
|
||||||
let pool = get_db_pool();
|
let pool = get_db_pool();
|
||||||
let server = server_with_pool(&pool).await;
|
let rt = Runtime::new().unwrap();
|
||||||
let form =
|
|
||||||
format!("username=test_user&displayname=Test+User&password={pw}&pw_verify={pw}");
|
|
||||||
let body = massage(&form);
|
|
||||||
|
|
||||||
let resp = server
|
rt.block_on(async {
|
||||||
.post("/signup")
|
let server = server_with_pool(&pool).await;
|
||||||
// failure to sign up is not failure to submit the request
|
let form = format!(
|
||||||
.expect_success()
|
"username=test_user&displayname=Test+User&password={pw}&pw_verify={pw}"
|
||||||
.bytes(body)
|
);
|
||||||
.content_type(FORM_CONTENT_TYPE)
|
let body = massage(&form);
|
||||||
.await;
|
|
||||||
|
|
||||||
// no user in db
|
let resp = server
|
||||||
let user = User::try_get("test_user", &pool).await;
|
.post("/signup")
|
||||||
assert!(user.is_err());
|
// failure to sign up is not failure to submit the request
|
||||||
|
.expect_success()
|
||||||
|
.bytes(body)
|
||||||
|
.content_type(FORM_CONTENT_TYPE)
|
||||||
|
.await;
|
||||||
|
|
||||||
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
// no user in db
|
||||||
let expected = CreateUserError(CreateUserErrorKind::BadPassword).to_string();
|
let user = User::try_get("bad_user", &pool).await;
|
||||||
assert_eq!(&expected, body);
|
assert!(user.is_err());
|
||||||
|
|
||||||
|
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
||||||
|
let expected = CreateUserError(CreateUserErrorKind::BadPassword).to_string();
|
||||||
|
assert_eq!(&expected, body);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[test]
|
||||||
async fn username_short() {
|
fn username_short() {
|
||||||
let pool = get_db_pool();
|
let pool = get_db_pool();
|
||||||
let server = server_with_pool(&pool).await;
|
let rt = Runtime::new().unwrap();
|
||||||
let body = massage(USERNAME_SHORT_FORM);
|
rt.block_on(async {
|
||||||
|
let server = server_with_pool(&pool).await;
|
||||||
|
let body = massage(USERNAME_SHORT_FORM);
|
||||||
|
|
||||||
let resp = server
|
let resp = server
|
||||||
.post("/signup")
|
.post("/signup")
|
||||||
// failure to sign up is not failure to submit the request
|
// failure to sign up is not failure to submit the request
|
||||||
.expect_success()
|
.expect_success()
|
||||||
.bytes(body)
|
.bytes(body)
|
||||||
.content_type(FORM_CONTENT_TYPE)
|
.content_type(FORM_CONTENT_TYPE)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// no user in db
|
// no user in db
|
||||||
let user = User::try_get("test_user", &pool).await;
|
let user = User::try_get("bad_user", &pool).await;
|
||||||
assert!(user.is_err());
|
assert!(user.is_err());
|
||||||
|
|
||||||
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
||||||
let expected = CreateUserError(CreateUserErrorKind::BadUsername).to_string();
|
let expected = CreateUserError(CreateUserErrorKind::BadUsername).to_string();
|
||||||
assert_eq!(&expected, body);
|
assert_eq!(&expected, body);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[test]
|
||||||
async fn username_long() {
|
fn username_long() {
|
||||||
let pool = get_db_pool();
|
let pool = get_db_pool();
|
||||||
let server = server_with_pool(&pool).await;
|
let rt = Runtime::new().unwrap();
|
||||||
let body = massage(USERNAME_LONG_FORM);
|
rt.block_on(async {
|
||||||
|
let server = server_with_pool(&pool).await;
|
||||||
|
let body = massage(USERNAME_LONG_FORM);
|
||||||
|
|
||||||
let resp = server
|
let resp = server
|
||||||
.post("/signup")
|
.post("/signup")
|
||||||
// failure to sign up is not failure to submit the request
|
// failure to sign up is not failure to submit the request
|
||||||
.expect_success()
|
.expect_success()
|
||||||
.bytes(body)
|
.bytes(body)
|
||||||
.content_type(FORM_CONTENT_TYPE)
|
.content_type(FORM_CONTENT_TYPE)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// no user in db
|
// no user in db
|
||||||
let user = User::try_get("test_user", &pool).await;
|
let user = User::try_get("bad_user", &pool).await;
|
||||||
assert!(user.is_err());
|
assert!(user.is_err());
|
||||||
|
|
||||||
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
||||||
let expected = CreateUserError(CreateUserErrorKind::BadUsername).to_string();
|
let expected = CreateUserError(CreateUserErrorKind::BadUsername).to_string();
|
||||||
assert_eq!(&expected, body);
|
assert_eq!(&expected, body);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[test]
|
||||||
async fn username_duplicate() {
|
fn username_duplicate() {
|
||||||
let pool = get_db_pool();
|
let pool = get_db_pool();
|
||||||
let server = server_with_pool(&pool).await;
|
let rt = Runtime::new().unwrap();
|
||||||
let body = massage(GOOD_FORM);
|
rt.block_on(async {
|
||||||
|
let server = server_with_pool(&pool).await;
|
||||||
|
let body = massage(GOOD_FORM);
|
||||||
|
|
||||||
let _resp = server
|
let _resp = server
|
||||||
.post("/signup")
|
.post("/signup")
|
||||||
.expect_failure() // 303 is "failure"
|
.expect_failure() // 303 is "failure"
|
||||||
.bytes(body.clone())
|
.bytes(body.clone())
|
||||||
.content_type(FORM_CONTENT_TYPE)
|
.content_type(FORM_CONTENT_TYPE)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// get the new user from the db
|
// get the new user from the db
|
||||||
let user = User::try_get("test_user", &pool).await;
|
let user = User::try_get("good_user", &pool).await;
|
||||||
assert!(user.is_ok());
|
assert!(user.is_ok());
|
||||||
|
|
||||||
// now try again
|
// now try again
|
||||||
let resp = server
|
let resp = server
|
||||||
.post("/signup")
|
.post("/signup")
|
||||||
.expect_success()
|
.expect_success()
|
||||||
.bytes(body)
|
.bytes(body)
|
||||||
.content_type(FORM_CONTENT_TYPE)
|
.content_type(FORM_CONTENT_TYPE)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_eq!(resp.status_code(), StatusCode::OK);
|
assert_eq!(resp.status_code(), StatusCode::OK);
|
||||||
let expected = CreateUserError(CreateUserErrorKind::AlreadyExists).to_string();
|
let expected = CreateUserError(CreateUserErrorKind::AlreadyExists).to_string();
|
||||||
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
||||||
assert_eq!(&expected, body);
|
assert_eq!(&expected, body);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[test]
|
||||||
async fn displayname_long() {
|
fn displayname_long() {
|
||||||
let pool = get_db_pool();
|
let pool = get_db_pool();
|
||||||
let server = server_with_pool(&pool).await;
|
let rt = Runtime::new().unwrap();
|
||||||
let body = massage(DISPLAYNAME_LONG_FORM);
|
rt.block_on(async {
|
||||||
|
let server = server_with_pool(&pool).await;
|
||||||
|
let body = massage(DISPLAYNAME_LONG_FORM);
|
||||||
|
|
||||||
let resp = server
|
let resp = server
|
||||||
.post("/signup")
|
.post("/signup")
|
||||||
// failure to sign up is not failure to submit the request
|
// failure to sign up is not failure to submit the request
|
||||||
.expect_success()
|
.expect_success()
|
||||||
.bytes(body)
|
.bytes(body)
|
||||||
.content_type(FORM_CONTENT_TYPE)
|
.content_type(FORM_CONTENT_TYPE)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// no user in db
|
// no user in db
|
||||||
let user = User::try_get("test_user", &pool).await;
|
let user = User::try_get("bad_user", &pool).await;
|
||||||
assert!(user.is_err());
|
assert!(user.is_err());
|
||||||
|
|
||||||
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
let body = std::str::from_utf8(resp.bytes()).unwrap();
|
||||||
let expected = CreateUserError(CreateUserErrorKind::BadDisplayname).to_string();
|
let expected = CreateUserError(CreateUserErrorKind::BadDisplayname).to_string();
|
||||||
assert_eq!(&expected, body);
|
assert_eq!(&expected, body);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[test]
|
||||||
async fn handle_signup_success() {
|
fn handle_signup_success() {
|
||||||
let pool = get_db_pool();
|
let pool = get_db_pool();
|
||||||
let server = server_with_pool(&pool).await;
|
let rt = Runtime::new().unwrap();
|
||||||
|
|
||||||
let path = format!("/signup_success/nope");
|
rt.block_on(async {
|
||||||
|
let server = server_with_pool(&pool).await;
|
||||||
|
|
||||||
let resp = server.get(&path).expect_failure().await;
|
let path = format!("/signup_success/nope");
|
||||||
assert_eq!(resp.status_code(), StatusCode::SEE_OTHER);
|
|
||||||
|
let resp = server.get(&path).expect_failure().await;
|
||||||
|
assert_eq!(resp.status_code(), StatusCode::SEE_OTHER);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,51 +17,19 @@ pub fn get_test_user() -> User {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn server() -> TestServer {
|
|
||||||
let pool = crate::db::get_db_pool();
|
|
||||||
let secret = [0u8; 64];
|
|
||||||
|
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let user = get_test_user();
|
|
||||||
rt.block_on(async {
|
|
||||||
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 users")
|
|
||||||
.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()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn server_with_pool(pool: &SqlitePool) -> TestServer {
|
pub async fn server_with_pool(pool: &SqlitePool) -> TestServer {
|
||||||
let secret = [0u8; 64];
|
let secret = [0u8; 64];
|
||||||
|
|
||||||
let r = sqlx::query("select count(*) from users")
|
let user = get_test_user();
|
||||||
.fetch_one(pool)
|
|
||||||
.await;
|
|
||||||
assert!(r.is_ok());
|
|
||||||
|
|
||||||
let app = crate::app(pool.clone(), &secret).await.into_make_service();
|
insert_user(&user, pool).await;
|
||||||
|
let r: i32 = sqlx::query_scalar("select count(*) from users")
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default();
|
||||||
|
assert!(r == 1);
|
||||||
|
|
||||||
|
let app = crate::app(pool.clone(), &secret).await;
|
||||||
|
|
||||||
let config = TestServerConfig {
|
let config = TestServerConfig {
|
||||||
save_cookies: true,
|
save_cookies: true,
|
||||||
|
|
Loading…
Reference in a new issue