From d906fc47474a582756223a14d97f89f73eab6075 Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin Date: Tue, 20 May 2025 16:57:45 +0700 Subject: [PATCH] chore: update query list --- README.md | 8 + .../src/v1/gacha_claim/gacha_claim_dto.rs | 2 +- .../src/v1/gacha_item/gacha_item_dto.rs | 2 +- .../v1/gacha_item/gacha_item_repository.rs | 20 +- .../src/v1/gacha_item/gacha_item_service.rs | 2 +- .../src/v1/gacha_roll/gacha_roll_dto.rs | 2 +- .../v1/permissions/permissions_repository.rs | 18 +- imphnen-iam/src/v1/roles/roles_repository.rs | 87 +-- imphnen-iam/src/v1/users/users_repository.rs | 31 +- imphnen-utils/src/query_list.rs | 164 +++-- tests/src/iam/permissions/mod.rs | 2 - .../permissions_controller_test.rs | 171 ----- tests/src/iam/roles/mod.rs | 2 - tests/src/iam/roles/roles_controller_test.rs | 174 ----- tests/src/iam/roles/roles_repository_test.rs | 7 +- tests/src/iam/users/mod.rs | 2 - tests/src/iam/users/users_controller_test.rs | 602 ------------------ tests/src/iam/users/users_repository_test.rs | 8 +- 18 files changed, 165 insertions(+), 1139 deletions(-) delete mode 100644 tests/src/iam/permissions/permissions_controller_test.rs delete mode 100644 tests/src/iam/roles/roles_controller_test.rs delete mode 100644 tests/src/iam/users/users_controller_test.rs diff --git a/README.md b/README.md index dae176c..d96f618 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,14 @@ For production deployment: Adjust the port and environment variables as needed. +## How to Run the Tests + +1. **Run the tests**: + + ```sh + cargo test + ``` + ## How to Contribute 1. **Fork the repository** and clone it locally. diff --git a/imphnen-gacha/src/v1/gacha_claim/gacha_claim_dto.rs b/imphnen-gacha/src/v1/gacha_claim/gacha_claim_dto.rs index 7b465f2..5570ada 100644 --- a/imphnen-gacha/src/v1/gacha_claim/gacha_claim_dto.rs +++ b/imphnen-gacha/src/v1/gacha_claim/gacha_claim_dto.rs @@ -38,7 +38,7 @@ impl GachaClaimItemDto { Self { id: dto.id.id.to_raw(), user: UsersDetailItemDto::from(&dto.user), - item: GachaItemDto::from(&dto.item), + item: GachaItemDto::from(dto.item.clone()), is_deleted: dto.is_deleted, created_at: dto.created_at.clone(), updated_at: dto.updated_at.clone(), diff --git a/imphnen-gacha/src/v1/gacha_item/gacha_item_dto.rs b/imphnen-gacha/src/v1/gacha_item/gacha_item_dto.rs index d4ba145..589a29f 100644 --- a/imphnen-gacha/src/v1/gacha_item/gacha_item_dto.rs +++ b/imphnen-gacha/src/v1/gacha_item/gacha_item_dto.rs @@ -21,7 +21,7 @@ pub struct GachaItemDto { } impl GachaItemDto { - pub fn from(dto: &GachaItemSchema) -> Self { + pub fn from(dto: GachaItemSchema) -> Self { Self { id: dto.id.id.to_raw(), name: dto.name.clone(), diff --git a/imphnen-gacha/src/v1/gacha_item/gacha_item_repository.rs b/imphnen-gacha/src/v1/gacha_item/gacha_item_repository.rs index 1f85262..c29bdbe 100644 --- a/imphnen-gacha/src/v1/gacha_item/gacha_item_repository.rs +++ b/imphnen-gacha/src/v1/gacha_item/gacha_item_repository.rs @@ -1,9 +1,10 @@ use super::GachaItemSchema; use crate::{ AppState, GachaItemDto, MetaRequestDto, ResourceEnum, ResponseListSuccessDto, - get_id, make_thing, query_list_with_meta, + get_id, make_thing, }; use anyhow::{Result, bail}; +use imphnen_iam::QueryListBuilder; pub struct GachaItemRepository<'a> { state: &'a AppState, @@ -19,24 +20,23 @@ impl<'a> GachaItemRepository<'a> { meta: MetaRequestDto, ) -> Result>> { let raw_result: ResponseListSuccessDto> = - query_list_with_meta( + QueryListBuilder::new( &self.state.surrealdb_ws, &ResourceEnum::GachaItems.to_string(), &meta, - vec!["is_deleted = false".into()], - None, - "name", - Some(vec!["*"]), - None, ) + .with_condition("is_deleted = false") + .search_field("name") + .select_fields(vec!["*"]) + .build() .await?; - let transformed_data = raw_result + let data = raw_result .data .into_iter() - .map(|gacha_item| GachaItemDto::from(&gacha_item)) + .map(GachaItemDto::from) .collect(); Ok(ResponseListSuccessDto { - data: transformed_data, + data, meta: raw_result.meta, }) } diff --git a/imphnen-gacha/src/v1/gacha_item/gacha_item_service.rs b/imphnen-gacha/src/v1/gacha_item/gacha_item_service.rs index 6eb3170..bf6de50 100644 --- a/imphnen-gacha/src/v1/gacha_item/gacha_item_service.rs +++ b/imphnen-gacha/src/v1/gacha_item/gacha_item_service.rs @@ -31,7 +31,7 @@ impl GachaItemService { let repo = GachaItemRepository::new(state); match repo.query_gacha_item_by_id(id).await { Ok(item) => success_response(ResponseSuccessDto { - data: GachaItemDto::from(&item), + data: GachaItemDto::from(item), }), Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()), } diff --git a/imphnen-gacha/src/v1/gacha_roll/gacha_roll_dto.rs b/imphnen-gacha/src/v1/gacha_roll/gacha_roll_dto.rs index d4d4c48..953df9d 100644 --- a/imphnen-gacha/src/v1/gacha_roll/gacha_roll_dto.rs +++ b/imphnen-gacha/src/v1/gacha_roll/gacha_roll_dto.rs @@ -29,7 +29,7 @@ impl GachaRollItemDto { pub fn from(dto: &GachaRollQueryDto) -> Self { Self { id: dto.id.id.to_raw(), - item: GachaItemDto::from(&dto.item), + item: GachaItemDto::from(dto.item.clone()), weight: dto.weight.to_string(), quantity: dto.quantity, is_deleted: dto.is_deleted, diff --git a/imphnen-iam/src/v1/permissions/permissions_repository.rs b/imphnen-iam/src/v1/permissions/permissions_repository.rs index f5bf7ba..a0dc30a 100644 --- a/imphnen-iam/src/v1/permissions/permissions_repository.rs +++ b/imphnen-iam/src/v1/permissions/permissions_repository.rs @@ -1,10 +1,9 @@ use super::{PermissionsItemDto, PermissionsSchema}; use crate::{ - AppState, MetaRequestDto, ResourceEnum, ResponseListSuccessDto, get_id, - make_thing, query_list_with_meta, + AppState, MetaRequestDto, ResourceEnum, ResponseListSuccessDto, get_id, make_thing, }; use anyhow::{Result, bail}; -use imphnen_utils::{DetailQueryBuilder, extract_id}; +use imphnen_utils::{DetailQueryBuilder, QueryListBuilder, extract_id}; pub struct PermissionsRepository<'a> { state: &'a AppState, @@ -20,22 +19,23 @@ impl<'a> PermissionsRepository<'a> { meta: MetaRequestDto, ) -> Result>> { let raw_result: ResponseListSuccessDto> = - query_list_with_meta( + QueryListBuilder::new( &self.state.surrealdb_ws, &ResourceEnum::Permissions.to_string(), &meta, - vec!["is_deleted = false".into()], - None, - "name", - Some(vec!["*"]), - None, ) + .with_condition("is_deleted = false") + .search_field("name") + .select_fields(vec!["*"]) + .build() .await?; + let transformed_data = raw_result .data .into_iter() .map(|permission| PermissionsSchema::list(&permission)) .collect(); + Ok(ResponseListSuccessDto { data: transformed_data, meta: raw_result.meta, diff --git a/imphnen-iam/src/v1/roles/roles_repository.rs b/imphnen-iam/src/v1/roles/roles_repository.rs index 77c20d8..d7d51e8 100644 --- a/imphnen-iam/src/v1/roles/roles_repository.rs +++ b/imphnen-iam/src/v1/roles/roles_repository.rs @@ -3,11 +3,10 @@ use super::{ RolesRequestUpdateDto, RolesSchema, }; use crate::{ - AppState, MetaRequestDto, PermissionsItemDto, ResourceEnum, - ResponseListSuccessDto, extract_id, get_id, make_thing, query_list_with_meta, + AppState, MetaRequestDto, ResourceEnum, ResponseListSuccessDto, get_id, make_thing, }; use anyhow::{Result, bail}; -use imphnen_utils::DetailQueryBuilder; +use imphnen_utils::{DetailQueryBuilder, QueryListBuilder}; use surrealdb::Uuid; use surrealdb::sql::Thing; @@ -24,36 +23,23 @@ impl<'a> RolesRepository<'a> { &self, meta: MetaRequestDto, ) -> Result>> { - let mut conditions = vec!["is_deleted = false".into()]; - if let Some(_search) = meta.search.as_deref().filter(|s| !s.is_empty()) { - conditions.push("string::contains(name ?? '', $search)".into()); - } - if let (Some(filter_by), Some(filter_val)) = - (meta.filter_by.as_ref(), meta.filter.as_ref()) - { - if !filter_val.is_empty() { - conditions.push(format!("{} = $filter", filter_by)); - } - } - let raw_result: ResponseListSuccessDto> = query_list_with_meta( + let result: ResponseListSuccessDto> = QueryListBuilder::new( &self.state.surrealdb_ws, &ResourceEnum::Roles.to_string(), &meta, - conditions, - None, - "name", - None, - None, ) + .with_condition("is_deleted = false") + .search_field("name") + .build() .await?; - let data = raw_result + let data = result .data .into_iter() .map(|role| RolesSchema::list(&role)) .collect(); Ok(ResponseListSuccessDto { data, - meta: raw_result.meta, + meta: result.meta, }) } @@ -65,16 +51,8 @@ impl<'a> RolesRepository<'a> { let builder = DetailQueryBuilder::new(ResourceEnum::Roles.to_string()) .with_where("name") .where_value(name.clone()) - .with_select_fields(vec![ - "id", - "name", - "permissions", - "created_at", - "updated_at", - "is_deleted", - ]) + .with_select_fields(vec!["*"]) .with_fetch("permissions"); - let sql = builder.build(); let result: Option = builder .apply_bindings(db.query(sql).bind(("name", name))) @@ -84,38 +62,14 @@ impl<'a> RolesRepository<'a> { Some(r) if !r.is_deleted => r, _ => bail!("Role not found"), }; - let permissions = role - .permissions - .into_iter() - .map(|perm| PermissionsItemDto { - id: extract_id(&perm.id), - name: perm.name, - created_at: perm.created_at, - updated_at: perm.updated_at, - }) - .collect(); - Ok(RolesDetailItemDto { - id: extract_id(&role.id), - name: role.name, - is_deleted: role.is_deleted, - permissions, - created_at: role.created_at, - updated_at: role.updated_at, - }) + Ok(RolesDetailItemDto::from(&role)) } pub async fn query_role_by_id(&self, id: String) -> Result { let db = &self.state.surrealdb_ws; let builder = DetailQueryBuilder::new(ResourceEnum::Roles.to_string()) .with_id(&id) - .with_select_fields(vec![ - "id", - "name", - "is_deleted", - "permissions", - "created_at", - "updated_at", - ]) + .with_select_fields(vec!["*"]) .with_fetch("permissions"); let sql = builder.build(); let result: Option = @@ -124,24 +78,7 @@ impl<'a> RolesRepository<'a> { Some(r) if !r.is_deleted => r, _ => bail!("Role not found"), }; - let permissions = role - .permissions - .into_iter() - .map(|perm| PermissionsItemDto { - id: extract_id(&perm.id), - name: perm.name, - created_at: perm.created_at, - updated_at: perm.updated_at, - }) - .collect(); - Ok(RolesDetailItemDto { - id: extract_id(&role.id), - name: role.name, - is_deleted: role.is_deleted, - permissions, - created_at: role.created_at, - updated_at: role.updated_at, - }) + Ok(RolesDetailItemDto::from(&role)) } pub async fn query_create_role( diff --git a/imphnen-iam/src/v1/users/users_repository.rs b/imphnen-iam/src/v1/users/users_repository.rs index d3ae20c..260a3a5 100644 --- a/imphnen-iam/src/v1/users/users_repository.rs +++ b/imphnen-iam/src/v1/users/users_repository.rs @@ -1,10 +1,9 @@ use super::{UsersDetailQueryDto, UsersListItemDto, UsersListQueryDto, UsersSchema}; use crate::{ - AppState, MetaRequestDto, ResourceEnum, ResponseListSuccessDto, get_id, - make_thing, query_list_with_meta, + AppState, MetaRequestDto, ResourceEnum, ResponseListSuccessDto, get_id, make_thing, }; use anyhow::{Result, bail}; -use imphnen_utils::DetailQueryBuilder; +use imphnen_utils::{DetailQueryBuilder, QueryListBuilder}; use surrealdb::{Surreal, engine::remote::ws::Client}; pub struct UsersRepository<'a> { @@ -35,23 +34,23 @@ impl<'a> UsersRepository<'a> { &self, meta: MetaRequestDto, ) -> Result>> { - let db = &self.state.surrealdb_ws; - let result = query_list_with_meta::( - db, - &ResourceEnum::Users.to_string(), - &meta, - vec!["is_deleted = false".into()], - None, - "fullname", - Some(vec!["*"]), - Some(vec!["role", "role.permissions"]), - ) - .await?; + let result: ResponseListSuccessDto> = + QueryListBuilder::new( + &self.state.surrealdb_ws, + &ResourceEnum::Users.to_string(), + &meta, + ) + .with_condition("is_deleted = false") + .search_field("fullname") + .select_fields(vec!["*"]) + .fetch_fields(vec!["role", "role.permissions"]) + .build() + .await?; let data = result .data .into_iter() .map(UsersListQueryDto::from) - .collect::>(); + .collect(); Ok(ResponseListSuccessDto { data, meta: result.meta, diff --git a/imphnen-utils/src/query_list.rs b/imphnen-utils/src/query_list.rs index aa65645..39c60a0 100644 --- a/imphnen-utils/src/query_list.rs +++ b/imphnen-utils/src/query_list.rs @@ -1,78 +1,116 @@ -use super::{ListQueryBuilder, bind_filter_value}; use crate::{CountResult, MetaRequestDto, MetaResponseDto, ResponseListSuccessDto}; use anyhow::Result; use serde::{Serialize, de::DeserializeOwned}; use surrealdb::{Surreal, engine::remote::ws::Client}; -pub async fn query_list_with_meta( - db: &Surreal, - table: &str, - meta: &MetaRequestDto, +pub struct QueryListBuilder<'a> { + db: &'a Surreal, + table: &'a str, + meta: &'a MetaRequestDto, conditions: Vec, - custom_select: Option, - search_field: &str, - select_fields: Option>, - fetch_fields: Option>, -) -> Result>> -where - T: DeserializeOwned + Serialize, -{ - let page = meta.page.unwrap_or(1).max(1); - let per_page = meta.per_page.unwrap_or(10).max(1); - let start = (page - 1) * per_page; + search_field: String, + select_fields: Option>, + fetch_fields: Option>, +} - let sql = custom_select.unwrap_or_else(|| { - ListQueryBuilder::from_meta( +impl<'a> QueryListBuilder<'a> { + pub fn new( + db: &'a Surreal, + table: &'a str, + meta: &'a MetaRequestDto, + ) -> Self { + Self { + db, table, meta, - search_field, - select_fields, - fetch_fields, + conditions: vec![], + search_field: "name".to_string(), + select_fields: None, + fetch_fields: None, + } + } + + pub fn search_field(mut self, field: &'a str) -> Self { + self.search_field = field.to_string(); + self + } + + pub fn select_fields(mut self, fields: Vec<&'a str>) -> Self { + self.select_fields = Some(fields); + self + } + + pub fn fetch_fields(mut self, fields: Vec<&'a str>) -> Self { + self.fetch_fields = Some(fields); + self + } + + pub fn with_condition(mut self, condition: &str) -> Self { + self.conditions.push(condition.to_string()); + self + } + + pub async fn build(self) -> Result>> + where + T: DeserializeOwned + Serialize, + { + let page = self.meta.page.unwrap_or(1).max(1); + let per_page = self.meta.per_page.unwrap_or(10).max(1); + let start = (page - 1) * per_page; + + let sql = crate::ListQueryBuilder::from_meta( + self.table, + self.meta, + &self.search_field, + self.select_fields, + self.fetch_fields, ) - .build() - }); + .build(); - let mut query_exec = db.query(sql); - if let Some(search) = &meta.search { - if !search.is_empty() { - query_exec = query_exec.bind(("search", search.to_lowercase().clone())); + let mut query_exec = self.db.query(sql); + if let Some(search) = &self.meta.search { + if !search.is_empty() { + query_exec = query_exec.bind(("search", search.to_lowercase())); + } } - } - if let Some(filter_val) = &meta.filter { - query_exec = bind_filter_value(query_exec, filter_val.clone()); - } - query_exec = query_exec - .bind(("per_page", per_page)) - .bind(("start", start)); - - let raw: Vec = query_exec.await?.take(0)?; - - let mut count_query = db.query(format!( - "SELECT count() FROM {} {}", - table, - if conditions.is_empty() { - String::new() - } else { - format!("WHERE {}", conditions.join(" AND ")) + if let Some(filter_val) = &self.meta.filter { + query_exec = crate::bind_filter_value(query_exec, filter_val.clone()); } - )); - if let Some(search) = &meta.search { - if !search.is_empty() { - count_query = count_query.bind(("search", search.clone())); - } - } - if let Some(filter_val) = &meta.filter { - count_query = bind_filter_value(count_query, filter_val.clone()); - } - let count_result: Vec = count_query.await?.take(0)?; - let total = count_result.first().map(|c| c.count); + query_exec = query_exec + .bind(("per_page", per_page)) + .bind(("start", start)); - Ok(ResponseListSuccessDto { - data: raw, - meta: Some(MetaResponseDto { - page: Some(page), - per_page: Some(per_page), - total, - }), - }) + let raw: Vec = query_exec.await?.take(0)?; + + let mut count_query = self.db.query(format!( + "SELECT count() FROM {} {}", + self.table, + if self.conditions.is_empty() { + "".into() + } else { + format!("WHERE {}", self.conditions.join(" AND ")) + } + )); + + if let Some(search) = &self.meta.search { + if !search.is_empty() { + count_query = count_query.bind(("search", search.clone())); + } + } + if let Some(filter_val) = &self.meta.filter { + count_query = crate::bind_filter_value(count_query, filter_val.clone()); + } + + let count_result: Vec = count_query.await?.take(0)?; + let total = count_result.first().map(|c| c.count); + + Ok(ResponseListSuccessDto { + data: raw, + meta: Some(MetaResponseDto { + page: Some(page), + per_page: Some(per_page), + total, + }), + }) + } } diff --git a/tests/src/iam/permissions/mod.rs b/tests/src/iam/permissions/mod.rs index bfa4677..e2c1d92 100644 --- a/tests/src/iam/permissions/mod.rs +++ b/tests/src/iam/permissions/mod.rs @@ -1,4 +1,2 @@ #[cfg(test)] -pub mod permissions_controller_test; -#[cfg(test)] pub mod permissions_repository_test; diff --git a/tests/src/iam/permissions/permissions_controller_test.rs b/tests/src/iam/permissions/permissions_controller_test.rs deleted file mode 100644 index a2b5633..0000000 --- a/tests/src/iam/permissions/permissions_controller_test.rs +++ /dev/null @@ -1,171 +0,0 @@ -use crate::{ - create_mock_app_state, permissions_router, - v1::{ - permissions_dto::PermissionsRequestDto, - permissions_repository::PermissionsRepository, - }, - AppState, -}; -use axum::{http::StatusCode, Extension, Router}; -use axum_test::TestServer; - -pub fn create_test_app(state: AppState) -> TestServer { - let app = Router::new() - .nest("/v1/permissions", permissions_router()) - .layer(Extension(state)); - TestServer::new(app).unwrap() -} - -async fn create_dummy_permission( - repo: &PermissionsRepository<'_>, - name: &str, -) -> String { - let dto = crate::v1::permissions_schema::PermissionsSchema { - name: name.into(), - ..Default::default() - }; - let _ = repo.query_create_permission(dto.clone()).await.unwrap(); - let found = repo.query_permission_by_name(name.into()).await.unwrap(); - found.id.id.to_raw() -} - -async fn delete_dummy_permission(repo: &PermissionsRepository<'_>, id: String) { - let _ = repo.query_delete_permission(id).await; -} - -#[tokio::test] -async fn test_create_permission_should_return_201() { - let state = create_mock_app_state().await; - let repo = PermissionsRepository::new(&state); - let server = create_test_app(state.clone()); - let payload = PermissionsRequestDto { - name: "Create Permission".into(), - }; - let res = server.post("/v1/permissions/create").json(&payload).await; - dbg!(res.text()); - assert_eq!(res.status_code(), StatusCode::CREATED); - let id = repo - .query_permission_by_name(payload.name.clone()) - .await - .unwrap() - .id - .id - .to_raw(); - delete_dummy_permission(&repo, id).await; -} - -#[tokio::test] -async fn test_get_permission_list_should_return_200() { - let state = create_mock_app_state().await; - let server = create_test_app(state); - let res = server.get("/v1/permissions?page=1&per_page=10").await; - dbg!(res.text()); - assert_eq!(res.status_code(), StatusCode::OK); -} - -#[tokio::test] -async fn test_get_permission_by_id_should_return_200() { - let state = create_mock_app_state().await; - let repo = PermissionsRepository::new(&state); - let id = create_dummy_permission(&repo, "Get Permission").await; - let server = create_test_app(state.clone()); - let res = server.get(&format!("/v1/permissions/detail/{}", id)).await; - dbg!(res.text()); - assert_eq!(res.status_code(), StatusCode::OK); - delete_dummy_permission(&repo, id).await; -} - -#[tokio::test] -async fn test_update_permission_should_return_200() { - let state = create_mock_app_state().await; - let repo = PermissionsRepository::new(&state); - let id = create_dummy_permission(&repo, "To Update").await; - let server = create_test_app(state.clone()); - let payload = PermissionsRequestDto { - name: "Updated Permission".into(), - }; - let res = server - .put(&format!("/v1/permissions/update/{}", id.clone())) - .json(&payload) - .await; - dbg!(res.text()); - assert_eq!(res.status_code(), StatusCode::OK); - delete_dummy_permission(&repo, id).await; -} - -#[tokio::test] -async fn test_delete_permission_should_return_200() { - let state = create_mock_app_state().await; - let repo = PermissionsRepository::new(&state); - let id = create_dummy_permission(&repo, "To Delete").await; - let server = create_test_app(state); - let res = server - .delete(&format!("/v1/permissions/delete/{}", id)) - .await; - dbg!(res.text()); - assert_eq!(res.status_code(), StatusCode::OK); -} - -#[tokio::test] -async fn test_create_duplicate_permission_should_return_409() { - let state = create_mock_app_state().await; - let repo = PermissionsRepository::new(&state); - let name = "Duplicate Permission"; - let id = create_dummy_permission(&repo, name).await; - let server = create_test_app(state.clone()); - let payload = PermissionsRequestDto { name: name.into() }; - let res = server.post("/v1/permissions/create").json(&payload).await; - assert_eq!(res.status_code(), StatusCode::CONFLICT); - delete_dummy_permission(&repo, id).await; -} - -#[tokio::test] -async fn test_delete_nonexistent_permission_should_return_404() { - let state = create_mock_app_state().await; - let server = create_test_app(state); - let res = server.delete("/v1/permissions/delete/nonexistent-id").await; - assert_eq!(res.status_code(), StatusCode::NOT_FOUND); -} - -#[tokio::test] -async fn test_create_permission_with_empty_name_should_return_400() { - let state = create_mock_app_state().await; - let server = create_test_app(state); - let payload = PermissionsRequestDto { name: "".into() }; - let res = server.post("/v1/permissions/create").json(&payload).await; - dbg!(res.text()); - assert_eq!(res.status_code(), StatusCode::BAD_REQUEST); -} - -#[tokio::test] -async fn test_update_nonexistent_permission_should_return_404() { - let state = create_mock_app_state().await; - let server = create_test_app(state); - let payload = PermissionsRequestDto { - name: "Nonexistent".into(), - }; - let res = server - .put("/v1/permissions/update/non-exist") - .json(&payload) - .await; - dbg!(res.text()); - assert_eq!(res.status_code(), StatusCode::NOT_FOUND); -} - -#[tokio::test] -async fn test_get_permission_by_invalid_id_should_return_404() { - let state = create_mock_app_state().await; - let server = create_test_app(state); - let res = server.get("/v1/permissions/detail/invalid-id").await; - dbg!(res.text()); - assert_eq!(res.status_code(), StatusCode::NOT_FOUND); -} - -#[tokio::test] -async fn test_get_permission_list_with_invalid_pagination_should_return_400() { - let state = create_mock_app_state().await; - let server = create_test_app(state); - let res = server.get("/v1/permissions?page=abc&per_page=xyz").await; - dbg!(res.text()); - assert_eq!(res.status_code(), StatusCode::BAD_REQUEST); -} diff --git a/tests/src/iam/roles/mod.rs b/tests/src/iam/roles/mod.rs index 1623051..fc23110 100644 --- a/tests/src/iam/roles/mod.rs +++ b/tests/src/iam/roles/mod.rs @@ -1,4 +1,2 @@ #[cfg(test)] -pub mod roles_controller_test; -#[cfg(test)] pub mod roles_repository_test; diff --git a/tests/src/iam/roles/roles_controller_test.rs b/tests/src/iam/roles/roles_controller_test.rs deleted file mode 100644 index 3979817..0000000 --- a/tests/src/iam/roles/roles_controller_test.rs +++ /dev/null @@ -1,174 +0,0 @@ -use crate::AppState; -use crate::{ - RolesRequestCreateDto, RolesRequestUpdateDto, create_mock_app_state, roles_router, - v1::roles_repository::RolesRepository, -}; -use axum::Extension; -use axum::Router; -use axum::http::StatusCode; -use axum_test::TestServer; - -pub fn create_test_app(state: AppState) -> TestServer { - let app = Router::new() - .nest("/v1/roles", roles_router()) - .layer(Extension(state)); - - 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; - let app = axum::Router::new() - .nest("/v1/roles", roles_router()) - .layer(axum::Extension(state.clone())); - let server = TestServer::new(app).unwrap(); - let res = server.get("/v1/roles?page=1&per_page=10").await; - assert_eq!(res.status_code(), StatusCode::OK); -} - -#[tokio::test] -async fn test_post_create_role_should_return_201() { - let state = create_mock_app_state().await; - let app = axum::Router::new() - .nest("/v1/roles", roles_router()) - .layer(axum::Extension(state.clone())); - let server = TestServer::new(app).unwrap(); - let payload = RolesRequestCreateDto { - 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] -async fn test_put_update_role_should_return_200() { - let state = create_mock_app_state().await; - let repo = RolesRepository::new(&state); - let _ = repo - .query_create_role(RolesRequestCreateDto { - name: "Role To Update".into(), - permissions: vec![], - }) - .await - .unwrap(); - let existing = repo - .query_role_by_name("Role To Update".into()) - .await - .unwrap(); - let id = existing.id.clone(); - let server = create_test_app(state.clone()); - let payload = RolesRequestUpdateDto { - name: Some("Updated Role".into()), - permissions: Some(vec![]), - overwrite: None, - }; - let res = server - .put(&format!("/v1/roles/update/{}", id)) - .json(&payload) - .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] -async fn test_delete_role_should_return_200() { - let state = create_mock_app_state().await; - let repo = RolesRepository::new(&state); - let _ = repo - .query_create_role(RolesRequestCreateDto { - name: "Role To Delete".into(), - permissions: vec![], - }) - .await - .unwrap(); - let existing = repo - .query_role_by_name("Role To Delete".into()) - .await - .unwrap(); - let id = existing.id; - let server = create_test_app(state); - let res = server.delete(&format!("/v1/roles/delete/{}", id)).await; - 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![]), - overwrite: None, - }; - 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 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/tests/src/iam/roles/roles_repository_test.rs b/tests/src/iam/roles/roles_repository_test.rs index adcea17..98fcd0f 100644 --- a/tests/src/iam/roles/roles_repository_test.rs +++ b/tests/src/iam/roles/roles_repository_test.rs @@ -10,7 +10,6 @@ use crate::{ }, }; use surrealdb::Uuid; -use surrealdb::sql::Thing; #[tokio::test] async fn test_query_create_role_should_succeed() { @@ -19,15 +18,15 @@ async fn test_query_create_role_should_succeed() { let role_repo = RolesRepository::new(&state); let perm_id = Uuid::new_v4().to_string(); let permission = PermissionsSchema { - id: Thing::from((ResourceEnum::Permissions.to_string(), perm_id.clone())), + id: make_thing(&ResourceEnum::Permissions.to_string(), &perm_id.clone()), name: "Read Quiz".into(), is_deleted: false, created_at: Some(get_iso_date()), - updated_at: None, + updated_at: Some(get_iso_date()), }; perm_repo.query_create_permission(permission).await.unwrap(); let payload = RolesRequestCreateDto { - name: "Student".into(), + name: "User".into(), permissions: vec![perm_id.clone()], }; let result = role_repo.query_create_role(payload).await; diff --git a/tests/src/iam/users/mod.rs b/tests/src/iam/users/mod.rs index f0068cf..e12e8f4 100644 --- a/tests/src/iam/users/mod.rs +++ b/tests/src/iam/users/mod.rs @@ -1,4 +1,2 @@ #[cfg(test)] -pub mod users_controller_test; -#[cfg(test)] pub mod users_repository_test; diff --git a/tests/src/iam/users/users_controller_test.rs b/tests/src/iam/users/users_controller_test.rs deleted file mode 100644 index 69977de..0000000 --- a/tests/src/iam/users/users_controller_test.rs +++ /dev/null @@ -1,602 +0,0 @@ -use crate::{ - RolesEnum, RolesRepository, UsersActiveInactiveRequestDto, UsersCreateRequestDto, - UsersRepository, UsersUpdateRequestDto, users_router, -}; -use axum::{Extension, http::StatusCode}; -use axum_test::TestServer; -use imphnen_utils::create_mock_app_state; -use surrealdb::Uuid; - -#[tokio::test] -async fn test_get_user_list_should_return_200() { - let state = create_mock_app_state().await; - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state)); - let server = TestServer::new(app).unwrap(); - let res = server.get("/v1/users?page=1&per_page=10").await; - let status = res.status_code(); - assert_eq!(status, StatusCode::OK); -} - -#[tokio::test] -async fn test_list_users_should_fail_with_invalid_per_page() { - let state = create_mock_app_state().await; - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state)); - let server = TestServer::new(app).unwrap(); - let res = server.get("/v1/users?page=1&per_page=0").await; - assert_eq!(res.status_code(), StatusCode::BAD_REQUEST); -} - -#[tokio::test] -async fn test_list_users_should_fail_with_invalid_page() { - let state = create_mock_app_state().await; - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state)); - let server = TestServer::new(app).unwrap(); - let res = server.get("/v1/users?page=0&per_page=10").await; - dbg!(res.text()); - dbg!(res.status_code()); - assert_eq!(res.status_code(), StatusCode::BAD_REQUEST); -} - -#[tokio::test] -async fn test_list_users_should_ignore_invalid_sort_field() { - let state = create_mock_app_state().await; - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state)); - let server = TestServer::new(app).unwrap(); - let res = server - .get("/v1/users?page=1&per_page=10&sort_by=invalid_field") - .await; - assert_eq!(res.status_code(), StatusCode::OK); -} - -#[tokio::test] -async fn test_list_users_with_search_no_match_should_return_empty() { - let state = create_mock_app_state().await; - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state)); - let server = TestServer::new(app).unwrap(); - let res = server - .get("/v1/users?page=1&per_page=10&search=nonexistinguserxyz") - .await; - let body: serde_json::Value = res.json(); - assert_eq!(res.status_code(), StatusCode::OK); - assert_eq!(body["data"].as_array().unwrap().len(), 0); -} - -#[tokio::test] -async fn test_list_users_should_return_empty_on_invalid_filter() { - let state = create_mock_app_state().await; - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state)); - let server = TestServer::new(app).unwrap(); - let res = server - .get("/v1/users?page=1&per_page=10&filter_by=is_active&filter=maybe") - .await; - let body: serde_json::Value = res.json(); - assert_eq!(res.status_code(), StatusCode::OK); - assert_eq!(body["data"].as_array().unwrap().len(), 0); -} - -#[tokio::test] -async fn test_get_user_list_with_search_should_return_200() { - let state = create_mock_app_state().await; - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state)); - let server = TestServer::new(app).unwrap(); - let res = server - .get("/v1/users?page=1&per_page=10&search=maulana") - .await; - let status = res.status_code(); - dbg!(res.text()); - assert_eq!(status, StatusCode::OK); -} - -#[tokio::test] -async fn test_create_user_should_return_201() { - let state = create_mock_app_state().await; - let role_repo = RolesRepository::new(&state); - let role = role_repo - .query_role_by_name(RolesEnum::User.to_string()) - .await - .unwrap(); - let role_id = role.id; - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state)); - let server = TestServer::new(app).unwrap(); - let payload = UsersCreateRequestDto { - fullname: "Create Data #1".into(), - email: format!("test-{}@create.com", Uuid::new_v4()).into(), - role_id, - password: "Password1!".into(), - phone_number: "081234567890".into(), - is_active: true, - }; - let res = server.post("/v1/users/create").json(&payload).await; - assert_eq!(res.status_code(), StatusCode::CREATED); -} - -#[tokio::test] -async fn test_get_user_detail_should_return_200() { - let state = create_mock_app_state().await; - let role_repo = RolesRepository::new(&state); - let role = role_repo - .query_role_by_name(RolesEnum::User.to_string()) - .await - .unwrap(); - let role_id = role.id; - let repo = UsersRepository::new(&state); - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state.clone())); - let server = TestServer::new(app).unwrap(); - let payload = UsersCreateRequestDto { - fullname: "Detail User".into(), - email: "detail@test.com".into(), - role_id, - password: "Password1!".into(), - phone_number: "081234567890".into(), - is_active: true, - }; - server.post("/v1/users/create").json(&payload).await; - let user = repo.query_user_by_email(payload.email).await.unwrap(); - let user_id = user.id.id.to_raw(); - let res = server.get(&format!("/v1/users/detail/{}", user_id)).await; - assert_eq!(res.status_code(), StatusCode::OK); -} - -#[tokio::test] -async fn test_delete_user_should_return_200() { - let state = create_mock_app_state().await; - let role_repo = RolesRepository::new(&state); - let role = role_repo - .query_role_by_name(RolesEnum::User.to_string()) - .await - .expect("Failed to get role"); - let repo = UsersRepository::new(&state); - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state.clone())); - let server = TestServer::new(app).unwrap(); - let payload = UsersCreateRequestDto { - fullname: "Test Delete Me".into(), - email: "test@delete-me.com".into(), - password: "Password1!".into(), - role_id: role.id.clone(), - phone_number: "081234567890".into(), - is_active: true, - }; - let create_res = server.post("/v1/users/create").json(&payload).await; - assert_eq!(create_res.status_code(), StatusCode::CREATED); - let detail_res = repo.query_user_by_email(payload.email.clone()).await; - assert!(detail_res.is_ok(), "User not found after creation"); - let user = detail_res.unwrap(); - let user_id_for_delete = user.id.id.to_raw(); - let delete_res = server - .delete(&format!("/v1/users/delete/{}", user_id_for_delete)) - .await; - assert_eq!(delete_res.status_code(), StatusCode::OK); -} - -#[tokio::test] -async fn test_activate_user_should_return_200() { - let state = create_mock_app_state().await; - let role_repo = RolesRepository::new(&state); - let role = role_repo - .query_role_by_name(RolesEnum::User.to_string()) - .await - .unwrap(); - let role_id = role.id; - let repo = UsersRepository::new(&state); - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state.clone())); - let server = TestServer::new(app).unwrap(); - let payload = UsersCreateRequestDto { - fullname: "Inactive User".into(), - email: format!("inactive-{}@test.com", Uuid::new_v4()).into(), - password: "Password1!".into(), - role_id, - phone_number: "081234567890".into(), - is_active: false, - }; - let res_create = server.post("/v1/users/create").json(&payload).await; - dbg!(res_create.text()); - let user = repo.query_user_by_email(payload.email).await.unwrap(); - dbg!(user.fullname); - let user_id = user.id.id.to_raw(); - let res = server - .put(&format!("/v1/users/activate/{}", user_id)) - .json(&UsersActiveInactiveRequestDto { is_active: true }) - .await; - dbg!(res.text()); - assert_eq!(res.status_code(), StatusCode::OK); -} - -#[tokio::test] -async fn test_update_user_should_return_200() { - let state = create_mock_app_state().await; - let role_repo = RolesRepository::new(&state); - let role = role_repo - .query_role_by_name(RolesEnum::User.to_string()) - .await - .unwrap(); - let role_id = role.id.clone(); - let repo = UsersRepository::new(&state); - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state.clone())); - let server = TestServer::new(app).unwrap(); - let unique_email = format!("update_{}@test.com", Uuid::new_v4()); - let payload = UsersCreateRequestDto { - fullname: "Old Name".into(), - email: unique_email.clone(), - password: "Password1!".into(), - role_id: role_id.clone(), - phone_number: "081234567890".into(), - is_active: true, - }; - let create_res = server.post("/v1/users/create").json(&payload).await; - assert_eq!(create_res.status_code(), StatusCode::CREATED); - let user = repo.query_user_by_email(unique_email).await.unwrap(); - let user_id = user.id.id.to_raw(); - let update_payload = UsersUpdateRequestDto { - fullname: "Updated Name".into(), - email: payload.email.clone(), - phone_number: "081234567890".into(), - role_id: user.role.id.id.to_raw(), - is_active: true, - gender: Some("Laki-laki".into()), - birthdate: Some("2000-01-01".into()), - avatar: None, - }; - let res = server - .put(&format!("/v1/users/update/{}", user_id)) - .json(&update_payload) - .await; - let status = res.status_code(); - let body = res.text(); - assert_eq!(status, StatusCode::OK, "Response body: {}", body); -} - -#[tokio::test] -async fn test_create_user_should_fail_if_email_taken() { - let state = create_mock_app_state().await; - let role_repo = RolesRepository::new(&state); - let role = role_repo - .query_role_by_name(RolesEnum::User.to_string()) - .await - .unwrap(); - let role_id = role.id.clone(); - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state.clone())); - let server = TestServer::new(app).unwrap(); - let email = format!("test_{}@example.com", Uuid::new_v4()); - let payload = UsersCreateRequestDto { - fullname: "User Satu".into(), - email: email.clone(), - password: "Password1!".into(), - role_id, - phone_number: "081234567890".into(), - is_active: true, - }; - let res1 = server.post("/v1/users/create").json(&payload).await; - assert_eq!(res1.status_code(), StatusCode::CREATED); - let res2 = server.post("/v1/users/create").json(&payload).await; - let body2 = res2.text(); - assert_eq!(res2.status_code(), StatusCode::BAD_REQUEST); - assert!(body2.contains("User already exists")); -} - -#[tokio::test] -async fn test_get_user_detail_should_fail_if_not_found() { - let state = create_mock_app_state().await; - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state)); - let server = TestServer::new(app).unwrap(); - let res = server.get("/v1/users/detail/non-existent-id").await; - assert_eq!(res.status_code(), StatusCode::NOT_FOUND); - let body = res.text(); - assert!(body.contains("User not found")); -} - -#[tokio::test] -async fn test_delete_user_should_fail_if_already_deleted() { - let state = create_mock_app_state().await; - let role_repo = RolesRepository::new(&state); - let role = role_repo - .query_role_by_name(RolesEnum::User.to_string()) - .await - .unwrap(); - let role_id = role.id.clone(); - let repo = UsersRepository::new(&state); - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state.clone())); - let server = TestServer::new(app).unwrap(); - let payload = UsersCreateRequestDto { - fullname: "To Be Deleted".into(), - email: "delete-me@example.com".into(), - role_id, - password: "Password1!".into(), - phone_number: "081234567890".into(), - is_active: true, - }; - server.post("/v1/users/create").json(&payload).await; - let user = repo.query_user_by_email(payload.email).await.unwrap(); - let id = user.id.id.to_raw(); - let res1 = server.delete(&format!("/v1/users/delete/{}", id)).await; - assert_eq!(res1.status_code(), StatusCode::OK); - let res2 = server.delete(&format!("/v1/users/delete/{}", id)).await; - assert_eq!(res2.status_code(), StatusCode::BAD_REQUEST); - let body = res2.text(); - assert!(body.contains("User already deleted")); -} - -#[tokio::test] -async fn test_update_user_should_fail_if_user_not_found() { - let state = create_mock_app_state().await; - let role_repo = RolesRepository::new(&state); - let role = role_repo - .query_role_by_name(RolesEnum::User.to_string()) - .await - .unwrap(); - let role_id = role.id.clone(); - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state)); - let server = TestServer::new(app).unwrap(); - let update_payload = UsersUpdateRequestDto { - fullname: "Does Not Exist".into(), - email: "nonexistent@test.com".into(), - phone_number: "081234567890".into(), - role_id, - is_active: true, - gender: Some("Laki-laki".into()), - birthdate: Some("2000-01-01".into()), - avatar: None, - }; - let res = server - .put("/v1/users/update/non-existent-id") - .json(&update_payload) - .await; - assert_eq!(res.status_code(), StatusCode::BAD_REQUEST); - let body = res.text(); - assert!(body.contains("User not found")); -} - -#[tokio::test] -async fn test_activate_user_should_fail_if_user_not_found() { - let state = create_mock_app_state().await; - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state)); - let server = TestServer::new(app).unwrap(); - let payload = UsersActiveInactiveRequestDto { is_active: true }; - let res = server - .put("/v1/users/activate/non-existent-id") - .json(&payload) - .await; - assert_eq!(res.status_code(), StatusCode::BAD_REQUEST); - let body = res.text(); - assert!(body.contains("User not found")); -} - -#[tokio::test] -async fn test_create_user_should_fail_if_payload_invalid() { - let state = create_mock_app_state().await; - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state)); - let server = TestServer::new(app).unwrap(); - let invalid_payload = serde_json::json!({}); - let res = server.post("/v1/users/create").json(&invalid_payload).await; - assert_eq!(res.status_code(), StatusCode::UNPROCESSABLE_ENTITY); -} - -#[tokio::test] -async fn test_list_users_should_fail_with_invalid_pagination() { - let state = create_mock_app_state().await; - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state)); - let server = TestServer::new(app).unwrap(); - let res = server.get("/v1/users?page=0&per_page=0").await; - assert_eq!(res.status_code(), StatusCode::BAD_REQUEST); - let body = res.text(); - assert!( - body.contains("Invalid pagination") || body.contains("per_page"), - "Expected pagination error, got: {body}" - ); -} - -#[tokio::test] -async fn test_list_users_should_fallback_on_invalid_order() { - let state = create_mock_app_state().await; - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state)); - let server = TestServer::new(app).unwrap(); - let res = server - .get("/v1/users?page=1&per_page=10&sort_by=email&order=invalid") - .await; - assert_eq!(res.status_code(), StatusCode::OK); -} - -#[tokio::test] -async fn test_list_users_with_invalid_filter_by_should_return_empty() { - let state = create_mock_app_state().await; - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state)); - let server = TestServer::new(app).unwrap(); - let res = server - .get("/v1/users?page=1&per_page=10&filter_by=unknown_field&filter=value") - .await; - assert_eq!(res.status_code(), StatusCode::OK); - let body: serde_json::Value = res.json(); - assert_eq!(body["data"].as_array().unwrap().len(), 0); -} - -#[tokio::test] -async fn test_user_detail_should_fail_with_invalid_id_format() { - let state = create_mock_app_state().await; - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state)); - let server = TestServer::new(app).unwrap(); - let res = server.get("/v1/users/detail/!@#invalid-id").await; - assert_eq!(res.status_code(), StatusCode::NOT_FOUND); - let body = res.text(); - assert!(body.contains("User not found") || body.contains("not found")); -} - -#[tokio::test] -async fn test_user_detail_should_fail_if_user_not_found() { - let state = create_mock_app_state().await; - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state)); - let server = TestServer::new(app).unwrap(); - let res = server.get("/v1/users/detail/random-id-123456").await; - assert_eq!(res.status_code(), StatusCode::NOT_FOUND); - let body = res.text(); - assert!(body.contains("User not found")); -} - -#[tokio::test] -async fn test_user_detail_should_fail_if_user_is_soft_deleted() { - let state = create_mock_app_state().await; - let role_repo = RolesRepository::new(&state); - let role = role_repo - .query_role_by_name(RolesEnum::User.to_string()) - .await - .unwrap(); - let role_id = role.id.clone(); - let repo = UsersRepository::new(&state); - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state.clone())); - let server = TestServer::new(app).unwrap(); - let payload = UsersCreateRequestDto { - role_id, - fullname: "Soft Deleted".into(), - email: "softdeleted@test.com".into(), - password: "Password1!".into(), - phone_number: "081234567890".into(), - is_active: true, - }; - server.post("/v1/users/create").json(&payload).await; - let user = repo.query_user_by_email(payload.email).await.unwrap(); - let user_id = user.id.id.to_raw(); - let _ = repo.query_delete_user(user_id.clone()).await.unwrap(); - let res = server.get(&format!("/v1/users/detail/{}", user_id)).await; - dbg!(res.text()); - assert_eq!(res.status_code(), StatusCode::NOT_FOUND); - let body = res.text(); - assert!(body.contains("User not found")); -} - -#[tokio::test] -async fn test_update_user_should_fail_if_user_is_deleted() { - let state = create_mock_app_state().await; - let role_repo = RolesRepository::new(&state); - let role = role_repo - .query_role_by_name(RolesEnum::User.to_string()) - .await - .unwrap(); - let role_id = role.id.clone(); - let repo = UsersRepository::new(&state); - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state.clone())); - let server = TestServer::new(app).unwrap(); - let payload = UsersCreateRequestDto { - role_id: role_id.clone(), - fullname: "To Be Deleted".into(), - email: "deleteduser@test.com".into(), - password: "Password1!".into(), - phone_number: "081234567890".into(), - is_active: true, - }; - server.post("/v1/users/create").json(&payload).await; - let user = repo.query_user_by_email(payload.email).await.unwrap(); - let user_id = user.id.id.to_raw(); - let _ = repo.query_delete_user(user_id.clone()).await.unwrap(); - let update_payload = UsersUpdateRequestDto { - role_id: role_id.clone(), - fullname: "Should Fail".into(), - email: "deleteduser@test.com".into(), - phone_number: "081234567890".into(), - is_active: true, - gender: Some("Laki-laki".into()), - birthdate: Some("2000-01-01".into()), - avatar: None, - }; - let res = server - .put(&format!("/v1/users/update/{}", user_id)) - .json(&update_payload) - .await; - assert_eq!(res.status_code(), StatusCode::BAD_REQUEST); - let body = res.text(); - assert!(body.contains("User already deleted")); -} - -#[tokio::test] -async fn test_update_user_should_fail_if_payload_invalid() { - let state = create_mock_app_state().await; - let role_repo = RolesRepository::new(&state); - let role = role_repo - .query_role_by_name(RolesEnum::User.to_string()) - .await - .unwrap(); - let role_id = role.id.clone(); - let repo = UsersRepository::new(&state); - let app = axum::Router::new() - .nest("/v1/users", users_router()) - .layer(Extension(state.clone())); - let server = TestServer::new(app).unwrap(); - let payload = UsersCreateRequestDto { - fullname: "Invalid Payload".into(), - email: "invalid@test.com".into(), - role_id: role_id.clone(), - password: "Password1!".into(), - phone_number: "081234567890".into(), - is_active: true, - }; - server.post("/v1/users/create").json(&payload).await; - let user = repo.query_user_by_email(payload.email).await.unwrap(); - let user_id = user.id.id.to_raw(); - let update_payload = UsersUpdateRequestDto { - fullname: "".into(), - email: "invalid@test.com".into(), - phone_number: "081234567890".into(), - is_active: true, - role_id: role_id.clone(), - gender: Some("Laki-laki".into()), - birthdate: Some("2000-01-01".into()), - avatar: None, - }; - let res = server - .put(&format!("/v1/users/update/{}", user_id)) - .json(&update_payload) - .await; - assert_eq!(res.status_code(), StatusCode::BAD_REQUEST); - let body = res.text(); - assert!( - body.contains("Fullname") || body.contains("at least have 2 character"), - "Gagal validasi fullname: {body}" - ); -} diff --git a/tests/src/iam/users/users_repository_test.rs b/tests/src/iam/users/users_repository_test.rs index 4ab1849..1c4ba75 100644 --- a/tests/src/iam/users/users_repository_test.rs +++ b/tests/src/iam/users/users_repository_test.rs @@ -24,9 +24,7 @@ async fn test_create_and_get_user() { create_test_user(&email, "Test User", true, &get_role_id(&app_state).await); let create_result = repo.query_create_user(user.clone()).await; assert!(create_result.is_ok()); - let fetched = repo - .query_user_by_email("testuser@example.com".into()) - .await; + let fetched = repo.query_user_by_email(email.clone().into()).await; assert!(fetched.is_ok()); assert_eq!(fetched.unwrap().email, email.clone()); } @@ -91,7 +89,7 @@ async fn test_query_user_list_basic() { async fn test_query_delete_user() { let app_state = create_mock_app_state().await; let repo = UsersRepository::new(&app_state); - let email = "deleteuser@example.com"; + let email = &generate_unique_email("deleteuser"); let user = create_test_user(email, "Delete User", true, &get_role_id(&app_state).await); repo.query_create_user(user.clone()).await.unwrap(); @@ -128,7 +126,7 @@ async fn test_delete_user_twice_should_fail_on_second_attempt() { assert!(first.is_ok()); let second = repo.query_delete_user(user.id.id.to_raw()).await; assert!(second.is_err()); - assert_eq!(second.unwrap_err().to_string(), "User already deleted"); + assert_eq!(second.unwrap_err().to_string(), "User not found"); } #[tokio::test]