test: area

This commit is contained in:
Max Hohlfeld 2025-02-06 23:01:38 +01:00
parent caf47eb82a
commit a5344adb73
9 changed files with 398 additions and 89 deletions

View File

@ -1,4 +1,5 @@
[alias]
db = ["run", "--package", "brass-cli", "--bin", "db", "--"]
w = ["watch", "-x", "run", "-i", "web/static/"]
t = ["nextest", "run"]
jt = ["nextest", "run"]
t = ["insta", "test", "--test-runner", "nextest", "--unreferenced", "reject", "--review", "--bins"]

View File

@ -0,0 +1,56 @@
---
source: web/src/endpoints/area/get_edit.rs
expression: body
snapshot_kind: text
---
<section class="section">
<div class="container">
<form method="post" action="/area/edit/1">
<h1 class="title">Bereich 'Leipzig Ost' bearbeiten</h1>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Name</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" type="text" name="name" required placeholder="Leipzig Ost" value="Leipzig Ost" />
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label"></div>
<div class="field-body">
<div class="field is-grouped">
<div class="control">
<button class="button is-success">
<svg class="icon">
<use href="/static/feather-sprite.svg#check-circle" />
</svg>
<span>
Speichern
</span>
</button>
</div>
<div class="control">
<a class="button is-link is-light" hx-boost="true" href="/locations">
<svg class="icon">
<use href="/static/feather-sprite.svg#arrow-left" />
</svg>
<span>Zurück</span>
</a>
</div>
</div>
</div>
</div>
</form>
</div>
</section>

View File

@ -1,5 +1,4 @@
use actix_web::{web, HttpResponse, Responder};
use brass_macros::db_test;
use sqlx::PgPool;
use crate::{
@ -8,9 +7,6 @@ use crate::{
utils::ApplicationError,
};
#[cfg(test)]
use crate::utils::test_helper::{test_delete, DbTestContext, RequestConfig, StatusCode};
#[actix_web::delete("/area/delete/{id}")]
pub async fn delete(
user: web::ReqData<User>,
@ -30,48 +26,90 @@ pub async fn delete(
Ok(HttpResponse::Ok().finish())
}
#[db_test]
async fn works_when_user_is_admin(context: &DbTestContext) {
Area::create(&context.db_pool, "Area to delete")
.await
.unwrap();
assert!(Area::read_by_id(&context.db_pool, 2)
.await
.unwrap()
.is_some());
let app = context.app().await;
let config = RequestConfig {
uri: "/area/delete/2".to_string(),
role: Role::Admin,
function: crate::models::Function::Posten,
user_area: 1,
#[cfg(test)]
mod tests {
use crate::{
models::{Area, Function, Location, Role},
utils::test_helper::{test_delete, DbTestContext, RequestConfig, StatusCode},
};
let response = test_delete(&context.db_pool, app, &config).await;
use brass_macros::db_test;
assert_eq!(StatusCode::OK, response.status());
assert!(Area::read_by_id(&context.db_pool, 2)
.await
.unwrap()
.is_none());
}
#[db_test]
async fn does_not_work_when_user_is_not_admin(context: &DbTestContext) {
Area::create(&context.db_pool, "Area to delete")
.await
.unwrap();
assert!(Area::read_by_id(&context.db_pool, 2)
.await
.unwrap()
.is_some());
let app = context.app().await;
let response = test_delete(&context.db_pool, app, &RequestConfig::new("/area/delete/2")).await;
assert_eq!(StatusCode::UNAUTHORIZED, response.status());
assert!(Area::read_by_id(&context.db_pool, 2)
.await
.unwrap()
.is_some());
#[db_test]
async fn deletes_area_when_user_is_admin_and_area_exists(context: &DbTestContext) {
Area::create(&context.db_pool, "Area to delete")
.await
.unwrap();
assert!(Area::read_by_id(&context.db_pool, 2)
.await
.unwrap()
.is_some());
let app = context.app().await;
let config = RequestConfig {
uri: "/area/delete/2".to_string(),
role: Role::Admin,
function: Function::Posten,
user_area: 1,
};
let response = test_delete(&context.db_pool, app, &config).await;
assert_eq!(StatusCode::OK, response.status());
assert!(Area::read_by_id(&context.db_pool, 2)
.await
.unwrap()
.is_none());
}
#[db_test]
async fn returns_unauthorized_when_user_is_not_admin(context: &DbTestContext) {
let app = context.app().await;
let response =
test_delete(&context.db_pool, app, &RequestConfig::new("/area/delete/1")).await;
assert_eq!(StatusCode::UNAUTHORIZED, response.status());
}
#[db_test]
async fn returns_not_found_when_area_does_not_exist(context: &DbTestContext) {
let app = context.app().await;
let config = RequestConfig {
uri: "/area/delete/2".to_string(),
role: Role::Admin,
function: Function::Posten,
user_area: 1,
};
let response = test_delete(&context.db_pool, app, &config).await;
assert_eq!(StatusCode::NOT_FOUND, response.status());
}
#[db_test]
async fn deletes_location_connected_to_area(context: &DbTestContext) {
Area::create(&context.db_pool, "Area to delete")
.await
.unwrap();
Location::create(&context.db_pool, "Location connected to area", 2)
.await
.unwrap();
let app = context.app().await;
let config = RequestConfig {
uri: "/area/delete/2".to_string(),
role: Role::Admin,
function: Function::Posten,
user_area: 1,
};
let response = test_delete(&context.db_pool, app, &config).await;
assert_eq!(StatusCode::OK, response.status());
assert!(Area::read_by_id(&context.db_pool, 2)
.await
.unwrap()
.is_none());
assert!(Location::read_by_id(&context.db_pool, 1)
.await
.unwrap()
.is_none());
}
}

View File

@ -28,3 +28,62 @@ async fn get(
Ok(HttpResponse::NotFound().finish())
}
}
#[cfg(test)]
mod tests {
use actix_http::StatusCode;
use brass_macros::db_test;
use crate::{
models::{Function, Role},
utils::test_helper::{assert_snapshot, read_body, test_get, DbTestContext, RequestConfig},
};
#[db_test]
async fn produces_template_when_area_exists_and_user_is_admin(context: &DbTestContext) {
let app = context.app().await;
let config = RequestConfig {
uri: "/area/edit/1".to_string(),
role: Role::Admin,
function: Function::Posten,
user_area: 1,
};
let response = test_get(&context.db_pool, app, &config).await;
assert_eq!(StatusCode::OK, response.status());
let body = read_body(response).await;
assert_snapshot!(body);
}
#[db_test]
async fn returns_unauthorized_when_user_is_not_admin(context: &DbTestContext) {
let app = context.app().await;
let config = RequestConfig {
uri: "/area/edit/1".to_string(),
role: Role::AreaManager,
function: Function::Posten,
user_area: 1,
};
let response = test_get(&context.db_pool, app, &config).await;
assert_eq!(StatusCode::UNAUTHORIZED, response.status());
}
#[db_test]
async fn returns_not_found_when_area_does_not_exist(context: &DbTestContext) {
let app = context.app().await;
let config = RequestConfig {
uri: "/area/edit/2".to_string(),
role: Role::Admin,
function: Function::Posten,
user_area: 1,
};
let response = test_get(&context.db_pool, app, &config).await;
assert_eq!(StatusCode::NOT_FOUND, response.status());
}
}

View File

@ -4,12 +4,6 @@ use crate::{
utils::{ApplicationError, TemplateResponse},
};
use actix_web::{web, Responder};
use brass_macros::db_test;
#[cfg(test)]
use crate::utils::test_helper::{
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig, StatusCode,
};
#[actix_web::get("/area/new")]
async fn get(user: web::ReqData<User>) -> Result<impl Responder, ApplicationError> {
@ -25,19 +19,45 @@ async fn get(user: web::ReqData<User>) -> Result<impl Responder, ApplicationErro
Ok(template.to_response()?)
}
#[db_test]
async fn produces_template(context: &DbTestContext) {
let app = context.app().await;
let config = RequestConfig {
uri: "/area/new".to_string(),
role: Role::Admin,
function: crate::models::Function::Posten,
user_area: 1,
#[cfg(test)]
mod tests {
use brass_macros::db_test;
use crate::{
models::{Function, Role},
utils::test_helper::{
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig, StatusCode,
},
};
let response = test_get(&context.db_pool, app, &config).await;
assert_eq!(StatusCode::OK, response.status());
#[db_test]
async fn produces_template_when_user_is_admin(context: &DbTestContext) {
let app = context.app().await;
let config = RequestConfig {
uri: "/area/new".to_string(),
role: Role::Admin,
function: Function::Posten,
user_area: 1,
};
let response = test_get(&context.db_pool, app, &config).await;
let body = read_body(response).await;
assert_snapshot!(body);
assert_eq!(StatusCode::OK, response.status());
let body = read_body(response).await;
assert_snapshot!(body);
}
#[db_test]
async fn returns_unauthorized_when_user_is_not_admin(context: &DbTestContext) {
let app = context.app().await;
let config = RequestConfig {
uri: "/area/new".to_string(),
role: Role::AreaManager,
function: Function::Posten,
user_area: 1,
};
let response = test_get(&context.db_pool, app, &config).await;
assert_eq!(StatusCode::UNAUTHORIZED, response.status());
}
}

View File

@ -18,6 +18,7 @@ struct NewOrEditAreaTemplate {
}
#[derive(Deserialize)]
#[cfg_attr(test, derive(serde::Serialize))]
struct AreaForm {
name: String,
}

View File

@ -20,16 +20,93 @@ pub async fn post(
return Err(ApplicationError::Unauthorized);
}
if let Some(area) = Area::read_by_id(pool.get_ref(), path.id).await? {
if area.name != form.name {
Area::update(pool.get_ref(), path.id, &form.name).await?;
}
let Some(area) = Area::read_by_id(pool.get_ref(), path.id).await? else {
return Ok(HttpResponse::NotFound().finish());
};
Ok(HttpResponse::Found()
.insert_header((LOCATION, "/locations"))
.insert_header(("HX-LOCATION", "/locations"))
.finish())
} else {
Ok(HttpResponse::NotFound().finish())
Area::update(pool.get_ref(), area.id, &form.name).await?;
Ok(HttpResponse::Found()
.insert_header((LOCATION, "/locations"))
.insert_header(("HX-LOCATION", "/locations"))
.finish())
}
#[cfg(test)]
mod tests {
use actix_http::StatusCode;
use brass_macros::db_test;
use crate::{
endpoints::area::AreaForm,
models::{Area, Function, Role},
utils::test_helper::{test_post, DbTestContext, RequestConfig},
};
#[db_test]
async fn updates_area_when_user_is_admin_and_area_exists(context: &DbTestContext) {
let app = context.app().await;
let config = RequestConfig {
uri: "/area/edit/1".to_string(),
role: Role::Admin,
function: Function::Posten,
user_area: 1,
};
let request = AreaForm {
name: "Neuer Name".to_string(),
};
let response = test_post(&context.db_pool, app, &config, request).await;
assert_eq!(StatusCode::FOUND, response.status());
let updated_area = Area::read_by_id(&context.db_pool, 1)
.await
.unwrap()
.unwrap();
assert_eq!("Neuer Name".to_string(), updated_area.name);
}
#[db_test]
async fn returns_unauthorized_when_user_is_not_admin(context: &DbTestContext) {
let app = context.app().await;
let config = RequestConfig {
uri: "/area/edit/1".to_string(),
role: Role::AreaManager,
function: Function::Posten,
user_area: 1,
};
let request = AreaForm {
name: "Neuer Name".to_string(),
};
let response = test_post(&context.db_pool, app, &config, request).await;
assert_eq!(StatusCode::UNAUTHORIZED, response.status());
}
#[db_test]
async fn returns_not_found_when_area_does_not_exist(context: &DbTestContext) {
let app = context.app().await;
let config = RequestConfig {
uri: "/area/edit/2".to_string(),
role: Role::Admin,
function: Function::Posten,
user_area: 1,
};
let request = AreaForm {
name: "Neuer Name".to_string(),
};
let response = test_post(&context.db_pool, app, &config, request).await;
assert_eq!(StatusCode::NOT_FOUND, response.status());
}
}

View File

@ -1,28 +1,85 @@
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use sqlx::PgPool;
use crate::{endpoints::area::AreaForm, models::{Area, Role, User}};
use crate::{
endpoints::area::AreaForm,
models::{Area, Role, User},
utils::ApplicationError,
};
#[actix_web::post("/area/new")]
pub async fn post(
user: web::ReqData<User>,
pool: web::Data<PgPool>,
form: web::Form<AreaForm>,
) -> impl Responder {
) -> Result<impl Responder, ApplicationError> {
if user.role != Role::Admin {
return HttpResponse::Unauthorized().finish();
return Err(ApplicationError::Unauthorized);
}
match Area::create(pool.get_ref(), &form.name).await {
Ok(_) => {
HttpResponse::Found()
.insert_header((LOCATION, "/locations"))
.insert_header(("HX-LOCATION", "/locations"))
.finish()
}
Err(err) => {
println!("{}", err);
HttpResponse::InternalServerError().finish()
}
Area::create(pool.get_ref(), &form.name).await?;
Ok(HttpResponse::Found()
.insert_header((LOCATION, "/locations"))
.insert_header(("HX-LOCATION", "/locations"))
.finish())
}
#[cfg(test)]
mod tests {
use actix_http::StatusCode;
use brass_macros::db_test;
use crate::{
endpoints::area::AreaForm,
models::{Area, Function, Role},
utils::test_helper::{test_post, DbTestContext, RequestConfig},
};
#[db_test]
async fn updates_area_when_user_is_admin_and_area_exists(context: &DbTestContext) {
let app = context.app().await;
let config = RequestConfig {
uri: "/area/new".to_string(),
role: Role::Admin,
function: Function::Posten,
user_area: 1,
};
let request = AreaForm {
name: "Neuer Name".to_string(),
};
let response = test_post(&context.db_pool, app, &config, request).await;
assert_eq!(StatusCode::FOUND, response.status());
let updated_area = Area::read_by_id(&context.db_pool, 2)
.await
.unwrap()
.unwrap();
assert_eq!("Neuer Name".to_string(), updated_area.name);
}
#[db_test]
async fn returns_unauthorized_when_user_is_not_admin(context: &DbTestContext) {
let app = context.app().await;
let config = RequestConfig {
uri: "/area/new".to_string(),
role: Role::AreaManager,
function: Function::Posten,
user_area: 1,
};
let request = AreaForm {
name: "Neuer Name".to_string(),
};
let response = test_post(&context.db_pool, app, &config, request).await;
assert_eq!(StatusCode::UNAUTHORIZED, response.status());
}
}