use axum::{ extract::Path, http::{header::{AUTHORIZATION, CONTENT_TYPE}, HeaderMap, Method}, routing::{get, post, put}, Json, Router, }; use tower_http::cors::{Any, CorsLayer}; use crate::{ core::Core, database::UserId, handlers::{ check_password, create_game, create_user, get_user, healthcheck, set_password, wrap_handler, AuthRequest, CreateGameRequest, CreateUserRequest, SetPasswordRequest, }, }; pub fn routes(core: Core) -> Router { Router::new() .route( "/api/v1/health", get({ let core = core.clone(); move || healthcheck(core) }) .layer( CorsLayer::new() .allow_methods([Method::GET]) .allow_origin(Any), ), ) .route( "/api/v1/auth", post({ let core = core.clone(); move |req: Json| wrap_handler(|| check_password(core, req)) }) .layer( CorsLayer::new() .allow_methods([Method::POST]) .allow_headers([CONTENT_TYPE]) .allow_origin(Any), ), ) .route( // By default, just get the self user. "/api/v1/user", get({ let core = core.clone(); move |headers: HeaderMap| wrap_handler(|| get_user(core, headers, None)) }) .layer( CorsLayer::new() .allow_methods([Method::GET]) .allow_headers([AUTHORIZATION]) .allow_origin(Any), ) .put({ let core = core.clone(); move |headers: HeaderMap, req: Json| { let Json(req) = req; wrap_handler(|| create_user(core, headers, req)) } }), ) .route( "/api/v1/user/password", put({ let core = core.clone(); move |headers: HeaderMap, req: Json| { let Json(req) = req; wrap_handler(|| set_password(core, headers, req)) } }), ) .route( "/api/v1/user/:user_id", get({ let core = core.clone(); move |user_id: Path, headers: HeaderMap| { let Path(user_id) = user_id; wrap_handler(|| get_user(core, headers, Some(user_id))) } }), ) .route( "/api/v1/games", put({ let core = core.clone(); move |headers: HeaderMap, req: Json| { let Json(req) = req; wrap_handler(|| create_game(core, headers, req)) } }), ) } #[cfg(test)] mod test { use std::path::PathBuf; use axum::http::StatusCode; use axum_test::TestServer; use cool_asserts::assert_matches; use result_extended::ResultExt; use super::*; use crate::{ asset_db::FsAssets, core::Core, database::{Database, DbConn, GameId, SessionId, UserId}, handlers::CreateGameRequest, types::UserProfile, }; fn setup_without_admin() -> (Core, TestServer) { let memory_db: Option = None; let conn = DbConn::new(memory_db); let core = Core::new(FsAssets::new(PathBuf::from("/home/savanni/Pictures")), conn); let app = routes(core.clone()); let server = TestServer::new(app).unwrap(); (core, server) } async fn setup_admin_enabled() -> (Core, TestServer) { let memory_db: Option = None; let conn = DbConn::new(memory_db); conn.save_user(Some(UserId::from("admin")), "admin", "aoeu", true, true) .await .unwrap(); let core = Core::new(FsAssets::new(PathBuf::from("/home/savanni/Pictures")), conn); let app = routes(core.clone()); let server = TestServer::new(app).unwrap(); (core, server) } async fn setup_with_user() -> (Core, TestServer) { let (core, server) = setup_admin_enabled().await; let response = server .post("/api/v1/auth") .json(&AuthRequest { username: "admin".to_owned(), password: "aoeu".to_owned(), }) .await; response.assert_status_ok(); let session_id: Option = response.json(); let session_id = session_id.unwrap(); let response = server .put("/api/v1/user") .add_header("Authorization", format!("Bearer {}", session_id)) .json(&CreateUserRequest { username: "savanni".to_owned(), }) .await; response.assert_status_ok(); let response = server .put("/api/v1/user") .add_header("Authorization", format!("Bearer {}", session_id)) .json(&CreateUserRequest { username: "shephard".to_owned(), }) .await; response.assert_status_ok(); (core, server) } #[tokio::test] async fn it_returns_a_healthcheck() { let (core, server) = setup_without_admin(); let response = server.get("/api/v1/health").await; response.assert_status_ok(); let b: crate::handlers::HealthCheck = response.json(); assert_eq!(b, crate::handlers::HealthCheck { ok: false }); assert_matches!( core.save_user(Some(UserId::from("admin")), "admin", "aoeu", true, true) .await, ResultExt::Ok(_) ); let response = server.get("/api/v1/health").await; response.assert_status_ok(); let b: crate::handlers::HealthCheck = response.json(); assert_eq!(b, crate::handlers::HealthCheck { ok: true }); } #[tokio::test] async fn it_authenticates_a_user() { let (_core, server) = setup_admin_enabled().await; let response = server .post("/api/v1/auth") .json(&AuthRequest { username: "admin".to_owned(), password: "wrong".to_owned(), }) .await; response.assert_status(StatusCode::UNAUTHORIZED); let response = server .post("/api/v1/auth") .json(&AuthRequest { username: "unknown".to_owned(), password: "wrong".to_owned(), }) .await; response.assert_status(StatusCode::UNAUTHORIZED); let response = server .post("/api/v1/auth") .json(&AuthRequest { username: "admin".to_owned(), password: "aoeu".to_owned(), }) .await; response.assert_status_ok(); let session_id: Option = response.json(); assert!(session_id.is_some()); } #[tokio::test] async fn it_returns_user_profile() { let (_core, server) = setup_admin_enabled().await; let response = server.get("/api/v1/user").await; response.assert_status(StatusCode::UNAUTHORIZED); let response = server .post("/api/v1/auth") .json(&AuthRequest { username: "admin".to_owned(), password: "aoeu".to_owned(), }) .await; response.assert_status_ok(); let session_id: Option = response.json(); let session_id = session_id.unwrap(); let response = server .get("/api/v1/user") .add_header("Authorization", format!("Bearer {}", session_id)) .await; response.assert_status_ok(); let profile: Option = response.json(); let profile = profile.unwrap(); assert_eq!(profile.id, UserId::from("admin")); assert_eq!(profile.name, "admin"); } #[tokio::test] async fn an_admin_can_create_a_user() { // All of the contents of this test are basically required for any test on individual // users, so I moved it all into the setup code. let (_core, _server) = setup_with_user().await; } #[tokio::test] async fn a_user_can_get_any_user_profile() { let (core, server) = setup_with_user().await; let savanni = match core.user_by_username("savanni").await { ResultExt::Ok(Some(savanni)) => savanni, ResultExt::Ok(None) => panic!("user was not initialized"), ResultExt::Err(err) => panic!("{:?}", err), ResultExt::Fatal(err) => panic!("{:?}", err), }; let response = server .post("/api/v1/auth") .json(&AuthRequest { username: "savanni".to_owned(), password: "".to_owned(), }) .await; let session_id: Option = response.json(); let session_id = session_id.unwrap(); let response = server .get(&format!("/api/v1/user/{}", savanni.id)) .add_header("Authorization", format!("Bearer {}", session_id)) .await; response.assert_status_ok(); let profile: Option = response.json(); let profile = profile.unwrap(); assert_eq!(profile.name, "savanni"); let response = server .get("/api/v1/user/admin") .add_header("Authorization", format!("Bearer {}", session_id)) .await; response.assert_status_ok(); let profile: Option = response.json(); let profile = profile.unwrap(); assert_eq!(profile.name, "admin"); } #[tokio::test] async fn a_user_can_change_their_password() { let (_core, server) = setup_with_user().await; let response = server .post("/api/v1/auth") .json(&AuthRequest { username: "savanni".to_owned(), password: "".to_owned(), }) .await; let session_id: Option = response.json(); let session_id = session_id.unwrap(); let response = server .get("/api/v1/user") .add_header("Authorization", format!("Bearer {}", session_id)) .await; let profile = response.json::>().unwrap(); assert_eq!(profile.name, "savanni"); let response = server .put("/api/v1/user/password") .json(&SetPasswordRequest { password_1: "abcdefg".to_owned(), password_2: "abcd".to_owned(), }) .await; response.assert_status(StatusCode::UNAUTHORIZED); let response = server .put("/api/v1/user/password") .add_header("Authorization", format!("Bearer {}", session_id)) .json(&SetPasswordRequest { password_1: "abcdefg".to_owned(), password_2: "abcd".to_owned(), }) .await; response.assert_status(StatusCode::BAD_REQUEST); let response = server .put("/api/v1/user/password") .add_header("Authorization", format!("Bearer {}", session_id)) .json(&SetPasswordRequest { password_1: "abcdefg".to_owned(), password_2: "abcdefg".to_owned(), }) .await; response.assert_status(StatusCode::OK); } #[tokio::test] async fn a_user_can_create_a_game() { let (_core, server) = setup_with_user().await; let response = server .post("/api/v1/auth") .json(&AuthRequest { username: "savanni".to_owned(), password: "".to_owned(), }) .await; let session_id = response.json::>().unwrap(); let response = server .put("/api/v1/games") .add_header("Authorization", format!("Bearer {}", session_id)) .json(&CreateGameRequest { game_type: "Candela".to_owned(), game_name: "Circle of the Winter Solstice".to_owned(), }) .await; let _game_id = response.json::>().unwrap(); } #[ignore] #[tokio::test] async fn gms_can_invite_others_into_a_game() { unimplemented!(); } }