From 11b5b73b4e174b85dab398aedd32e97caf0dfe4b Mon Sep 17 00:00:00 2001 From: Beast Date: Wed, 18 Feb 2026 14:14:39 +0800 Subject: [PATCH 1/4] feat: wrap not found error We don't want to overload metrics with useless error. So, not found data in db or handler will be wrapped in status 200 but contain error message still. --- src/errors.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 6b5cfaa..989a73f 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -131,7 +131,7 @@ fn map_handler_error(err: HandlerError) -> (StatusCode, String) { }, HandlerError::Referral(err) => match err { - ReferralHandlerError::ReferralNotFound(err) => (StatusCode::NOT_FOUND, err), + ReferralHandlerError::ReferralNotFound(err) => (StatusCode::OK, err), ReferralHandlerError::InvalidReferral(err) => (StatusCode::BAD_REQUEST, err), ReferralHandlerError::DuplicateReferral(err) => (StatusCode::CONFLICT, err), }, @@ -141,7 +141,7 @@ fn map_handler_error(err: HandlerError) -> (StatusCode, String) { fn map_db_error(err: DbError) -> (StatusCode, String) { match err { DbError::UniqueViolation(err) => (StatusCode::CONFLICT, err), - DbError::RecordNotFound(err) | DbError::AddressNotFound(err) => (StatusCode::NOT_FOUND, err), + DbError::RecordNotFound(err) | DbError::AddressNotFound(err) => (StatusCode::OK, err), DbError::Database(err) => { error!("Database error: {}", err); From 0c9feca5e2b82ddba2cf1b182b9754376bf47527 Mon Sep 17 00:00:00 2001 From: Beast Date: Wed, 18 Feb 2026 14:40:13 +0800 Subject: [PATCH 2/4] fix: integration tests --- src/handlers/raid_quest.rs | 14 ++++++++++---- src/handlers/relevant_tweet.rs | 5 ++++- src/handlers/tweet_author.rs | 8 +++++++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/handlers/raid_quest.rs b/src/handlers/raid_quest.rs index e6f1b83..2b87c71 100644 --- a/src/handlers/raid_quest.rs +++ b/src/handlers/raid_quest.rs @@ -689,8 +689,11 @@ mod tests { .await .unwrap(); - // 404/RecordNotFound for No Active Raid - assert!(response.status().is_server_error() || response.status() == StatusCode::NOT_FOUND); + // 200 OK / Handler Error + assert_eq!(response.status(), StatusCode::OK); + let body_bytes = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap(); + let body: Value = serde_json::from_slice(&body_bytes).unwrap(); + assert_eq!(body["error"].as_str().unwrap(), "No active raid is found"); } #[tokio::test] @@ -727,8 +730,11 @@ mod tests { .await .unwrap(); - // 400 Bad Request / Handler Error - assert_eq!(response.status(), StatusCode::NOT_FOUND); + // 200 OK / Handler Error + assert_eq!(response.status(), StatusCode::OK); + let body_bytes = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap(); + let body: Value = serde_json::from_slice(&body_bytes).unwrap(); + assert_eq!(body["error"].as_str().unwrap(), "User doesn't have X association"); } #[tokio::test] diff --git a/src/handlers/relevant_tweet.rs b/src/handlers/relevant_tweet.rs index 834db71..092aa21 100644 --- a/src/handlers/relevant_tweet.rs +++ b/src/handlers/relevant_tweet.rs @@ -379,6 +379,9 @@ mod tests { .await .unwrap(); - assert_eq!(response.status(), 404); + assert_eq!(response.status(), 200); + let body_bytes = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap(); + let body: Value = serde_json::from_slice(&body_bytes).unwrap(); + assert_eq!(body["error"].as_str().unwrap(), "Tweet non_existent_tweet not found"); } } diff --git a/src/handlers/tweet_author.rs b/src/handlers/tweet_author.rs index 455234c..fb72fb0 100644 --- a/src/handlers/tweet_author.rs +++ b/src/handlers/tweet_author.rs @@ -359,7 +359,13 @@ mod tests { .await .unwrap(); - assert_eq!(response.status(), 404); + assert_eq!(response.status(), 200); + let body_bytes = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap(); + let body: Value = serde_json::from_slice(&body_bytes).unwrap(); + assert_eq!( + body["error"].as_str().unwrap(), + "Tweet Author non_existent_id not found" + ); } #[tokio::test] From 6d0a4daaef6482324f54644a0b1539dccea92e9d Mon Sep 17 00:00:00 2001 From: Beast Date: Wed, 18 Feb 2026 18:21:44 +0800 Subject: [PATCH 3/4] fix: auth endpoint status return + add regression test --- src/handlers/auth.rs | 45 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/src/handlers/auth.rs b/src/handlers/auth.rs index effc21e..940c1ae 100644 --- a/src/handlers/auth.rs +++ b/src/handlers/auth.rs @@ -12,7 +12,6 @@ use tower_cookies::{Cookie, Cookies}; use uuid::Uuid; use crate::{ - db_persistence::DbError, handlers::{HandlerError, SuccessResponse}, http_server::{AppState, Challenge}, models::{ @@ -293,10 +292,9 @@ pub async fn handle_admin_login( .admin .find_by_username(&body.username) .await? - .ok_or(AppError::Database(DbError::RecordNotFound(format!( - "Admin with username {} is not exist", - &body.username, - ))))?; + .ok_or(AppError::Handler(HandlerError::Auth( + AuthHandlerError::Unauthorized("Invalid username or password".to_string()), + )))?; let parsed_hash = PasswordHash::new(&admin.password).map_err(|_| AppError::Server("Failed generating token".to_string()))?; @@ -342,7 +340,7 @@ mod tests { use std::sync::Arc; use crate::{ - handlers::auth::handle_x_oauth_callback, + handlers::auth::{handle_admin_login, handle_x_oauth_callback}, http_server::AppState, models::x_association::XAssociation, routes::auth::auth_routes, @@ -351,7 +349,7 @@ mod tests { test_db::{create_persisted_address, reset_database}, }, }; - use axum::{body::Body, http, routing::get}; + use axum::{body::Body, http, routing::get, routing::post}; use rusx::{ auth::TwitterToken, resources::{ @@ -607,4 +605,37 @@ mod tests { .unwrap(); assert_eq!(resp.status(), http::StatusCode::OK); } + + #[tokio::test] + async fn test_admin_login_nonexistent_username_returns_401() { + let state = create_test_app_state().await; + reset_database(&state.db.pool).await; + + let router = axum::Router::new() + .route("/auth/admin/login", post(handle_admin_login)) + .with_state(state); + + let payload = serde_json::json!({ + "username": "nonexistent_admin", + "password": "any_password" + }); + + let response = router + .oneshot( + http::Request::builder() + .method("POST") + .uri("/auth/admin/login") + .header(http::header::CONTENT_TYPE, "application/json") + .body(Body::from(serde_json::to_vec(&payload).unwrap())) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(response.status(), http::StatusCode::UNAUTHORIZED); + + let body_bytes = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap(); + let body: serde_json::Value = serde_json::from_slice(&body_bytes).unwrap(); + assert_eq!(body["error"], "Invalid username or password"); + } } From 9f3d3368f6c8cfe5fa692a43549ed0565be7861e Mon Sep 17 00:00:00 2001 From: Beast Date: Wed, 18 Feb 2026 18:27:56 +0800 Subject: [PATCH 4/4] fix: formatting --- src/handlers/auth.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/handlers/auth.rs b/src/handlers/auth.rs index 940c1ae..994d707 100644 --- a/src/handlers/auth.rs +++ b/src/handlers/auth.rs @@ -292,9 +292,9 @@ pub async fn handle_admin_login( .admin .find_by_username(&body.username) .await? - .ok_or(AppError::Handler(HandlerError::Auth( - AuthHandlerError::Unauthorized("Invalid username or password".to_string()), - )))?; + .ok_or(AppError::Handler(HandlerError::Auth(AuthHandlerError::Unauthorized( + "Invalid username or password".to_string(), + ))))?; let parsed_hash = PasswordHash::new(&admin.password).map_err(|_| AppError::Server("Failed generating token".to_string()))?;