feat(project): init
This commit is contained in:
155
tests/integration_test.rs
Normal file
155
tests/integration_test.rs
Normal file
@@ -0,0 +1,155 @@
|
||||
use axum::{Router, body::Body, routing::get};
|
||||
use common_telemetry::{
|
||||
error::AppError,
|
||||
telemetry::{self, TelemetryConfig},
|
||||
};
|
||||
use http::{Request, StatusCode};
|
||||
use serde_json::Value;
|
||||
use std::{fs, time::Duration};
|
||||
use tower::ServiceExt; // for oneshot
|
||||
|
||||
// --- 模拟业务 Handler ---
|
||||
|
||||
// 模拟场景1:Access Token 过期
|
||||
async fn handler_access_expired() -> Result<String, AppError> {
|
||||
// 模拟业务逻辑判断...
|
||||
Err(AppError::AccessTokenExpired)
|
||||
}
|
||||
|
||||
// 模拟场景2:Refresh 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())
|
||||
}
|
||||
|
||||
#[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));
|
||||
|
||||
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();
|
||||
|
||||
// 稍微等待异步日志写入磁盘
|
||||
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 会在作用域结束时自动删除清理
|
||||
}
|
||||
Reference in New Issue
Block a user