refactor: rewrite todos and test

This commit is contained in:
Max Hohlfeld 2024-12-16 23:27:00 +01:00
parent 31b09a1c76
commit 0844cc5a17
6 changed files with 258 additions and 85 deletions

17
Cargo.lock generated
View File

@ -691,6 +691,7 @@ dependencies = [
"change-detection",
"chrono",
"dotenv",
"fake",
"futures-util",
"idna 1.0.2",
"insta",
@ -1079,6 +1080,12 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "deunicode"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00"
[[package]]
name = "digest"
version = "0.10.7"
@ -1208,6 +1215,16 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "fake"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "661cb0601b5f4050d1e65452c5b0ea555c0b3e88fb5ed7855906adc6c42523ef"
dependencies = [
"deunicode",
"rand",
]
[[package]]
name = "fancy-regex"
version = "0.13.0"

View File

@ -43,3 +43,4 @@ change-detection = "1.2.0"
[dev-dependencies]
insta = "1.41.1"
fake = "3.0.1"

View File

@ -1,5 +1,5 @@
use rinja::Template;
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use crate::models::{Area, Location, Role, User};
use crate::filters;
@ -19,7 +19,7 @@ pub struct LocationTemplate {
location: Option<Location>,
}
#[derive(Deserialize)]
#[derive(Deserialize, Serialize)]
pub struct LocationForm {
name: String,
area: Option<i32>,

View File

@ -1,11 +1,16 @@
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use brass_macros::db_test;
use sqlx::PgPool;
use crate::{
endpoints::location::LocationForm,
models::{Location, Role, User}, utils::ApplicationError,
models::{Location, Role, User},
utils::ApplicationError,
};
#[cfg(test)]
use crate::utils::test_helper::{test_post, DbTestContext, RequestConfig, StatusCode};
#[actix_web::post("/locations/new")]
pub async fn post(
user: web::ReqData<User>,
@ -22,17 +27,66 @@ pub async fn post(
area_id = form.area.unwrap();
}
// TODO: rework
match Location::create(pool.get_ref(), &form.name, area_id).await {
Ok(_) => {
return Ok(HttpResponse::Found()
.insert_header((LOCATION, "/locations"))
.insert_header(("HX-LOCATION", "/locations"))
.finish())
}
Err(err) => {
println!("{}", err);
return Ok(HttpResponse::InternalServerError().finish());
}
}
Location::create(pool.get_ref(), &form.name, area_id).await?;
return Ok(HttpResponse::Found()
.insert_header((LOCATION, "/locations"))
.insert_header(("HX-LOCATION", "/locations"))
.finish());
}
#[db_test]
async fn works_when_user_is_admin(context: &DbTestContext) {
let app = context.app().await;
let config = RequestConfig {
uri: "/locations/new".to_string(),
role: Role::Admin,
function: crate::models::Function::Posten,
user_area: 1,
};
let form = LocationForm {
name: "Hauptbahnhof".to_string(),
area: Some(1),
};
let response = test_post(&context.db_pool, app, &config, form).await;
assert_eq!(StatusCode::FOUND, response.status());
assert_eq!(
"Hauptbahnhof".to_string(),
Location::read_by_id(&context.db_pool, 1)
.await
.unwrap()
.unwrap()
.name
);
}
#[db_test]
async fn uses_area_id_of_area_manager(context: &DbTestContext) {
let app = context.app().await;
let config = RequestConfig {
uri: "/locations/new".to_string(),
role: Role::AreaManager,
function: crate::models::Function::Posten,
user_area: 1,
};
let form = LocationForm {
name: "Hauptbahnhof".to_string(),
area: None,
};
let response = test_post(&context.db_pool, app, &config, form).await;
assert_eq!(StatusCode::FOUND, response.status());
assert_eq!(
"Hauptbahnhof".to_string(),
Location::read_by_id(&context.db_pool, 1)
.await
.unwrap()
.unwrap()
.name
);
}

View File

@ -1,14 +1,24 @@
use actix_identity::Identity;
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use serde::Deserialize;
use brass_macros::db_test;
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use crate::{
endpoints::IdPath,
models::{Function, Role, User},
utils::ApplicationError,
};
#[derive(Deserialize)]
#[cfg(test)]
use crate::utils::test_helper::{test_post, DbTestContext, RequestConfig, StatusCode};
#[cfg(test)]
use fake::{
faker::{internet::raw::SafeEmail, name::raw::Name},
locales::EN,
Fake,
};
#[derive(Deserialize, Serialize)]
pub struct EditUserForm {
email: String,
name: String,
@ -19,77 +29,167 @@ pub struct EditUserForm {
#[actix_web::post("/users/edit/{id}")]
pub async fn post_edit(
user: Identity,
user: web::ReqData<User>,
pool: web::Data<PgPool>,
path: web::Path<IdPath>,
form: web::Form<EditUserForm>,
) -> impl Responder {
// TODO: rewrite
let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap())
.await
.unwrap().unwrap();
if current_user.role != Role::AreaManager && current_user.role != Role::Admin {
return HttpResponse::Unauthorized().finish();
) -> Result<impl Responder, ApplicationError> {
if user.role != Role::AreaManager && user.role != Role::Admin {
return Err(ApplicationError::Unauthorized);
}
if let Some(user_in_db) = User::read_by_id(pool.get_ref(), path.id).await.unwrap() {
if current_user.role == Role::AreaManager && current_user.area_id != user_in_db.area_id {
return HttpResponse::Unauthorized().finish();
}
let mut changed = false;
let email = if user_in_db.email != form.email {
changed = true;
Some(form.email.as_str())
} else {
None
};
let name = if user_in_db.name != form.name {
changed = true;
Some(form.name.as_str())
} else {
None
};
let role = if user_in_db.role as u8 != form.role {
if let Ok(r) = Role::try_from(form.role) {
changed = true;
Some(r)
} else {
None
}
} else {
None
};
let function = if user_in_db.function as u8 != form.function {
if let Ok(f) = Function::try_from(form.function) {
changed = true;
Some(f)
} else {
None
}
} else {
None
};
let area = if current_user.role == Role::Admin && form.area.is_some() && user_in_db.area_id != form.area.unwrap() {
changed = true;
Some(form.area.unwrap())
} else {
None
};
if changed {
match User::update(pool.get_ref(), path.id, email, name, None, None, role, function, area, None, None).await {
Ok(_) => return HttpResponse::Found().insert_header((LOCATION, "/users")).finish(),
Err(err) => println!("{}", err)
}
}
if user.id == path.id {
return Ok(HttpResponse::BadRequest().finish());
}
return HttpResponse::BadRequest().body("Fehler beim Bearbeiten des Nutzers");
let Some(user_in_db) = User::read_by_id(pool.get_ref(), path.id).await? else {
return Ok(HttpResponse::NotFound().finish());
};
if user.role == Role::AreaManager && user.area_id != user_in_db.area_id {
return Err(ApplicationError::Unauthorized);
}
let mut changed = false;
let email = if user_in_db.email != form.email {
changed = true;
Some(form.email.as_str())
} else {
None
};
let name = if user_in_db.name != form.name {
changed = true;
Some(form.name.as_str())
} else {
None
};
let role = if user_in_db.role as u8 != form.role {
if let Ok(r) = Role::try_from(form.role) {
changed = true;
Some(r)
} else {
None
}
} else {
None
};
let function = if user_in_db.function as u8 != form.function {
if let Ok(f) = Function::try_from(form.function) {
changed = true;
Some(f)
} else {
None
}
} else {
None
};
let area = if user.role == Role::Admin
&& form.area.is_some()
&& user_in_db.area_id != form.area.unwrap()
{
changed = true;
Some(form.area.unwrap())
} else {
None
};
if changed {
User::update(
pool.get_ref(),
path.id,
email,
name,
None,
None,
role,
function,
area,
None,
None,
)
.await?;
}
Ok(HttpResponse::Found()
.insert_header((LOCATION, "/users"))
.insert_header(("HX-LOCATION", "/users"))
.finish())
}
#[db_test]
async fn works_when_user_is_admin(context: &DbTestContext) {
User::create(
&context.db_pool,
&Name(EN).fake::<String>(),
&SafeEmail(EN).fake::<String>(),
Role::Staff,
Function::Posten,
1,
)
.await
.unwrap();
crate::models::Area::create(&context.db_pool, "Süd")
.await
.unwrap();
let app = context.app().await;
let config = RequestConfig {
uri: "/users/edit/1".to_string(),
role: Role::Admin,
function: crate::models::Function::Posten,
user_area: 1,
};
let new_name: String = Name(EN).fake();
let new_mail: String = SafeEmail(EN).fake();
let form = EditUserForm {
name: new_name.clone(),
email: new_mail.clone(),
role: Role::AreaManager as u8,
function: Function::Fuehrungsassistent as u8,
area: Some(2),
};
let response = test_post(&context.db_pool, app, &config, form).await;
assert_eq!(StatusCode::FOUND, response.status());
let updated_user = User::read_by_id(&context.db_pool, 1)
.await
.unwrap()
.unwrap();
assert_eq!(new_name, updated_user.name);
assert_eq!(new_mail, updated_user.email);
assert_eq!(Role::AreaManager, updated_user.role);
assert_eq!(Function::Fuehrungsassistent, updated_user.function);
assert_eq!(2, updated_user.area_id);
}
#[db_test]
async fn cant_edit_oneself(context: &DbTestContext) {
let app = context.app().await;
let config = RequestConfig {
uri: "/users/edit/1".to_string(),
role: Role::Admin,
function: crate::models::Function::Posten,
user_area: 1,
};
let form = EditUserForm {
name: "".to_string(),
email: "".to_string(),
role: Role::AreaManager as u8,
function: Function::Fuehrungsassistent as u8,
area: Some(1),
};
let response = test_post(&context.db_pool, app, &config, form).await;
assert_eq!(StatusCode::BAD_REQUEST, response.status());
}

View File

@ -28,6 +28,7 @@ impl TryFrom<u8> for Function {
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
1 => Ok(Function::Posten),
5 => Ok(Function::Fuehrungsassistent),
10 => Ok(Function::Wachhabender),
_ => Err(ApplicationError::UnsupportedEnumValue {
value: value.to_string(),