Add minimal test for basic hackathon operations and enhance test utilities

- Introduced a new test module for hackathon-related functionality.
- Implemented a basic test for creating a hackathon using a mock repository.
- Enhanced the test utilities in `lib.rs` for better request handling and response extraction.
- Added a `ServiceClient` struct to facilitate HTTP requests in tests.
- Created a `RequestBuilder` to streamline building and sending requests with headers and JSON bodies.
This commit is contained in:
MythEclipse
2025-10-11 10:58:51 +07:00
parent 466ba3391a
commit c10443f881
33 changed files with 4547 additions and 4082 deletions
+1 -1
View File
@@ -65,7 +65,7 @@ impl AuthServiceTrait for AuthService {
payload: AuthLoginRequestDto,
state: &AppState,
) -> Pin<Box<dyn Future<Output = Response> + Send>> {
let payload = payload;
let state = state.to_owned();
Box::pin(async move {
if let Err((status, message)) = validate_request(&payload) {
@@ -93,4 +93,10 @@ where
fn clone(&self) -> Self {
Self::with_service(self.google_oauth_service.clone())
}
}
impl Default for GoogleOauthController<GoogleOauthServiceImpl<crate::v1::auth::auth_service::AuthService, crate::v1::users::users_service::UsersService>> {
fn default() -> Self {
Self::new()
}
}
@@ -1,6 +1,8 @@
use std::pin::Pin;
use std::future::Future;
use anyhow::Result;
// Type alias to reduce clippy type_complexity warnings for long Future signatures
type GoogleOauthCallbackFut<'a> = Pin<Box<dyn Future<Output = Result<(UsersDetailItemDto, TokenDto), Error>> + Send + 'a>>;
use oauth2::{
AuthUrl, ClientId, ClientSecret, CsrfToken, PkceCodeChallenge, PkceCodeVerifier,
@@ -103,7 +105,7 @@ pub trait GoogleOauthService<A: AuthServiceTrait + Send + Sync + 'static, U: Use
// Removed new() from trait
fn with_services(auth_service: A, users_service: U, env: &'static Env) -> Self;
fn generate_auth_url(&self, custom_redirect_uri: Option<String>) -> (Url, CsrfToken);
fn google_oauth_callback(&self, auth_request: AuthRequest, app_state: &AppState) -> Pin<Box<dyn Future<Output = Result<(UsersDetailItemDto, TokenDto), Error>> + Send + '_>>; // Changed return type
fn google_oauth_callback(&self, auth_request: AuthRequest, app_state: &AppState) -> GoogleOauthCallbackFut<'_>; // Changed return type
}
#[derive(Clone)]
@@ -5,6 +5,7 @@ use axum::{
response::Response, Extension,
};
use axum_extra::headers::{authorization::Bearer, Authorization, HeaderMapExt};
use surrealdb::sql::Thing;
pub async fn permissions_guard(
headers: HeaderMap,
@@ -31,15 +32,22 @@ pub async fn permissions_guard(
})?
.claims;
// Fetch user from database to get permissions
// Fetch user from database to get permissions. Try email first, then try using the sub as a user id.
let user_repo = UsersRepository::new(&state);
let user = match user_repo.query_user_by_email(claims.sub.clone()).await {
Ok(user) => user,
Ok(u) => u,
Err(_) => {
return Err(common_response(
StatusCode::UNAUTHORIZED,
"User not found",
));
// Try treat claims.sub as a Thing id (user id)
let thing = Thing::from(("app_users".to_string(), claims.sub.clone()));
match user_repo.query_user_by_id(&thing).await {
Ok(u2) => u2,
Err(_) => {
return Err(common_response(
StatusCode::UNAUTHORIZED,
"User not found",
));
}
}
}
};
+1 -1
View File
@@ -46,7 +46,7 @@ impl RolesSchema {
.permissions
.as_ref()
.unwrap_or(&vec![])
.into_iter()
.iter()
.filter_map(|perm| {
perm.as_ref().and_then(|p| p.id.as_ref().map(|id| make_thing_from_enum(ResourceEnum::Permissions, &id.id.to_raw())))
})
+9 -12
View File
@@ -318,10 +318,9 @@ pub async fn get_admin_team_list(
axum::extract::Query(meta): axum::extract::Query<MetaRequestDto>,
) -> Response {
let state = state;
with_perms(headers, axum::Extension(state), vec![PermissionsEnum::ReadListTeams], move |_claims, state| {
let response = TeamsService::get_admin_team_list(&state, meta);
response
}).await
with_perms(headers, axum::Extension(state), vec![PermissionsEnum::ReadListTeams], move |_claims, state| {
TeamsService::get_admin_team_list(&state, meta)
}).await
}
#[utoipa::path(
@@ -344,10 +343,9 @@ pub async fn get_admin_team_by_id(
Path(id): Path<String>,
) -> Response {
let state = state;
with_perms(headers, axum::Extension(state), vec![PermissionsEnum::ReadDetailTeams], move |_claims, state| {
let response = TeamsService::get_admin_team_by_id(&state, id);
response
}).await
with_perms(headers, axum::Extension(state), vec![PermissionsEnum::ReadDetailTeams], move |_claims, state| {
TeamsService::get_admin_team_by_id(&state, id)
}).await
}
#[utoipa::path(
@@ -370,10 +368,9 @@ pub async fn get_admin_team_members(
Path(id): Path<String>,
) -> Response {
let state = state;
with_perms(headers, axum::Extension(state), vec![PermissionsEnum::ReadDetailTeams], move |_claims, state| {
let response = TeamsService::get_admin_team_members(&state, id);
response
}).await
with_perms(headers, axum::Extension(state), vec![PermissionsEnum::ReadDetailTeams], move |_claims, state| {
TeamsService::get_admin_team_members(&state, id)
}).await
}
pub fn teams_router() -> Router {
+9 -6
View File
@@ -83,7 +83,7 @@ impl<'a> TeamsRepository<'a> {
if team.is_deleted {
bail!("Team not found");
}
Ok(TeamsDetailQueryDto::from(team))
Ok(team)
}
pub async fn query_create_team(&self, data: TeamsSchema) -> Result<String> {
@@ -102,7 +102,11 @@ impl<'a> TeamsRepository<'a> {
}
match record {
Some(_) => Ok("Success create team".into()),
Some(saved) => {
// Return the created team id as part of the message so callers can parse it in tests
let id = saved.id.id.to_raw();
Ok(format!("Success create team {}", id))
}
None => bail!("Failed to create team"),
}
}
@@ -347,10 +351,9 @@ impl<'a> TeamsRepository<'a> {
let mut conditions = vec!["is_deleted = false".to_string(), "is_active = true".to_string()];
if let Some(open) = search_params.open {
if open {
conditions.push("is_open = true".to_string());
}
if let Some(open) = search_params.open
&& open {
conditions.push("is_open = true".to_string());
}
if let Some(location) = &search_params.location {
+7 -13
View File
@@ -704,17 +704,14 @@ impl TeamsServiceTrait for TeamsService {
}
}
match Self::get_user_info_with_privacy(
if let Ok(mut leader_dto) = Self::get_user_info_with_privacy(
&team.leader_id.id.to_raw(),
&claims.user_id,
is_member,
&state,
).await {
Ok(mut leader_dto) => {
leader_dto.role = "leader".to_string();
member_dtos.insert(0, leader_dto);
}
Err(_) => {}
leader_dto.role = "leader".to_string();
member_dtos.insert(0, leader_dto);
}
success_response(ResponseSuccessDto { data: member_dtos })
@@ -825,17 +822,14 @@ impl TeamsServiceTrait for TeamsService {
}
// Add leader with full sensitive info
match Self::get_user_info_with_privacy(
if let Ok(mut leader_dto) = Self::get_user_info_with_privacy(
&team.leader_id.id.to_raw(),
"system",
true,
&state,
).await {
Ok(mut leader_dto) => {
leader_dto.role = "leader".to_string();
member_dtos.insert(0, leader_dto);
}
Err(_) => {}
leader_dto.role = "leader".to_string();
member_dtos.insert(0, leader_dto);
}
let team_dto = team.into_admin_detail_dto(member_dtos);
@@ -916,7 +910,7 @@ impl TeamsServiceTrait for TeamsService {
Ok(_) => common_response(StatusCode::OK, &format!("Successfully left team: {}", team.name)),
Err(e) => {
error!("Failed to remove team member: {}", e);
return common_response(StatusCode::INTERNAL_SERVER_ERROR, "Failed to leave team")
common_response(StatusCode::INTERNAL_SERVER_ERROR, "Failed to leave team")
},
}
})