monorepo/visions/server/src/routes.rs

365 lines
12 KiB
Rust
Raw Normal View History

2024-12-31 17:56:05 +00:00
use axum::{
extract::Path,
http::HeaderMap,
routing::{get, post, put},
2024-12-31 17:56:05 +00:00
Json, Router,
};
use crate::{
core::Core,
database::UserId,
2024-12-31 21:34:59 +00:00
handlers::{
check_password, create_game, create_user, get_user, healthcheck, set_password, AuthRequest,
CreateGameRequest, CreateUserRequest, SetPasswordRequest,
2024-12-31 21:34:59 +00:00
},
};
pub fn routes(core: Core) -> Router {
Router::new()
.route(
"/api/v1/health",
get({
let core = core.clone();
move || healthcheck(core)
}),
)
.route(
"/api/v1/auth",
post({
let core = core.clone();
move |req: Json<AuthRequest>| check_password(core, req)
}),
)
.route(
// By default, just get the self user.
"/api/v1/user",
get({
let core = core.clone();
move |headers: HeaderMap| get_user(core, headers, None)
})
2024-12-31 21:34:59 +00:00
.put({
let core = core.clone();
move |headers: HeaderMap, req: Json<CreateUserRequest>| {
let Json(req) = req;
create_user(core, headers, req)
}
}),
)
.route(
"/api/v1/user/password",
put({
let core = core.clone();
move |headers: HeaderMap, req: Json<SetPasswordRequest>| {
let Json(req) = req;
set_password(core, headers, req)
}
}),
)
.route(
"/api/v1/user/:user_id",
get({
let core = core.clone();
move |user_id: Path<UserId>, headers: HeaderMap| {
let Path(user_id) = user_id;
get_user(core, headers, Some(user_id))
}
}),
)
2025-01-02 16:57:17 +00:00
.route(
"/api/v1/games",
put({
let core = core.clone();
move |headers: HeaderMap, req: Json<CreateGameRequest>| {
let Json(req) = req;
create_game(core, headers, req)
}
}),
)
}
#[cfg(test)]
mod test {
use std::path::PathBuf;
2024-12-31 17:56:05 +00:00
use axum::http::StatusCode;
use axum_test::TestServer;
2024-12-31 17:56:05 +00:00
use cool_asserts::assert_matches;
use result_extended::ResultExt;
use super::*;
2024-12-31 17:56:05 +00:00
use crate::{
asset_db::FsAssets,
core::Core,
2025-01-02 16:57:17 +00:00
database::{Database, DbConn, GameId, SessionId, UserId},
handlers::CreateGameRequest,
types::UserProfile,
2024-12-31 17:56:05 +00:00
};
2024-12-31 17:56:05 +00:00
fn setup_without_admin() -> (Core, TestServer) {
let memory_db: Option<PathBuf> = 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)
}
2024-12-31 17:56:05 +00:00
async fn setup_admin_enabled() -> (Core, TestServer) {
let memory_db: Option<PathBuf> = 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<SessionId> = 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;
2025-01-02 16:57:17 +00:00
response.assert_status_ok();
2025-01-02 16:57:17 +00:00
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() {
2024-12-31 17:56:05 +00:00
let (core, server) = setup_without_admin();
let response = server.get("/api/v1/health").await;
response.assert_status_ok();
2024-12-31 17:56:05 +00:00
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(_)
);
2024-12-31 17:56:05 +00:00
let response = server.get("/api/v1/health").await;
response.assert_status_ok();
let b: crate::handlers::HealthCheck = response.json();
2024-12-31 17:56:05 +00:00
assert_eq!(b, crate::handlers::HealthCheck { ok: true });
}
2024-12-31 17:56:05 +00:00
#[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<SessionId> = 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<SessionId> = 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<UserProfile> = 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<SessionId> = 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<UserProfile> = 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<UserProfile> = 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<SessionId> = 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::<Option<UserProfile>>().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
2025-01-02 16:57:17 +00:00
.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);
}
2024-12-31 21:34:59 +00:00
#[tokio::test]
async fn a_user_can_create_a_game() {
2025-01-02 16:57:17 +00:00
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::<Option<SessionId>>().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::<Option<GameId>>().unwrap();
2024-12-31 21:34:59 +00:00
}
#[ignore]
#[tokio::test]
async fn gms_can_invite_others_into_a_game() {
unimplemented!();
}
}