feat: 实现核心匹配引擎与请求处理器并修复解析逻辑
- 新增 router 模块:实现基于路径首段索引子集匹配。 - 新增 handler 模块:集成 Axum 处理器,支持 Smart Body 协议与延迟模拟。 - 修复解析与匹配故障:修正 YAML 字段类型解析错误。
This commit is contained in:
@@ -2,8 +2,10 @@ use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
use tempfile::tempdir;
|
||||
// 替换为你的项目实际名称
|
||||
use mock_server::config::{MockSource, MockRule};
|
||||
use mock_server::config::{MockRule, MockSource};
|
||||
use mock_server::loader::MockLoader;
|
||||
use mock_server::router::MockRouter;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// 模块一:验证 Config 反序列化逻辑
|
||||
#[test]
|
||||
@@ -65,10 +67,81 @@ fn test_loader_recursive_indexing() {
|
||||
|
||||
// 验证逻辑:
|
||||
// 即使物理路径很深,索引 Key 必须是逻辑路径的首段
|
||||
assert!(index.contains_key("api"), "必须通过 /api/v1/login 提取出 'api' 键");
|
||||
assert!(index.contains_key("health"), "必须通过 /health 提取出 'health' 键");
|
||||
assert!(
|
||||
index.contains_key("api"),
|
||||
"必须通过 /api/v1/login 提取出 'api' 键"
|
||||
);
|
||||
assert!(
|
||||
index.contains_key("health"),
|
||||
"必须通过 /health 提取出 'health' 键"
|
||||
);
|
||||
|
||||
// 验证扁平化后的总数
|
||||
let total: usize = index.values().map(|v| v.len()).sum();
|
||||
assert_eq!(total, 2);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_router_matching_logic() {
|
||||
// 1. 准备模拟数据(模拟 Loader 的输出)
|
||||
let mut index = HashMap::new();
|
||||
|
||||
let rule_auth = serde_yaml::from_str::<MockSource>(
|
||||
r#"
|
||||
id: "auth_v1"
|
||||
request:
|
||||
method: "POST"
|
||||
path: "/api/v1/login"
|
||||
headers: { "Content-Type": "application/json" }
|
||||
response: { status: 200, body: "token_123" }
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
.flatten();
|
||||
|
||||
let rule_user = serde_yaml::from_str::<MockSource>(
|
||||
r#"
|
||||
id: "get_user"
|
||||
request:
|
||||
method: "GET"
|
||||
path: "/api/user/info"
|
||||
query_params: { "id": "100" }
|
||||
response: { status: 200, body: "user_info" }
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
.flatten();
|
||||
|
||||
// 构建 HashMap 索引(模仿 Loader 的行为)
|
||||
index.insert(
|
||||
"api".to_string(),
|
||||
vec![rule_auth[0].clone(), rule_user[0].clone()],
|
||||
);
|
||||
|
||||
let router = MockRouter::new(index);
|
||||
|
||||
// 2. 测试场景 A:完全匹配
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert("Content-Type".to_string(), "application/json".to_string());
|
||||
|
||||
let matched = router.match_rule("POST", "/api/v1/login", &HashMap::new(), &headers);
|
||||
assert!(matched.is_some());
|
||||
assert_eq!(matched.unwrap().id, "auth_v1");
|
||||
|
||||
// 3. 测试场景 B:路径末尾斜杠归一化 (Trailing Slash)
|
||||
let matched_slash = router.match_rule("POST", "/api/v1/login/", &HashMap::new(), &headers);
|
||||
assert!(matched_slash.is_some(), "应该忽略路径末尾的斜杠");
|
||||
|
||||
// 4. 测试场景 C:Query 参数子集匹配
|
||||
let mut queries = HashMap::new();
|
||||
queries.insert("id".to_string(), "100".to_string());
|
||||
queries.insert("extra".to_string(), "unused".to_string()); // 额外的参数不应影响匹配
|
||||
|
||||
let matched_query = router.match_rule("GET", "/api/user/info", &queries, &HashMap::new());
|
||||
assert!(matched_query.is_some());
|
||||
assert_eq!(matched_query.unwrap().id, "get_user");
|
||||
|
||||
// 5. 测试场景 D:匹配失败(Method 错误)
|
||||
let fail_method = router.match_rule("GET", "/api/v1/login", &HashMap::new(), &headers);
|
||||
assert!(fail_method.is_none());
|
||||
}
|
||||
|
||||
114
tests/loader_test.rs
Normal file
114
tests/loader_test.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
use tempfile::tempdir;
|
||||
// 假设你的项目名在 Cargo.toml 中叫 mock_server
|
||||
use mock_server::config::MockSource;
|
||||
use mock_server::loader::MockLoader;
|
||||
|
||||
#[test]
|
||||
fn test_config_deserialization() {
|
||||
// 测试 1:验证单接口 YAML 解析
|
||||
let single_yaml = r#"
|
||||
id: "auth_v1"
|
||||
request:
|
||||
method: "POST"
|
||||
path: "/api/v1/login"
|
||||
response:
|
||||
status: 200
|
||||
body: "inline_content"
|
||||
"#;
|
||||
let res: MockSource = serde_yaml::from_str(single_yaml).expect("应该成功解析单接口");
|
||||
assert_eq!(res.flatten().len(), 1);
|
||||
// assert_eq!(res.flatten()[0].id, "auth_v1");
|
||||
|
||||
// 测试 2:验证多接口 YAML 数组解析
|
||||
let multi_yaml = r#"
|
||||
- id: "api_1"
|
||||
request: { method: "GET", path: "/1" }
|
||||
response: { status: 200, body: "b1" }
|
||||
- id: "api_2"
|
||||
request: { method: "GET", path: "/2" }
|
||||
response: { status: 200, body: "b2" }
|
||||
"#;
|
||||
let res_multi: MockSource = serde_yaml::from_str(multi_yaml).expect("应该成功解析接口数组");
|
||||
assert_eq!(res_multi.flatten().len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recursive_loading_logic() {
|
||||
// 创建临时 Mock 目录结构
|
||||
let root_dir = tempdir().expect("创建临时目录失败");
|
||||
let root_path = root_dir.path();
|
||||
|
||||
// 构造物理层级:mocks/v1/user/
|
||||
let user_dir = root_path.join("v1/user");
|
||||
fs::create_dir_all(&user_dir).unwrap();
|
||||
|
||||
// 在深层目录创建单接口文件
|
||||
let mut file1 = File::create(user_dir.join("get_profile.yaml")).unwrap();
|
||||
writeln!(
|
||||
file1,
|
||||
r#"
|
||||
id: "user_profile"
|
||||
request:
|
||||
method: "GET"
|
||||
path: "/api/v1/user/profile"
|
||||
response:
|
||||
status: 200
|
||||
body: "profile_data"
|
||||
"#
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 在根目录创建多接口文件
|
||||
let mut file2 = File::create(root_path.join("system.yaml")).unwrap();
|
||||
writeln!(
|
||||
file2,
|
||||
r#"
|
||||
- id: "sys_health"
|
||||
request: {{ method: "GET", path: "/health" }}
|
||||
response: {{ status: 200, body: "ok" }}
|
||||
- id: "sys_version"
|
||||
request: {{ method: "GET", path: "/version" }}
|
||||
response: {{ status: 200, body: "1.0.0" }}
|
||||
"#
|
||||
)
|
||||
.unwrap();
|
||||
// writeln!(
|
||||
// file2,
|
||||
// r#"
|
||||
// - id: "sys_health"
|
||||
// request:
|
||||
// method: "GET"
|
||||
// path: "/health"
|
||||
// response:
|
||||
// status: 200
|
||||
// body: "ok"
|
||||
// - id: "sys_version"
|
||||
// request:
|
||||
// method: "GET"
|
||||
// path: "/version"
|
||||
// response:
|
||||
// status: 200
|
||||
// body: "1.0.0"
|
||||
// "#
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// 执行加载
|
||||
let index = MockLoader::load_all_from_dir(root_path);
|
||||
|
||||
// 断言结果:
|
||||
// 1. 检查 Key 是否根据路径首段正确提取(/api/v1/... -> api, /health -> health)
|
||||
assert!(index.contains_key("api"), "索引应包含 'api' 键");
|
||||
assert!(index.contains_key("health"), "索引应包含 'health' 键");
|
||||
assert!(index.contains_key("version"), "索引应包含 'version' 键");
|
||||
|
||||
// 2. 检查规则总数
|
||||
let total_rules: usize = index.values().map(|v| v.len()).sum();
|
||||
assert_eq!(total_rules, 3, "总规则数应为 3");
|
||||
|
||||
// 3. 检查深层文件是否被正确读取
|
||||
let api_rules = &index["api"];
|
||||
assert_eq!(api_rules[0].id, "user_profile");
|
||||
}
|
||||
Reference in New Issue
Block a user