From 3ae9dbd7174063796bc572cf6fddb25c9d0c9bbb Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin Date: Sun, 30 Mar 2025 14:13:45 +0700 Subject: [PATCH] feat: success test --- src/apps/v1/users/users_controller_test.rs | 27 +++++--- src/apps/v1/users/users_repository.rs | 16 +++-- src/apps/v1/users/users_repository_test.rs | 2 +- src/apps/v1/users/users_service.rs | 3 +- src/bin/seed_users.rs | 75 ++++++++++++++++------ 5 files changed, 87 insertions(+), 36 deletions(-) diff --git a/src/apps/v1/users/users_controller_test.rs b/src/apps/v1/users/users_controller_test.rs index 0cb125d..a3f60fd 100644 --- a/src/apps/v1/users/users_controller_test.rs +++ b/src/apps/v1/users/users_controller_test.rs @@ -38,6 +38,8 @@ async fn test_list_users_should_fail_with_invalid_page() { .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); } @@ -114,7 +116,7 @@ async fn test_create_user_should_return_201() { let server = TestServer::new(app).unwrap(); let payload = UsersCreateRequestDto { fullname: "Create Data #1".into(), - email: "test@create.com".into(), + email: format!("test-{}@create.com", Uuid::new_v4()).into(), role_id, password: "Password1!".into(), student_type: "general".into(), @@ -247,9 +249,10 @@ async fn test_update_user_should_return_200() { .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: "update@test.com".into(), + email: unique_email.clone(), password: "Password1!".into(), role_id: role_id.clone(), student_type: "general".into(), @@ -258,15 +261,16 @@ async fn test_update_user_should_return_200() { referral_code: None, referred_by: None, }; - server.post("/v1/users/create").json(&payload).await; - let user = repo.query_user_by_email(payload.email).await.unwrap(); + 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: "update@test.com".into(), + email: payload.email.clone(), student_type: "general".into(), phone_number: "081234567890".into(), - role_id: role_id.clone(), + role_id: user.role.id.id.to_raw(), is_active: true, referral_code: None, referred_by: None, @@ -281,7 +285,8 @@ async fn test_update_user_should_return_200() { .json(&update_payload) .await; let status = res.status_code(); - assert_eq!(status, StatusCode::OK); + let body = res.text(); + assert_eq!(status, StatusCode::OK, "Response body: {}", body); } #[tokio::test] @@ -297,9 +302,10 @@ async fn test_create_user_should_fail_if_email_taken() { .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: "taken@example.com".into(), + email: email.clone(), password: "Password1!".into(), student_type: "general".into(), role_id, @@ -362,7 +368,7 @@ async fn test_delete_user_should_fail_if_already_deleted() { 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 not found")); + assert!(body.contains("User already deleted")); } #[tokio::test] @@ -531,6 +537,7 @@ async fn test_user_detail_should_fail_if_user_is_soft_deleted() { 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")); @@ -586,7 +593,7 @@ async fn test_update_user_should_fail_if_user_is_deleted() { .await; assert_eq!(res.status_code(), StatusCode::BAD_REQUEST); let body = res.text(); - assert!(body.contains("User not found")); + assert!(body.contains("User already deleted")); } #[tokio::test] diff --git a/src/apps/v1/users/users_repository.rs b/src/apps/v1/users/users_repository.rs index fb508a8..5abea16 100644 --- a/src/apps/v1/users/users_repository.rs +++ b/src/apps/v1/users/users_repository.rs @@ -7,7 +7,7 @@ use crate::{ PermissionsItemDto, PermissionsItemDtoRaw, ResourceEnum, ResponseListSuccessDto, RolesItemDto, RolesItemDtoRaw, }; -use anyhow::{bail, Result}; +use anyhow::{anyhow, bail, Result}; pub struct UsersRepository<'a> { state: &'a AppState, @@ -45,7 +45,14 @@ impl<'a> UsersRepository<'a> { }; let limit = meta.per_page.unwrap_or(10); - let start = (meta.page.unwrap_or(1) - 1) * limit; + let page = meta.page.unwrap_or(1); + + // Validate pagination parameters explicitly + if page < 1 || limit < 1 { + return Err(anyhow!("Invalid pagination parameters")); + } + + let start = (page - 1) * limit; let select_query = format!( " @@ -98,7 +105,7 @@ impl<'a> UsersRepository<'a> { referral_code: user.referral_code, student_type: user.student_type, is_active: user.is_active, - role: user.role.unwrap_or_else(|| "-".into()), // Handle role safely + role: user.role.unwrap_or_else(|| "-".into()), }) .collect::>(); @@ -247,6 +254,7 @@ impl<'a> UsersRepository<'a> { let merged = UsersSchema { password: existing.password, created_at: existing.created_at, + role: make_thing("app_roles", &existing.role.id), ..data.clone() }; let record: Option = db.update(record_key).merge(merged).await?; @@ -264,7 +272,7 @@ impl<'a> UsersRepository<'a> { let db = &self.state.surrealdb_ws; let user = self.query_user_by_email(email.clone()).await?; if user.is_deleted { - bail!("User not found"); + bail!("User already deleted"); } let record_key = get_id(&user.id)?; let record: Option = db diff --git a/src/apps/v1/users/users_repository_test.rs b/src/apps/v1/users/users_repository_test.rs index 00c0725..3b9fbb3 100644 --- a/src/apps/v1/users/users_repository_test.rs +++ b/src/apps/v1/users/users_repository_test.rs @@ -210,7 +210,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 not found"); + assert_eq!(second.unwrap_err().to_string(), "User already deleted"); } #[tokio::test] diff --git a/src/apps/v1/users/users_service.rs b/src/apps/v1/users/users_service.rs index fc0f8fd..3be4ba6 100644 --- a/src/apps/v1/users/users_service.rs +++ b/src/apps/v1/users/users_service.rs @@ -35,7 +35,7 @@ impl UsersService { pub async fn get_user_by_id(state: &AppState, id: String) -> Response { let repo = UsersRepository::new(state); match repo.query_user_by_id(id).await { - Ok(user) => success_response(ResponseSuccessDto { + Ok(user) if !user.is_deleted => success_response(ResponseSuccessDto { data: UsersDetailItemDto { id: user.id, role: user.role, @@ -54,6 +54,7 @@ impl UsersService { birthdate: user.birthdate, }, }), + Ok(_) => common_response(StatusCode::NOT_FOUND, "User not found"), Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()), } } diff --git a/src/bin/seed_users.rs b/src/bin/seed_users.rs index 6ca9c8f..71ecfbd 100644 --- a/src/bin/seed_users.rs +++ b/src/bin/seed_users.rs @@ -1,7 +1,30 @@ -use najm_course_api::{get_iso_date, make_thing, Env}; -use serde_json::json; +use najm_course_api::{get_iso_date, hash_password, Env}; +use serde::{Deserialize, Serialize}; use std::error::Error; -use surrealdb::{engine::remote::ws::Ws, opt::auth::Root, Surreal}; +use surrealdb::{engine::remote::ws::Ws, opt::auth::Root, sql::Thing, Surreal}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct UsersSchema { + pub id: Thing, + pub fullname: String, + pub email: String, + pub password: String, + pub avatar: Option, + pub phone_number: String, + pub referral_code: Option, + pub referred_by: Option, + pub identity_number: Option, + pub is_active: bool, + pub is_deleted: bool, + pub student_type: String, + pub religion: Option, + pub gender: Option, + pub birthdate: Option, + pub is_profile_completed: bool, + pub role: Thing, + pub created_at: String, + pub updated_at: String, +} #[tokio::main] async fn main() -> Result<(), Box> { @@ -15,6 +38,7 @@ async fn main() -> Result<(), Box> { db.use_ns(env.surrealdb_namespace) .use_db(env.surrealdb_dbname) .await?; + let users = vec![ ( "c3b1d6a8-8d4f-4b36-b789-2e532ec7a7b2", @@ -35,26 +59,37 @@ async fn main() -> Result<(), Box> { "5713cb37-dc02-4e87-8048-d7a41d352059", ), ]; - for (id, email, name, role_id) in users { - db.query("CREATE type::thing('app_users', $id) CONTENT $data") - .bind(("id", id)) - .bind(( - "data", - json!({ - "email": email, - "name": name, - "phone_number": "081234567890", - "student_type": "TNI", - "role": make_thing("app_roles", role_id), - "is_deleted": false, - "created_at": get_iso_date(), - "updated_at": get_iso_date(), - }), - )) + + for (id, email, fullname, role_id) in users { + let user = UsersSchema { + id: Thing::from(("app_users", id)), + fullname: fullname.into(), + email: email.into(), + password: hash_password("password").unwrap(), + avatar: None, + phone_number: "081234567890".into(), + referral_code: None, + referred_by: None, + identity_number: None, + is_active: true, + is_deleted: false, + student_type: "TNI".into(), + religion: None, + gender: None, + birthdate: None, + is_profile_completed: false, + role: Thing::from(("app_roles", role_id)), + created_at: get_iso_date(), + updated_at: get_iso_date(), + }; + + db.create::>(("app_users", id)) + .content(user) .await?; - println!("✅ Inserted user: {} ({})", name, email); + println!("✅ Inserted user: {} ({})", fullname, email); } + println!("✅ Semua users berhasil disimpan ke SurrealDB!"); Ok(()) }