From 2abeeb20dfc81f0d40f563c04bda017438846413 Mon Sep 17 00:00:00 2001 From: Max Hohlfeld Date: Mon, 30 Jun 2025 13:17:27 +0200 Subject: [PATCH] test: new assignment --- db/src/models/assignment_changeset.rs | 4 +- ...er_response_produces_updated_template.snap | 67 +++++++ web/src/endpoints/area/post_edit.rs | 6 +- web/src/endpoints/area/post_new.rs | 4 +- web/src/endpoints/assignment/post_new.rs | 180 +++++++++++++++++- web/src/endpoints/location/post_new.rs | 4 +- web/src/endpoints/user/post_edit.rs | 13 +- web/src/endpoints/vehicle/post_edit.rs | 6 +- web/src/endpoints/vehicle/post_new.rs | 4 +- web/src/utils/test_helper/mod.rs | 41 ++++ web/src/utils/test_helper/test_requests.rs | 2 +- 11 files changed, 310 insertions(+), 21 deletions(-) create mode 100644 web/snapshots/brass_web__endpoints__assignment__post_new__tests__inner_response_produces_updated_template.snap diff --git a/db/src/models/assignment_changeset.rs b/db/src/models/assignment_changeset.rs index c593e5cc..37eaf071 100644 --- a/db/src/models/assignment_changeset.rs +++ b/db/src/models/assignment_changeset.rs @@ -166,8 +166,8 @@ fn user_is_admin_or_area_manager_of_event_area( let user_is_area_manager_event_area = user.role == Role::AreaManager && user.area_id == event.location.as_ref().unwrap().area_id; - if !user_is_admin || !user_is_area_manager_event_area { - return Err(AsyncValidateError::new("")); + if !user_is_admin && !user_is_area_manager_event_area { + return Err(AsyncValidateError::new("TODO: admin or areamanager")); } Ok(()) diff --git a/web/snapshots/brass_web__endpoints__assignment__post_new__tests__inner_response_produces_updated_template.snap b/web/snapshots/brass_web__endpoints__assignment__post_new__tests__inner_response_produces_updated_template.snap new file mode 100644 index 00000000..60c58d4d --- /dev/null +++ b/web/snapshots/brass_web__endpoints__assignment__post_new__tests__inner_response_produces_updated_template.snap @@ -0,0 +1,67 @@ +--- +source: web/src/endpoints/assignment/post_new.rs +expression: body +snapshot_kind: text +--- + + + + + + + + + + + + + + + + + + + + + + + + +
NameFunktionZeitraumKommentarPlanung
Max Mustermann +
Posten
+
+ 10:00 bis 10.01.2025 20:00 + + + + + +
diff --git a/web/src/endpoints/area/post_edit.rs b/web/src/endpoints/area/post_edit.rs index d10e9386..1edf0845 100644 --- a/web/src/endpoints/area/post_edit.rs +++ b/web/src/endpoints/area/post_edit.rs @@ -55,7 +55,7 @@ mod tests { name: "Neuer Name".to_string(), }; - let response = test_post(&context.db_pool, app, &config, request).await; + let response = test_post(&context.db_pool, app, &config, Some(request)).await; assert_eq!(StatusCode::FOUND, response.status()); @@ -82,7 +82,7 @@ mod tests { name: "Neuer Name".to_string(), }; - let response = test_post(&context.db_pool, app, &config, request).await; + let response = test_post(&context.db_pool, app, &config, Some(request)).await; assert_eq!(StatusCode::UNAUTHORIZED, response.status()); } @@ -102,7 +102,7 @@ mod tests { name: "Neuer Name".to_string(), }; - let response = test_post(&context.db_pool, app, &config, request).await; + let response = test_post(&context.db_pool, app, &config, Some(request)).await; assert_eq!(StatusCode::NOT_FOUND, response.status()); } diff --git a/web/src/endpoints/area/post_new.rs b/web/src/endpoints/area/post_new.rs index cd8ebef1..b3db39cd 100644 --- a/web/src/endpoints/area/post_new.rs +++ b/web/src/endpoints/area/post_new.rs @@ -48,7 +48,7 @@ mod tests { name: "Neuer Name".to_string(), }; - let response = test_post(&context.db_pool, app, &config, request).await; + let response = test_post(&context.db_pool, app, &config, Some(request)).await; assert_eq!(StatusCode::FOUND, response.status()); @@ -75,7 +75,7 @@ mod tests { name: "Neuer Name".to_string(), }; - let response = test_post(&context.db_pool, app, &config, request).await; + let response = test_post(&context.db_pool, app, &config, Some(request)).await; assert_eq!(StatusCode::UNAUTHORIZED, response.status()); } diff --git a/web/src/endpoints/assignment/post_new.rs b/web/src/endpoints/assignment/post_new.rs index d6b094c5..2ae1b4dc 100644 --- a/web/src/endpoints/assignment/post_new.rs +++ b/web/src/endpoints/assignment/post_new.rs @@ -50,7 +50,7 @@ pub async fn post( }; if let Err(e) = changeset.validate_with_context(&context).await { - return Ok(HttpResponse::BadRequest().body(e.to_string())); + return Ok(HttpResponse::UnprocessableEntity().body(e.to_string())); }; Assignment::create(pool.get_ref(), event.id, query.availability, changeset).await?; @@ -73,3 +73,181 @@ pub async fn post( Ok(template.to_response()?) } + +#[cfg(test)] +mod tests { + use actix_http::StatusCode; + use brass_db::models::{ + Area, Availability, AvailabilityChangeset, Event, EventChangeset, Location, Role, User, + UserChangeset, + }; + use brass_macros::db_test; + use chrono::NaiveDateTime; + use fake::{Fake, Faker}; + use sqlx::PgPool; + + use crate::utils::test_helper::{ + assert_snapshot, test_post, DbTestContext, NaiveDateTimeExt, RequestConfig, + ServiceResponseExt, + }; + + async fn arrange(pool: &PgPool) { + Location::create(pool, &Faker.fake::(), 1) + .await + .unwrap(); + + let mut user_changeset: UserChangeset = Faker.fake(); + user_changeset.name = String::from("Max Mustermann"); + + User::create(pool, user_changeset).await.unwrap(); + } + + async fn arrange_event(pool: &PgPool, start: NaiveDateTime, end: NaiveDateTime, location: i32) { + let mut changeset: EventChangeset = EventChangeset::create_for_test(start, end); + changeset.location_id = location; + + Event::create(pool, changeset).await.unwrap(); + } + + async fn arrange_availability(pool: &PgPool, start: NaiveDateTime, end: NaiveDateTime) { + Availability::create( + pool, + 1, + AvailabilityChangeset { + time: (start, end), + comment: None, + }, + ) + .await + .unwrap(); + } + + #[db_test] + fn response_produces_updated_template(context: &DbTestContext) { + let app = context.app().await; + + let start = NaiveDateTime::from_ymd_and_hms(2025, 01, 10, 10, 0, 0).unwrap(); + let end = NaiveDateTime::from_ymd_and_hms(2025, 01, 10, 20, 0, 0).unwrap(); + + arrange(&context.db_pool).await; + arrange_event(&context.db_pool, start, end, 1).await; + arrange_availability(&context.db_pool, start, end).await; + + let config = RequestConfig::new("/assignments/new?availability=1&function=1&event=1") + .with_role(Role::Admin); + + let response = test_post::<_, _, String>(&context.db_pool, app, &config, None).await; + let (status, body) = response.into_status_and_body().await; + + assert_eq!(StatusCode::OK, status, "{body}"); + + assert_snapshot!(body); + } + + #[db_test] + fn fails_when_availability_does_not_exist(context: &DbTestContext) { + let app = context.app().await; + + let start = NaiveDateTime::from_ymd_and_hms(2025, 01, 10, 10, 0, 0).unwrap(); + let end = NaiveDateTime::from_ymd_and_hms(2025, 01, 10, 20, 0, 0).unwrap(); + arrange(&context.db_pool).await; + arrange_event(&context.db_pool, start, end, 1).await; + + let config = RequestConfig::new("/assignments/new?availability=1&function=1&event=1") + .with_role(Role::Admin); + + let response = test_post::<_, _, String>(&context.db_pool, app, &config, None).await; + + assert_eq!(StatusCode::UNPROCESSABLE_ENTITY, response.status()); + } + + #[db_test] + fn fails_when_event_does_not_exist(context: &DbTestContext) { + let app = context.app().await; + + let start = NaiveDateTime::from_ymd_and_hms(2025, 01, 10, 10, 0, 0).unwrap(); + let end = NaiveDateTime::from_ymd_and_hms(2025, 01, 10, 20, 0, 0).unwrap(); + arrange(&context.db_pool).await; + arrange_availability(&context.db_pool, start, end).await; + + let config = RequestConfig::new("/assignments/new?availability=1&function=1&event=1") + .with_role(Role::Admin); + + let response = test_post::<_, _, String>(&context.db_pool, app, &config, None).await; + + assert_eq!(StatusCode::NOT_FOUND, response.status()); + } + + #[db_test] + fn fails_when_area_manager_is_different_area_from_event(context: &DbTestContext) { + let app = context.app().await; + + let start = NaiveDateTime::from_ymd_and_hms(2025, 01, 10, 10, 0, 0).unwrap(); + let end = NaiveDateTime::from_ymd_and_hms(2025, 01, 10, 20, 0, 0).unwrap(); + + arrange(&context.db_pool).await; + arrange_event(&context.db_pool, start, end, 1).await; + arrange_availability(&context.db_pool, start, end).await; + Area::create(&context.db_pool, &Faker.fake::()) + .await + .unwrap(); + + let config = RequestConfig::new("/assignments/new?availability=1&function=1&event=1") + .with_role(Role::AreaManager) + .with_user_area(2); + + let response = test_post::<_, _, String>(&context.db_pool, app, &config, None).await; + + assert_eq!(StatusCode::UNPROCESSABLE_ENTITY, response.status()); + } + + #[db_test] + fn fails_when_availability_user_not_in_event_area(context: &DbTestContext) { + let app = context.app().await; + + let start = NaiveDateTime::from_ymd_and_hms(2025, 01, 10, 10, 0, 0).unwrap(); + let end = NaiveDateTime::from_ymd_and_hms(2025, 01, 10, 20, 0, 0).unwrap(); + + arrange(&context.db_pool).await; + Area::create(&context.db_pool, &Faker.fake::()) + .await + .unwrap(); + Location::create(&context.db_pool, &Faker.fake::(), 2) + .await + .unwrap(); + arrange_event(&context.db_pool, start, end, 2).await; + arrange_availability(&context.db_pool, start, end).await; + + let config = RequestConfig::new("/assignments/new?availability=1&function=1&event=1") + .with_role(Role::Admin); + + let response = test_post::<_, _, String>(&context.db_pool, app, &config, None).await; + + assert_eq!(StatusCode::UNPROCESSABLE_ENTITY, response.status()); + } + + #[db_test] + fn fails_assignment_time_doesnt_fit_into_availability_time(context: &DbTestContext) { + assert!(false) + } + + #[db_test] + fn fails_when_end_time_lies_before_start_time(context: &DbTestContext) { + assert!(false) + } + + #[db_test] + fn fails_when_availability_time_already_assigned(context: &DbTestContext) { + assert!(false) + } + + #[db_test] + fn fails_when_availability_user_does_not_have_function(context: &DbTestContext) { + assert!(false) + } + + #[db_test] + fn fails_when_event_already_has_enough_assignments_for_function(context: &DbTestContext) { + assert!(false) + } +} diff --git a/web/src/endpoints/location/post_new.rs b/web/src/endpoints/location/post_new.rs index d69518c6..f9b74f0c 100644 --- a/web/src/endpoints/location/post_new.rs +++ b/web/src/endpoints/location/post_new.rs @@ -52,7 +52,7 @@ mod tests { area: Some(1), }; - let response = test_post(&context.db_pool, app, &config, form).await; + let response = test_post(&context.db_pool, app, &config, Some(form)).await; assert_eq!(StatusCode::FOUND, response.status()); assert_eq!( @@ -80,7 +80,7 @@ mod tests { area: None, }; - let response = test_post(&context.db_pool, app, &config, form).await; + let response = test_post(&context.db_pool, app, &config, Some(form)).await; assert_eq!(StatusCode::FOUND, response.status()); assert_eq!( diff --git a/web/src/endpoints/user/post_edit.rs b/web/src/endpoints/user/post_edit.rs index 771952e0..898e16da 100644 --- a/web/src/endpoints/user/post_edit.rs +++ b/web/src/endpoints/user/post_edit.rs @@ -1,5 +1,8 @@ use actix_web::{http::header::LOCATION, web, HttpResponse, Responder}; -use brass_db::{models::{Function, Role, User, UserChangeset}, validation::{AsyncValidate, DbContext}}; +use brass_db::{ + models::{Function, Role, User, UserChangeset}, + validation::{AsyncValidate, DbContext}, +}; use sqlx::PgPool; use crate::{ @@ -113,7 +116,7 @@ mod tests { area: Some(2), }; - let response = test_post(&context.db_pool, app, &config, form).await; + let response = test_post(&context.db_pool, app, &config, Some(form)).await; assert_eq!(StatusCode::FOUND, response.status()); let updated_user = User::read_by_id(&context.db_pool, 1) @@ -148,7 +151,7 @@ mod tests { area: Some(1), }; - let response = test_post(&context.db_pool, app, &config, form).await; + let response = test_post(&context.db_pool, app, &config, Some(form)).await; assert_eq!(StatusCode::BAD_REQUEST, response.status()); } @@ -173,7 +176,7 @@ mod tests { area: Some(2), }; - let response = test_post(&context.db_pool, app, &config, form).await; + let response = test_post(&context.db_pool, app, &config, Some(form)).await; assert_eq!(StatusCode::FOUND, response.status()); let updated_user = User::read_by_id(&context.db_pool, 1) @@ -211,7 +214,7 @@ mod tests { area: Some(2), }; - let response = test_post(&context.db_pool, app, &config, form).await; + let response = test_post(&context.db_pool, app, &config, Some(form)).await; assert_eq!(StatusCode::UNPROCESSABLE_ENTITY, response.status()); } } diff --git a/web/src/endpoints/vehicle/post_edit.rs b/web/src/endpoints/vehicle/post_edit.rs index 3a746058..3bfca101 100644 --- a/web/src/endpoints/vehicle/post_edit.rs +++ b/web/src/endpoints/vehicle/post_edit.rs @@ -73,7 +73,7 @@ mod tests { radio_call_name: "11.49.2".to_string(), }; - let response = test_post(&context.db_pool, app, &config, request).await; + let response = test_post(&context.db_pool, app, &config, Some(request)).await; assert_eq!(StatusCode::FOUND, response.status()); @@ -93,7 +93,7 @@ mod tests { radio_call_name: "11.49.2".to_string(), }; - let response = test_post(&context.db_pool, app, &config, request).await; + let response = test_post(&context.db_pool, app, &config, Some(request)).await; assert_eq!(StatusCode::UNAUTHORIZED, response.status()); } @@ -109,7 +109,7 @@ mod tests { radio_call_name: "11.49.2".to_string(), }; - let response = test_post(&context.db_pool, app, &config, request).await; + let response = test_post(&context.db_pool, app, &config, Some(request)).await; assert_eq!(StatusCode::NOT_FOUND, response.status()); } diff --git a/web/src/endpoints/vehicle/post_new.rs b/web/src/endpoints/vehicle/post_new.rs index b3e39380..b5015468 100644 --- a/web/src/endpoints/vehicle/post_new.rs +++ b/web/src/endpoints/vehicle/post_new.rs @@ -53,7 +53,7 @@ mod tests { radio_call_name: "11.49.1".to_string(), }; - let response = test_post(&context.db_pool, app, &config, request).await; + let response = test_post(&context.db_pool, app, &config, Some(request)).await; assert_eq!(StatusCode::FOUND, response.status()); @@ -73,7 +73,7 @@ mod tests { radio_call_name: "11.49.2".to_string(), }; - let response = test_post(&context.db_pool, app, &config, request).await; + let response = test_post(&context.db_pool, app, &config, Some(request)).await; assert_eq!(StatusCode::UNAUTHORIZED, response.status()); } diff --git a/web/src/utils/test_helper/mod.rs b/web/src/utils/test_helper/mod.rs index 27b44174..a8b1e578 100644 --- a/web/src/utils/test_helper/mod.rs +++ b/web/src/utils/test_helper/mod.rs @@ -1,5 +1,9 @@ mod test_context; mod test_requests; +use actix_web::body::MessageBody; +use actix_web::dev::ServiceResponse; +use actix_web::test; +use chrono::{NaiveDate, NaiveDateTime}; pub use test_context::{setup, teardown, DbTestContext}; pub use test_requests::RequestConfig; pub use test_requests::{read_body, test_delete, test_get, test_post, test_put}; @@ -27,3 +31,40 @@ macro_rules! assert_mail_snapshot { pub(crate) use assert_mail_snapshot; pub(crate) use assert_snapshot; + +pub trait NaiveDateTimeExt { + fn from_ymd_and_hms( + year: i32, + month: u32, + day: u32, + hour: u32, + minute: u32, + second: u32, + ) -> Option; +} + +impl NaiveDateTimeExt for NaiveDateTime { + fn from_ymd_and_hms( + year: i32, + month: u32, + day: u32, + hour: u32, + minute: u32, + second: u32, + ) -> Option { + NaiveDate::from_ymd_opt(year, month, day)?.and_hms_opt(hour, minute, second) + } +} + +pub trait ServiceResponseExt { + async fn into_status_and_body(self) -> (StatusCode, String); +} + +impl ServiceResponseExt for ServiceResponse where B: MessageBody { + async fn into_status_and_body(self) -> (StatusCode, String) { + let status = self.status(); + let response = String::from_utf8(test::read_body(self).await.to_vec()).unwrap(); + + (status, response) + } +} diff --git a/web/src/utils/test_helper/test_requests.rs b/web/src/utils/test_helper/test_requests.rs index 05f3fcbd..e85bf346 100644 --- a/web/src/utils/test_helper/test_requests.rs +++ b/web/src/utils/test_helper/test_requests.rs @@ -118,7 +118,7 @@ pub async fn test_post( pool: &Pool, app: T, config: &RequestConfig, - form: F, + form: Option, ) -> ServiceResponse where T: Service, Error = Error>,