Files
common-telemetry/tests/integration_test.rs
2026-01-29 18:09:58 +08:00

240 lines
7.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use axum::{Router, body::Body, routing::get};
use common_telemetry::{
error::AppError,
response::AppResponse,
telemetry::{self, TelemetryConfig},
};
use http::{Request, StatusCode};
use serde_json::Value;
use std::{fs, time::Duration};
use tower::ServiceExt; // for oneshot
// --- 模拟业务 Handler ---
// 模拟场景1Access Token 过期
async fn handler_access_expired() -> Result<String, AppError> {
// 模拟业务逻辑判断...
Err(AppError::AccessTokenExpired)
}
// 模拟场景2Refresh Token 过期
async fn handler_refresh_expired() -> Result<String, AppError> {
Err(AppError::RefreshTokenExpired)
}
// 模拟场景3正常成功
async fn handler_success() -> Result<String, AppError> {
// 这里打印一条日志,测试 Tracing 是否工作
tracing::info!(user_id = 10086, "User accessed success handler");
Ok("Success Data".to_string())
}
async fn handler_success_wrapped() -> Result<AppResponse<String>, AppError> {
Ok(AppResponse::ok("Success Data".to_string()))
}
async fn handler_bad_request() -> Result<String, AppError> {
Err(AppError::BadRequest("bad params".into()))
}
#[cfg(feature = "with-validator")]
async fn handler_validation_error() -> Result<String, AppError> {
Err(AppError::ValidationError("field required".into()))
}
#[tokio::test]
async fn test_full_flow_error_and_logging() {
println!(">>> [Step 0] Test Initializing...");
// 1. 准备环境:创建一个临时目录存放日志,防止污染项目
let temp_dir = tempfile::tempdir().unwrap();
let log_dir_path = temp_dir.path().to_str().unwrap().to_string();
let log_filename = "test_app.log";
// 2. 初始化 Tracing (模拟生产环境配置)
let config = TelemetryConfig {
service_name: "test-service".into(),
log_level: "info".into(),
log_to_file: true, // 开启文件写入
log_dir: Some(log_dir_path.clone()),
log_file: Some(log_filename.to_string()),
};
// !!! 必须持有 guard否则日志不会写入
let _guard = telemetry::init(config);
// 给一点时间让 tracing 系统初始化
tokio::time::sleep(Duration::from_millis(50)).await;
// 3. 构建 Axum Router
let app = Router::new()
.route("/access-expired", get(handler_access_expired))
.route("/refresh-expired", get(handler_refresh_expired))
.route("/success", get(handler_success))
.route("/success-wrapped", get(handler_success_wrapped))
.route("/bad-request", get(handler_bad_request));
#[cfg(feature = "with-validator")]
let app = app.route("/validation-error", get(handler_validation_error));
println!(">>> [Step 1] Running Case A: Access Token Expired...");
// --- 测试用例 A: Access Token 过期 ---
let response = app
.clone()
.oneshot(
Request::builder()
.uri("/access-expired")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
// 验证 HTTP 状态码 (401)
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
// 验证 JSON Body
let body_bytes = axum::body::to_bytes(response.into_body(), usize::MAX)
.await
.unwrap();
let body: Value = serde_json::from_slice(&body_bytes).unwrap();
// 核心验证code 必须是 20001 (BizCode::AccessTokenExpired)
// 前端根据这个 code 进行静默刷新
assert_eq!(body["code"], 20001);
// --- 测试用例 B: Refresh Token 过期 ---
println!(">>> [Step 2] Running Case B: Refresh Token Expired...");
let response = app
.clone()
.oneshot(
Request::builder()
.uri("/refresh-expired")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
let body_bytes = axum::body::to_bytes(response.into_body(), usize::MAX)
.await
.unwrap();
let body: Value = serde_json::from_slice(&body_bytes).unwrap();
// 核心验证code 必须是 20002 (BizCode::RefreshTokenExpired)
// 前端根据这个 code 强制登出
assert_eq!(body["code"], 20002);
// --- 测试用例 C: 验证日志文件是否生成 ---
println!(">>> [Step 3] Running Case C: Log File Verification...");
// 先请求一次成功接口,触发 tracing::info!
let _ = app
.clone()
.oneshot(
Request::builder()
.uri("/success")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
// --- 测试用例 D: 统一成功响应格式 ---
println!(">>> [Step 4] Running Case D: Success Response Wrapper...");
let response = app
.clone()
.oneshot(
Request::builder()
.uri("/success-wrapped")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
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["code"], 0);
assert_eq!(body["message"], "Success");
assert_eq!(body["data"], "Success Data");
// --- 测试用例 E: 错误响应 details 字段 ---
println!(">>> [Step 5] Running Case E: Error Response Details...");
let response = app
.clone()
.oneshot(
Request::builder()
.uri("/bad-request")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
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["code"], 30000);
assert_eq!(body["details"], "bad params");
#[cfg(feature = "with-validator")]
{
let response = app
.clone()
.oneshot(
Request::builder()
.uri("/validation-error")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
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["code"], 30001);
assert_eq!(body["details"], "field required");
}
// 稍微等待异步日志写入磁盘
println!(" Waiting for logs to flush...");
tokio::time::sleep(Duration::from_millis(200)).await;
// 验证文件
let mut found_file = false;
let paths = fs::read_dir(temp_dir.path()).unwrap();
for path in paths {
let entry = path.unwrap();
let file_name = entry.file_name().into_string().unwrap();
println!(" Found file in temp dir: {}", file_name);
// tracing-appender 滚动日志通常格式为: filename.YYYY-MM-DD
if file_name.starts_with(log_filename) {
found_file = true;
let content = fs::read_to_string(entry.path()).unwrap();
println!(" Log Content Preview: {}", content.trim());
assert!(
content.contains("User accessed success handler"),
"日志内容丢失!"
);
assert!(content.contains("\"user_id\":10086"), "结构化字段丢失!");
}
}
assert!(found_file, "未在临时目录找到日志文件!");
println!(" [Success] Case C Passed (Log file verified)");
// temp_dir 会在作用域结束时自动删除清理
}