From 5777268441c346e38049ec206909beeac2a51889 Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin Date: Tue, 25 Mar 2025 19:30:02 +0700 Subject: [PATCH] feat: fixing role and auth test --- src/apps/v1/auth/auth_controller_test.rs | 4 +- src/apps/v1/roles/roles_controller_test.rs | 90 +++++++++++++++++++++- src/apps/v1/roles/roles_dto.rs | 7 +- src/apps/v1/roles/roles_service.rs | 79 ++++++++++++++----- 4 files changed, 154 insertions(+), 26 deletions(-) diff --git a/src/apps/v1/auth/auth_controller_test.rs b/src/apps/v1/auth/auth_controller_test.rs index 85e15ac..6b7b16a 100644 --- a/src/apps/v1/auth/auth_controller_test.rs +++ b/src/apps/v1/auth/auth_controller_test.rs @@ -143,8 +143,8 @@ async fn test_register_should_succeed() { "fullname": "Valid User", "student_type": "regular", "phone_number": "0812345678", - "reffered_by": "Facebook", - "refferal_code": "KFNB" + "reffered_by": "Facebook", + "refferal_code": "KFNB" }); let res = server.post("/v1/auth/register").json(&payload).await; assert_eq!(res.status_code(), StatusCode::CREATED); diff --git a/src/apps/v1/roles/roles_controller_test.rs b/src/apps/v1/roles/roles_controller_test.rs index 4bcd675..11ca255 100644 --- a/src/apps/v1/roles/roles_controller_test.rs +++ b/src/apps/v1/roles/roles_controller_test.rs @@ -16,6 +16,11 @@ pub fn create_test_app(state: AppState) -> TestServer { TestServer::new(app).unwrap() } +async fn delete_dummy_role(state: AppState, id: String) { + let repo = RolesRepository::new(&state); + let _ = repo.query_delete_role(id).await; +} + #[tokio::test] async fn test_get_role_list_should_return_200() { let state = create_mock_app_state().await; @@ -35,11 +40,18 @@ async fn test_post_create_role_should_return_201() { .layer(axum::Extension(state.clone())); let server = TestServer::new(app).unwrap(); let payload = RolesRequestCreateDto { - name: "Test Role".into(), + name: "Test Role #1".into(), permissions: vec![], }; let res = server.post("/v1/roles/create").json(&payload).await; + let repo = RolesRepository::new(&state); assert_eq!(res.status_code(), StatusCode::CREATED); + let id = repo + .query_role_by_name(payload.name.clone()) + .await + .unwrap() + .id; + delete_dummy_role(state, id).await; } #[tokio::test] @@ -58,7 +70,7 @@ async fn test_put_update_role_should_return_200() { .await .unwrap(); let id = existing.id.clone(); - let server = create_test_app(state); + let server = create_test_app(state.clone()); let payload = RolesRequestUpdateDto { name: Some("Updated Role".into()), permissions: Some(vec![]), @@ -69,6 +81,12 @@ async fn test_put_update_role_should_return_200() { .await; dbg!(res.text()); assert_eq!(res.status_code(), StatusCode::OK); + let id = repo + .query_role_by_name(payload.name.clone().unwrap()) + .await + .unwrap() + .id; + delete_dummy_role(state.clone(), id).await; } #[tokio::test] @@ -92,3 +110,71 @@ async fn test_delete_role_should_return_200() { dbg!(res.text()); assert_eq!(res.status_code(), StatusCode::OK); } + +#[tokio::test] +async fn test_post_create_role_with_empty_name_should_return_400() { + let state = create_mock_app_state().await; + let server = create_test_app(state); + let payload = RolesRequestCreateDto { + name: "".into(), + permissions: vec![], + }; + let res = server.post("/v1/roles/create").json(&payload).await; + dbg!(res.text()); + assert_eq!(res.status_code(), StatusCode::BAD_REQUEST); +} + +#[tokio::test] +async fn test_put_update_nonexistent_role_should_return_404() { + let state = create_mock_app_state().await; + let server = create_test_app(state); + let payload = RolesRequestUpdateDto { + name: Some("Does Not Exist".into()), + permissions: Some(vec![]), + }; + let res = server + .put("/v1/roles/update/nonexistent-id") + .json(&payload) + .await; + dbg!(res.text()); + assert_eq!(res.status_code(), StatusCode::NOT_FOUND); +} + +#[tokio::test] +async fn test_delete_nonexistent_role_should_return_404() { + let state = create_mock_app_state().await; + let server = create_test_app(state); + let res = server.delete("/v1/roles/delete/nonexistent-id").await; + dbg!(res.text()); + assert_eq!(res.status_code(), StatusCode::NOT_FOUND); +} + +#[tokio::test] +async fn test_get_role_list_with_invalid_page_params_should_return_400() { + let state = create_mock_app_state().await; + let server = create_test_app(state); + let res = server.get("/v1/roles?page=abc&per_page=xyz").await; + dbg!(res.text()); + assert_eq!(res.status_code(), StatusCode::BAD_REQUEST); +} + +#[tokio::test] +async fn test_create_duplicate_role_should_return_409() { + let state = create_mock_app_state().await; + let repo = RolesRepository::new(&state); + let _ = repo + .query_create_role(RolesRequestCreateDto { + name: "Admin".into(), + permissions: vec![], + }) + .await + .unwrap(); + let server = create_test_app(state); + let payload = RolesRequestCreateDto { + name: "Admin".into(), + permissions: vec![], + }; + let res = server.post("/v1/roles/create").json(&payload).await; + dbg!(res.text()); + assert_eq!(res.status_code(), StatusCode::CONFLICT); +} diff --git a/src/apps/v1/roles/roles_dto.rs b/src/apps/v1/roles/roles_dto.rs index 96cb1ea..69f4ab0 100644 --- a/src/apps/v1/roles/roles_dto.rs +++ b/src/apps/v1/roles/roles_dto.rs @@ -1,15 +1,18 @@ use crate::PermissionsItemDto; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; +use validator::Validate; -#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)] pub struct RolesRequestUpdateDto { + #[validate(length(min = 1, message = "Role name must not be empty"))] pub name: Option, pub permissions: Option>, } -#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)] pub struct RolesRequestCreateDto { + #[validate(length(min = 1, message = "Role name must not be empty"))] pub name: String, pub permissions: Vec, } diff --git a/src/apps/v1/roles/roles_service.rs b/src/apps/v1/roles/roles_service.rs index 12f5f81..f381d7a 100644 --- a/src/apps/v1/roles/roles_service.rs +++ b/src/apps/v1/roles/roles_service.rs @@ -1,18 +1,14 @@ -use crate::{ - common_response, success_list_response, success_response, AppState, - MetaRequestDto, ResponseListSuccessDto, ResponseSuccessDto, -}; -use axum::http::StatusCode; - use super::{RolesRepository, RolesRequestCreateDto, RolesRequestUpdateDto}; +use crate::{ + common_response, success_list_response, success_response, validate_request, + AppState, MetaRequestDto, ResponseListSuccessDto, ResponseSuccessDto, +}; +use axum::{http::StatusCode, response::Response}; pub struct RolesService; impl RolesService { - pub async fn get_role_list( - state: &AppState, - meta: MetaRequestDto, - ) -> axum::response::Response { + pub async fn get_role_list(state: &AppState, meta: MetaRequestDto) -> Response { let repo = RolesRepository::new(state); match repo.query_role_list(meta).await { Ok(data) => { @@ -26,10 +22,7 @@ impl RolesService { } } - pub async fn get_role_by_id( - state: &AppState, - id: String, - ) -> axum::response::Response { + pub async fn get_role_by_id(state: &AppState, id: String) -> Response { let repo = RolesRepository::new(state); match repo.query_role_by_id(id).await { Ok(role) => success_response(ResponseSuccessDto { data: role }), @@ -40,8 +33,20 @@ impl RolesService { pub async fn create_role( state: &AppState, payload: RolesRequestCreateDto, - ) -> axum::response::Response { + ) -> Response { + if let Err((status, message)) = validate_request(&payload) { + return common_response(status, &message); + } let repo = RolesRepository::new(state); + match repo.query_role_by_name(payload.name.clone()).await { + Ok(_role) => { + return common_response(StatusCode::CONFLICT, "Role name already exists"); + } + Err(err) if err.to_string().contains("not found") => {} + Err(e) => { + return common_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()); + } + } match repo.query_create_role(payload).await { Ok(msg) => common_response(StatusCode::CREATED, &msg), Err(e) => common_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()), @@ -52,19 +57,53 @@ impl RolesService { state: &AppState, id: String, payload: RolesRequestUpdateDto, - ) -> axum::response::Response { + ) -> Response { + if let Err((status, message)) = validate_request(&payload) { + return common_response(status, &message); + } let repo = RolesRepository::new(state); + let existing_role = match repo.query_role_by_id(id.clone()).await { + Ok(role) => role, + Err(err) if err.to_string().contains("not found") => { + return common_response(StatusCode::NOT_FOUND, "Role not found"); + } + Err(e) => { + return common_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()); + } + }; + if let Some(new_name) = payload.name.clone() { + match repo.query_role_by_name(new_name.clone()).await { + Ok(role_with_same_name) => { + if role_with_same_name.id != existing_role.id { + return common_response( + StatusCode::CONFLICT, + "Role name already exists", + ); + } + } + Err(err) if err.to_string().contains("not found") => {} + Err(e) => { + return common_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()); + } + } + } match repo.query_update_role(id, payload).await { Ok(msg) => common_response(StatusCode::OK, &msg), Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()), } } - pub async fn delete_role( - state: &AppState, - id: String, - ) -> axum::response::Response { + pub async fn delete_role(state: &AppState, id: String) -> Response { let repo = RolesRepository::new(state); + match repo.query_role_by_id(id.clone()).await { + Ok(_) => {} + Err(err) if err.to_string().contains("not found") => { + return common_response(StatusCode::NOT_FOUND, "Role not found"); + } + Err(e) => { + return common_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()); + } + } match repo.query_delete_role(id).await { Ok(msg) => common_response(StatusCode::OK, &msg), Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),