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.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use axum::Json;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::Json;
|
||||
use serde::Serialize;
|
||||
|
||||
/// 统一错误响应格式
|
||||
@@ -95,3 +95,91 @@ pub fn check_version(expected: i32, actual: i32) -> AppResult<i32> {
|
||||
Err(AppError::VersionMismatch)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn check_version_ok() {
|
||||
assert_eq!(check_version(1, 1).unwrap(), 2);
|
||||
assert_eq!(check_version(5, 5).unwrap(), 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_version_mismatch() {
|
||||
let result = check_version(1, 2);
|
||||
assert!(result.is_err());
|
||||
match result.unwrap_err() {
|
||||
AppError::VersionMismatch => {}
|
||||
other => panic!("Expected VersionMismatch, got {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn db_err_record_not_found_maps_to_not_found() {
|
||||
let err = sea_orm::DbErr::RecordNotFound("test".to_string());
|
||||
let app_err: AppError = err.into();
|
||||
match app_err {
|
||||
AppError::NotFound(msg) => assert_eq!(msg, "test"),
|
||||
other => panic!("Expected NotFound, got {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn db_err_generic_maps_to_internal() {
|
||||
let db_err = sea_orm::DbErr::Custom("some error".to_string());
|
||||
let app_err: AppError = db_err.into();
|
||||
match app_err {
|
||||
AppError::Internal(msg) => assert!(msg.contains("some error")),
|
||||
other => panic!("Expected Internal, got {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_error_into_response_status_codes() {
|
||||
// NotFound -> 404
|
||||
let resp = AppError::NotFound("test".to_string()).into_response();
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
|
||||
// Validation -> 400
|
||||
let resp = AppError::Validation("bad input".to_string()).into_response();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
// Unauthorized -> 401
|
||||
let resp = AppError::Unauthorized.into_response();
|
||||
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
|
||||
|
||||
// Forbidden -> 403
|
||||
let resp = AppError::Forbidden("no access".to_string()).into_response();
|
||||
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
|
||||
|
||||
// VersionMismatch -> 409
|
||||
let resp = AppError::VersionMismatch.into_response();
|
||||
assert_eq!(resp.status(), StatusCode::CONFLICT);
|
||||
|
||||
// TooManyRequests -> 429
|
||||
let resp = AppError::TooManyRequests.into_response();
|
||||
assert_eq!(resp.status(), StatusCode::TOO_MANY_REQUESTS);
|
||||
|
||||
// Internal -> 500
|
||||
let resp = AppError::Internal("oops".to_string()).into_response();
|
||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_error_internal_hides_details_from_response() {
|
||||
// Internal errors should map to 500 with a generic message
|
||||
let resp = AppError::Internal("sensitive db error detail".to_string()).into_response();
|
||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn anyhow_error_maps_to_internal() {
|
||||
let err: AppError = anyhow::anyhow!("something went wrong").into();
|
||||
match err {
|
||||
AppError::Internal(msg) => assert_eq!(msg, "something went wrong"),
|
||||
other => panic!("Expected Internal, got {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,93 @@ impl Pagination {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn pagination_defaults() {
|
||||
let p = Pagination {
|
||||
page: None,
|
||||
page_size: None,
|
||||
};
|
||||
assert_eq!(p.limit(), 20);
|
||||
assert_eq!(p.offset(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pagination_custom_values() {
|
||||
let p = Pagination {
|
||||
page: Some(3),
|
||||
page_size: Some(10),
|
||||
};
|
||||
assert_eq!(p.limit(), 10);
|
||||
assert_eq!(p.offset(), 20); // (3-1) * 10
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pagination_max_cap() {
|
||||
let p = Pagination {
|
||||
page: Some(1),
|
||||
page_size: Some(200),
|
||||
};
|
||||
assert_eq!(p.limit(), 100); // capped at 100
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pagination_page_zero_treated_as_first() {
|
||||
// page 0 -> saturating_sub wraps to 0 -> offset = 0
|
||||
let p = Pagination {
|
||||
page: Some(0),
|
||||
page_size: Some(10),
|
||||
};
|
||||
assert_eq!(p.offset(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pagination_page_one() {
|
||||
let p = Pagination {
|
||||
page: Some(1),
|
||||
page_size: Some(50),
|
||||
};
|
||||
assert_eq!(p.offset(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paginated_response_total_pages() {
|
||||
let resp = PaginatedResponse {
|
||||
data: vec![1, 2, 3],
|
||||
total: 25,
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
total_pages: 3,
|
||||
};
|
||||
assert_eq!(resp.data.len(), 3);
|
||||
assert_eq!(resp.total, 25);
|
||||
assert_eq!(resp.total_pages, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_response_ok() {
|
||||
let resp = ApiResponse::ok(42);
|
||||
assert!(resp.success);
|
||||
assert_eq!(resp.data, Some(42));
|
||||
assert!(resp.message.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tenant_context_fields() {
|
||||
let ctx = TenantContext {
|
||||
tenant_id: Uuid::now_v7(),
|
||||
user_id: Uuid::now_v7(),
|
||||
roles: vec!["admin".to_string()],
|
||||
permissions: vec!["user.read".to_string()],
|
||||
};
|
||||
assert_eq!(ctx.roles.len(), 1);
|
||||
assert_eq!(ctx.permissions.len(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// 分页响应
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct PaginatedResponse<T> {
|
||||
|
||||
Reference in New Issue
Block a user