Files
hms/crates/erp-auth/src/error.rs
iven ee65b6e3c9 test: add 149 unit tests across core, auth, config, message crates
Test coverage increased from ~34 to 183 tests (zero failures):

- erp-core (21): version check, pagination, API response, error mapping
- erp-auth (39): org tree building, DTO validation, error conversion,
  password hashing, user model mapping
- erp-config (57): DTO validation, numbering reset logic, menu tree
  building, error conversion. Fixed BatchSaveMenusReq nested validation
- erp-message (50): DTO validation, template rendering, query defaults,
  error conversion
- erp-workflow (16): unchanged (parser + expression tests)

All tests are pure unit tests requiring no database.
2026-04-15 01:06:34 +08:00

132 lines
4.0 KiB
Rust

use erp_core::error::AppError;
/// Auth module error types
#[derive(Debug, thiserror::Error)]
pub enum AuthError {
#[error("用户名或密码错误")]
InvalidCredentials,
#[error("Token 已过期")]
TokenExpired,
#[error("Token 已被吊销")]
TokenRevoked,
#[error("用户已被{0}")]
UserDisabled(String),
#[error("密码哈希错误")]
HashError(String),
#[error("JWT 错误: {0}")]
JwtError(#[from] jsonwebtoken::errors::Error),
#[error("{0}")]
Validation(String),
#[error("版本冲突: 数据已被其他操作修改,请刷新后重试")]
VersionMismatch,
}
impl From<AuthError> for AppError {
fn from(err: AuthError) -> Self {
match err {
AuthError::InvalidCredentials => AppError::Unauthorized,
AuthError::TokenExpired => AppError::Unauthorized,
AuthError::TokenRevoked => AppError::Unauthorized,
AuthError::UserDisabled(s) => AppError::Forbidden(s),
AuthError::Validation(s) => AppError::Validation(s),
AuthError::HashError(_) => AppError::Internal(err.to_string()),
AuthError::JwtError(_) => AppError::Unauthorized,
AuthError::VersionMismatch => AppError::VersionMismatch,
}
}
}
impl From<AppError> for AuthError {
fn from(err: AppError) -> Self {
match err {
AppError::VersionMismatch => AuthError::VersionMismatch,
other => AuthError::Validation(other.to_string()),
}
}
}
pub type AuthResult<T> = Result<T, AuthError>;
#[cfg(test)]
mod tests {
use super::*;
use erp_core::error::AppError;
#[test]
fn auth_error_invalid_credentials_maps_to_unauthorized() {
let app: AppError = AuthError::InvalidCredentials.into();
match app {
AppError::Unauthorized => {}
other => panic!("Expected Unauthorized, got {:?}", other),
}
}
#[test]
fn auth_error_token_expired_maps_to_unauthorized() {
let app: AppError = AuthError::TokenExpired.into();
match app {
AppError::Unauthorized => {}
other => panic!("Expected Unauthorized, got {:?}", other),
}
}
#[test]
fn auth_error_user_disabled_maps_to_forbidden() {
let app: AppError = AuthError::UserDisabled("已禁用".to_string()).into();
match app {
AppError::Forbidden(msg) => assert_eq!(msg, "已禁用"),
other => panic!("Expected Forbidden, got {:?}", other),
}
}
#[test]
fn auth_error_hash_error_maps_to_internal() {
let app: AppError = AuthError::HashError("argon2 failed".to_string()).into();
match app {
AppError::Internal(_) => {}
other => panic!("Expected Internal, got {:?}", other),
}
}
#[test]
fn auth_error_validation_maps_to_validation() {
let app: AppError = AuthError::Validation("用户名已存在".to_string()).into();
match app {
AppError::Validation(msg) => assert_eq!(msg, "用户名已存在"),
other => panic!("Expected Validation, got {:?}", other),
}
}
#[test]
fn auth_error_version_mismatch_roundtrip() {
// AuthError::VersionMismatch -> AppError::VersionMismatch -> AuthError::VersionMismatch
let app: AppError = AuthError::VersionMismatch.into();
match app {
AppError::VersionMismatch => {}
other => panic!("Expected VersionMismatch, got {:?}", other),
}
// And back
let auth: AuthError = AppError::VersionMismatch.into();
match auth {
AuthError::VersionMismatch => {}
other => panic!("Expected VersionMismatch, got {:?}", other),
}
}
#[test]
fn app_error_other_maps_to_auth_validation() {
let auth: AuthError = AppError::NotFound("not found".to_string()).into();
match auth {
AuthError::Validation(msg) => assert!(msg.contains("not found")),
other => panic!("Expected Validation, got {:?}", other),
}
}
}