Add comprehensive tests for mentor repository and authentication

- Implemented tests for creating, retrieving, updating, and deleting mentors in `mentor_repository_test.rs`.
- Added tests for user authentication, including successful login, invalid email formats, and inactive users in `auth_login_tests.rs`.
- Created a mock test environment setup in `mock_test.rs` to facilitate database operations during tests.
- Updated module structure to include new test files for mentors and authentication.
- Ensured cleanup of the database after tests to maintain isolation and prevent side effects.
This commit is contained in:
MythEclipse
2025-07-21 21:29:04 +07:00
parent e66f1f1634
commit 1a2e0c58b6
103 changed files with 7851 additions and 1509 deletions
@@ -1,6 +1,8 @@
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
use strum_macros::EnumIter;
#[derive(Debug, Clone, PartialEq, Eq, EnumIter)]
pub enum PermissionsEnum {
ReadListUsers,
ReadDetailUsers,
@@ -28,6 +30,16 @@ pub enum PermissionsEnum {
ReadDetailGachaRolls,
CreateGachaRolls,
ExecuteGachaRolls,
DeleteGachaRolls,
ReadListMentors,
ReadDetailMentors,
RegisterMentors,
ReadOwnMentorProfile,
UpdateOwnMentorProfile,
ReadOwnMentorStatus,
UpdateMentors,
VerifyMentors,
DeleteMentors,
}
impl fmt::Display for PermissionsEnum {
@@ -59,8 +71,18 @@ impl fmt::Display for PermissionsEnum {
PermissionsEnum::ReadDetailGachaRolls => "Read Detail Gacha Rolls",
PermissionsEnum::CreateGachaRolls => "Create Gacha Rolls",
PermissionsEnum::ExecuteGachaRolls => "Execute Gacha Rolls",
PermissionsEnum::DeleteGachaRolls => "Delete Gacha Rolls",
PermissionsEnum::ReadListMentors => "Read List Mentors",
PermissionsEnum::ReadDetailMentors => "Read Detail Mentors",
PermissionsEnum::RegisterMentors => "Register Mentors",
PermissionsEnum::ReadOwnMentorProfile => "Read Own Mentor Profile",
PermissionsEnum::UpdateOwnMentorProfile => "Update Own Mentor Profile",
PermissionsEnum::ReadOwnMentorStatus => "Read Own Mentor Status",
PermissionsEnum::UpdateMentors => "Update Mentors",
PermissionsEnum::VerifyMentors => "Verify Mentors",
PermissionsEnum::DeleteMentors => "Delete Mentors",
};
write!(f, "{}", permission_str)
write!(f, "{permission_str}")
}
}
@@ -101,6 +123,20 @@ impl PermissionsEnum {
}
PermissionsEnum::CreateGachaRolls => "18e36c63-fcb7-4877-b911-c5aa611e878f",
PermissionsEnum::ExecuteGachaRolls => "14c6a1cd-5c63-4643-89b5-b1a5f9920cc0",
PermissionsEnum::DeleteGachaRolls => "12345678-ABCD-EFAB-CDEF-0123456789AB",
PermissionsEnum::ReadListMentors => "a1b2c3d4-5e6f-7890-abcd-ef1234567890",
PermissionsEnum::ReadDetailMentors => "b2c3d4e5-6f78-9012-bcde-f23456789012",
PermissionsEnum::RegisterMentors => "c3d4e5f6-7890-1234-cdef-345678901234",
PermissionsEnum::ReadOwnMentorProfile => {
"d4e5f6a7-8901-2345-def0-456789012345"
}
PermissionsEnum::UpdateOwnMentorProfile => {
"e5f6a7b8-9012-3456-ef01-567890123456"
}
PermissionsEnum::ReadOwnMentorStatus => "f6a7b8c9-0123-4567-f012-678901234567",
PermissionsEnum::UpdateMentors => "a7b8c9d0-1234-5678-0123-789012345678",
PermissionsEnum::VerifyMentors => "b8c9d0e1-2345-6789-1234-890123456789",
PermissionsEnum::DeleteMentors => "c9d0e1f2-3456-7890-2345-901234567890",
}
}
}
@@ -1,5 +1,5 @@
use super::PermissionsEnum;
use crate::{common_response, extract_email, AppState, AuthRepository};
use crate::{AppState, AuthRepository, common_response, extract_email};
use axum::{
http::{HeaderMap, StatusCode},
response::Response,
@@ -29,14 +29,16 @@ pub async fn permissions_guard(
let role = raw_user.role;
let role_permissions: Vec<String> =
role.permissions.into_iter().map(|perm| perm.name).collect();
let has_all_permissions = required_permissions
.iter()
.all(|required| role_permissions.contains(&required.to_string()));
if !has_all_permissions {
return Err(common_response(
StatusCode::FORBIDDEN,
"You don't have the required permissions",
));
for required in &required_permissions {
let required_str = required.to_string();
if !role_permissions.contains(&required_str) {
eprintln!(" MISSING REQUIRED PERMISSION: {required_str}");
return Err(common_response(
StatusCode::FORBIDDEN,
"You don't have the required permissions",
));
}
}
Ok(())
}
@@ -4,6 +4,9 @@ use crate::{
};
use anyhow::{Result, bail};
use imphnen_utils::{DetailQueryBuilder, QueryListBuilder, extract_id};
use serde_json;
use std::time::Instant;
use tracing::instrument;
pub struct PermissionsRepository<'a> {
state: &'a AppState,
@@ -14,10 +17,12 @@ impl<'a> PermissionsRepository<'a> {
Self { state }
}
#[instrument(skip(self, meta), err)]
pub async fn query_permission_list(
&self,
meta: MetaRequestDto,
) -> Result<ResponseListSuccessDto<Vec<PermissionsItemDto>>> {
let now = Instant::now();
let raw_result: ResponseListSuccessDto<Vec<PermissionsSchema>> =
QueryListBuilder::new(
&self.state.surrealdb_ws,
@@ -30,6 +35,13 @@ impl<'a> PermissionsRepository<'a> {
.build()
.await?;
let elapsed = now.elapsed();
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
== "development"
{
println!("Query 'query_permission_list' took: {elapsed:.2?}");
}
let transformed_data = raw_result
.data
.into_iter()
@@ -42,25 +54,41 @@ impl<'a> PermissionsRepository<'a> {
})
}
#[instrument(skip(self, id), err)]
pub async fn query_permission_by_id(
&self,
id: String,
) -> Result<PermissionsSchema> {
let now = Instant::now();
let db = &self.state.surrealdb_ws;
let result: Option<PermissionsSchema> = db
.select((ResourceEnum::Permissions.to_string(), id.clone()))
.await?;
let elapsed = now.elapsed();
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
== "development"
{
println!("Query 'query_permission_by_id' took: {elapsed:.2?}");
}
match result {
Some(permission) if !permission.is_deleted => Ok(permission),
_ => bail!("Permission not found"),
}
}
#[instrument(skip(self, id), err)]
pub async fn transformed_query_permission_by_id(
&self,
id: String,
) -> Result<PermissionsItemDto> {
let now = Instant::now();
let raw_result = self.query_permission_by_id(id.clone()).await?;
let elapsed = now.elapsed();
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
== "development"
{
println!("Query 'transformed_query_permission_by_id' took: {elapsed:.2?}");
}
let transformed_data = PermissionsItemDto {
id: extract_id(&raw_result.id),
name: raw_result.name,
@@ -70,43 +98,60 @@ impl<'a> PermissionsRepository<'a> {
Ok(transformed_data)
}
#[instrument(skip(self, name), err)]
pub async fn query_permission_by_name(
&self,
name: String,
) -> Result<PermissionsSchema> {
let now = Instant::now();
let db = &self.state.surrealdb_ws;
let builder = DetailQueryBuilder::new(ResourceEnum::Permissions.to_string())
.with_where("name")
.where_value(name.clone())
.with_where("name", Some(name.clone()))
.with_select_fields(vec!["*"]);
let sql = builder.build();
let result: Option<PermissionsSchema> =
builder.apply_bindings(db.query(sql)).await?.take(0)?;
let elapsed = now.elapsed();
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
== "development"
{
println!("Query 'query_permission_by_name' took: {elapsed:.2?}");
}
match result {
Some(permission) => Ok(permission),
None => bail!("Permission not found"),
}
}
#[instrument(skip(self, data), err)]
pub async fn query_create_permission(
&self,
data: PermissionsSchema,
) -> Result<String> {
let now = Instant::now();
let db = &self.state.surrealdb_ws;
let record: Option<PermissionsSchema> = db
.create(ResourceEnum::Permissions.to_string())
.content(data)
.await?;
let elapsed = now.elapsed();
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
== "development"
{
println!("Query 'query_create_permission' took: {elapsed:.2?}");
}
match record {
Some(_) => Ok("Success create permission".into()),
None => bail!("Failed to create permission"),
}
}
#[instrument(skip(self, data), err)]
pub async fn query_update_permission(
&self,
data: PermissionsSchema,
) -> Result<String> {
let now = Instant::now();
let db = &self.state.surrealdb_ws;
let record_key = get_id(&data.id)?;
let existing = self.query_permission_by_id(data.id.id.to_raw()).await?;
@@ -119,13 +164,21 @@ impl<'a> PermissionsRepository<'a> {
};
let record: Option<PermissionsSchema> =
db.update(record_key).merge(merged).await?;
let elapsed = now.elapsed();
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
== "development"
{
println!("Query 'query_update_permission' took: {elapsed:.2?}");
}
match record {
Some(_) => Ok("Success update permission".into()),
None => bail!("Failed to update permission"),
}
}
#[instrument(skip(self, id), err)]
pub async fn query_delete_permission(&self, id: String) -> Result<String> {
let now = Instant::now();
let db = &self.state.surrealdb_ws;
let permission_id = make_thing(&ResourceEnum::Permissions.to_string(), &id);
let permission = self
@@ -139,6 +192,12 @@ impl<'a> PermissionsRepository<'a> {
.update(record_key)
.merge(serde_json::json!({ "is_deleted": true }))
.await?;
let elapsed = now.elapsed();
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
== "development"
{
println!("Query 'query_delete_permission' took: {elapsed:.2?}");
}
match record {
Some(_) => Ok("Success delete permission".into()),
None => bail!("Failed to delete permission"),
@@ -1,7 +1,7 @@
use crate::{
common_response, make_thing, success_list_response, success_response,
validate_request, AppState, MetaRequestDto, PermissionsRepository,
PermissionsSchema, ResourceEnum, ResponseListSuccessDto, ResponseSuccessDto,
AppState, MetaRequestDto, PermissionsRepository, PermissionsSchema, ResourceEnum,
ResponseListSuccessDto, ResponseSuccessDto, common_response, make_thing,
success_list_response, success_response, validate_request,
};
use axum::http::StatusCode;
use axum::response::Response;