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
+---
+
+
+
+ Name |
+ Funktion |
+ Zeitraum |
+ Kommentar |
+ Planung |
+ |
+
+
+
+
+
+
+ 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>,