feat: mock配置迁移至JSON格式并修复body匹配

- 将mock配置从YAML格式迁移到JSON格式
- 修复JSON字符串格式body匹配失败问题
- 添加MCP功能模块
- 更新mock-spec.md规范文档

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-29 09:43:11 +08:00
parent 061ceff4b8
commit d364307131
42 changed files with 1509 additions and 893 deletions

View File

@@ -1,35 +1,25 @@
use std::fs::{self, File};
use std::io::Write;
use tempfile::tempdir;
use mock_server::models::MockSource;
use mock_server::models::MockRule;
use mock_server::loader::MockLoader;
#[test]
fn test_config_deserialization() {
// 测试 1验证单接口 YAML 解析
let single_yaml = r#"
name: "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);
// 测试 2验证多接口 YAML 数组解析
let multi_yaml = r#"
- name: "api_1"
request: { method: "GET", path: "/1" }
response: { status: 200, body: "b1" }
- name: "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);
// 测试 1验证 JSON 解析
let single_json = r#"{
"name": "auth_v1",
"request": {
"method": "POST",
"path": "/api/v1/login"
},
"response": {
"status": 200,
"body": "inline_content"
}
}"#;
let res: MockRule = serde_json::from_str(single_json).expect("应该成功解析 JSON");
assert_eq!(res.name, "auth_v1");
}
#[test]
@@ -42,34 +32,39 @@ fn test_recursive_loading_logic() {
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();
// 在深层目录创建单接口 JSON 文件
let mut file1 = File::create(user_dir.join("get_profile.json")).unwrap();
writeln!(
file1,
r#"
name: "user_profile"
request:
method: "GET"
path: "/api/v1/user/profile"
response:
status: 200
body: "profile_data"
"#
r#"{{
"name": "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();
// 在根目录创建另一个 JSON 文件
let mut file2 = File::create(root_path.join("health.json")).unwrap();
writeln!(
file2,
r#"
- name: "sys_health"
request: {{ method: "GET", path: "/health" }}
response: {{ status: 200, body: "ok" }}
- name: "sys_version"
request: {{ method: "GET", path: "/version" }}
response: {{ status: 200, body: "1.0.0" }}
"#
r#"{{
"name": "sys_health",
"request": {{
"method": "GET",
"path": "/health"
}},
"response": {{
"status": 200,
"body": "ok"
}}
}}"#
)
.unwrap();
@@ -80,11 +75,10 @@ fn test_recursive_loading_logic() {
// 1. 检查 Key 是否根据路径首段正确提取
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");
assert_eq!(total_rules, 2, "总规则数应为 2");
// 3. 检查深层文件是否被正确读取
let api_rules = &index["api"];

View File

@@ -2,7 +2,7 @@ use std::fs::{self, File};
use std::io::Write;
use tempfile::tempdir;
use serde_json::json;
use mock_server::models::{MockRule, MockSource, Payload};
use mock_server::models::{MockRule, Payload};
use mock_server::loader::MockLoader;
use mock_server::router::MockRouter;
use std::collections::HashMap;
@@ -11,42 +11,27 @@ use std::collections::HashMap;
#[test]
fn test_config_parsing_scenarios() {
// 场景 A: 验证单接口配置 (增加 body 结构化校验)
let yaml_single = r#"
name: "auth_v1"
request:
method: "POST"
path: "/api/v1/login"
body: { "user": "admin" }
response: { status: 200, body: "welcome" }
"#;
let source_s: MockSource = serde_yaml::from_str(yaml_single).expect("解析单接口失败");
let rules = source_s.flatten();
assert_eq!(rules.len(), 1);
// 验证 body 是否被成功解析为 Value::Object
assert!(rules[0].request.body.is_some());
assert_eq!(rules[0].request.body.as_ref().unwrap()["user"], "admin");
let json_single = r#"{
"name": "auth_v1",
"request": {
"method": "POST",
"path": "/api/v1/login",
"body": { "user": "admin" }
},
"response": { "status": 200, "body": "welcome" }
}"#;
let rule: MockRule = serde_json::from_str(json_single).expect("解析单接口失败");
assert!(rule.request.body.is_some());
assert_eq!(rule.request.body.as_ref().unwrap()["user"], "admin");
// 场景 B: 验证多接口配置
let yaml_multi = r#"
- name: "api_1"
request: { method: "GET", path: "/health" }
response: { status: 200, body: "ok" }
- name: "api_2"
request: { method: "GET", path: "/version" }
response: { status: 200, body: "1.0" }
"#;
let source_m: MockSource = serde_yaml::from_str(yaml_multi).expect("解析多接口失败");
assert_eq!(source_m.flatten().len(), 2);
// 场景 C: 验证 Smart Body 的 file:// 协议字符串解析
let yaml_file = r#"
name: "export_api"
request: { method: "GET", path: "/download" }
response: { status: 200, body: "file://./storage/data.zip" }
"#;
let source_f: MockSource = serde_yaml::from_str(yaml_file).unwrap();
let rule = &source_f.flatten()[0];
assert!(rule.response.body.starts_with("file://"));
// 场景 B: 验证 Smart Body 的 file:// 协议字符串解析
let json_file = r#"{
"name": "export_api",
"request": { "method": "GET", "path": "/download" },
"response": { "status": 200, "body": "file://./storage/data.zip" }
}"#;
let rule_file: MockRule = serde_json::from_str(json_file).unwrap();
assert!(rule_file.response.body.starts_with("file://"));
}
/// 模块二:验证 Loader 递归扫描与索引构建
@@ -58,11 +43,11 @@ fn test_loader_recursive_indexing() {
let auth_path = root_path.join("v1/auth");
fs::create_dir_all(&auth_path).unwrap();
let mut f1 = File::create(auth_path.join("single_login.yaml")).unwrap();
writeln!(f1, "name: 'l1'\nrequest: {{ method: 'POST', path: '/api/v1/login' }}\nresponse: {{ status: 200, body: 'ok' }}").unwrap();
let mut f1 = File::create(auth_path.join("login.json")).unwrap();
writeln!(f1, r#"{{"name":"l1","request":{{"method":"POST","path":"/api/v1/login"}},"response":{{"status":200,"body":"ok"}}}}"#).unwrap();
let mut f2 = File::create(root_path.join("sys.yaml")).unwrap();
writeln!(f2, "- name: 's1'\n request: {{ method: 'GET', path: '/health' }}\n response: {{ status: 200, body: 'up' }}").unwrap();
let mut f2 = File::create(root_path.join("health.json")).unwrap();
writeln!(f2, r#"{{"name":"s1","request":{{"method":"GET","path":"/health"}},"response":{{"status":200,"body":"up"}}}}"#).unwrap();
let index = MockLoader::load_all_from_dir(root_path);
@@ -78,21 +63,19 @@ fn test_router_matching_logic() {
let mut index = HashMap::new();
// 1. 准备带有 Body 的规则
let rule_auth = serde_yaml::from_str::<MockSource>(
r#"
name: "auth_v1"
request:
method: "POST"
path: "/api/v1/login"
headers: { "Content-Type": "application/json" }
body: { "code": 123 }
response: { status: 200, body: "token_123" }
"#,
)
.unwrap()
.flatten();
let rule_json = r#"{
"name": "auth_v1",
"request": {
"method": "POST",
"path": "/api/v1/login",
"headers": { "Content-Type": "application/json" },
"body": { "code": 123 }
},
"response": { "status": 200, "body": "token_123" }
}"#;
let rule_auth: MockRule = serde_json::from_str(rule_json).unwrap();
index.insert("api".to_string(), vec![rule_auth[0].clone()]);
index.insert("api".to_string(), vec![rule_auth.clone()]);
let router = MockRouter::new(index);
// 2. 测试场景 AJSON 完全匹配
@@ -139,48 +122,39 @@ fn test_payload_type_matching() {
let mut index = HashMap::new();
// XML 规则
let xml_rule = serde_yaml::from_str::<MockSource>(
r#"
name: "xml_api"
request:
method: "POST"
path: "/api/xml"
body: "<root><name>test</name></root>"
response: { status: 200, body: "ok" }
"#,
)
.unwrap()
.flatten();
let xml_rule: MockRule = serde_json::from_str(r#"{
"name": "xml_api",
"request": {
"method": "POST",
"path": "/api/xml",
"body": "<root><name>test</name></root>"
},
"response": { "status": 200, "body": "ok" }
}"#).unwrap();
// Form 规则
let form_rule = serde_yaml::from_str::<MockSource>(
r#"
name: "form_api"
request:
method: "POST"
path: "/api/form"
body: { "username": "admin", "password": "123456" }
response: { status: 200, body: "login_ok" }
"#,
)
.unwrap()
.flatten();
let form_rule: MockRule = serde_json::from_str(r#"{
"name": "form_api",
"request": {
"method": "POST",
"path": "/api/form",
"body": { "username": "admin", "password": "123456" }
},
"response": { "status": 200, "body": "login_ok" }
}"#).unwrap();
// Text 规则
let text_rule = serde_yaml::from_str::<MockSource>(
r#"
name: "text_api"
request:
method: "POST"
path: "/api/text"
body: "plain text content"
response: { status: 200, body: "text_ok" }
"#,
)
.unwrap()
.flatten();
let text_rule: MockRule = serde_json::from_str(r#"{
"name": "text_api",
"request": {
"method": "POST",
"path": "/api/text",
"body": "plain text content"
},
"response": { "status": 200, "body": "text_ok" }
}"#).unwrap();
index.insert("api".to_string(), vec![xml_rule[0].clone(), form_rule[0].clone(), text_rule[0].clone()]);
index.insert("api".to_string(), vec![xml_rule, form_rule, text_rule]);
let router = MockRouter::new(index);
// 测试 XML 匹配
@@ -252,34 +226,28 @@ fn test_multipart_matching() {
let mut index = HashMap::new();
// 数组形式:只匹配字段名
let array_rule = serde_yaml::from_str::<MockSource>(
r#"
name: "upload_array"
request:
method: "POST"
path: "/api/upload/array"
body: ["file", "description"]
response: { status: 200, body: "ok" }
"#,
)
.unwrap()
.flatten();
let array_rule: MockRule = serde_json::from_str(r#"{
"name": "upload_array",
"request": {
"method": "POST",
"path": "/api/upload/array",
"body": ["file", "description"]
},
"response": { "status": 200, "body": "ok" }
}"#).unwrap();
// 对象形式:匹配键值对
let object_rule = serde_yaml::from_str::<MockSource>(
r#"
name: "upload_object"
request:
method: "POST"
path: "/api/upload/object"
body: { "username": "admin", "role": "user" }
response: { status: 200, body: "ok" }
"#,
)
.unwrap()
.flatten();
// 对象形式:匹配键
let object_rule: MockRule = serde_json::from_str(r#"{
"name": "upload_object",
"request": {
"method": "POST",
"path": "/api/upload/object",
"body": { "username": "admin", "role": "user" }
},
"response": { "status": 200, "body": "ok" }
}"#).unwrap();
index.insert("api".to_string(), vec![array_rule[0].clone(), object_rule[0].clone()]);
index.insert("api".to_string(), vec![array_rule, object_rule]);
let router = MockRouter::new(index);
// 测试数组形式:匹配字段名
@@ -299,7 +267,7 @@ response: { status: 200, body: "ok" }
// 测试对象形式:键名匹配(值被忽略)
let mut correct_data = HashMap::new();
correct_data.insert("username".to_string(), "any_value".to_string()); // 值不重要
correct_data.insert("username".to_string(), "any_value".to_string());
correct_data.insert("role".to_string(), "any_role".to_string());
let correct_payload = Payload::Multipart(correct_data);
let object_matched = router.match_rule("POST", "/api/upload/object", &HashMap::new(), &HashMap::new(), &correct_payload);
@@ -308,118 +276,43 @@ response: { status: 200, body: "ok" }
// 测试对象形式:键名不匹配
let mut wrong_data = HashMap::new();
wrong_data.insert("username".to_string(), "admin".to_string());
wrong_data.insert("other_field".to_string(), "value".to_string()); // 缺少 role 字段
wrong_data.insert("other_field".to_string(), "value".to_string());
let wrong_payload = Payload::Multipart(wrong_data);
let wrong_matched = router.match_rule("POST", "/api/upload/object", &HashMap::new(), &HashMap::new(), &wrong_payload);
assert!(wrong_matched.is_none(), "缺少键名不应该成功");
}
/// 模块七:验证 YAML 块语法(折叠 `>` 和字面量 `|`
#[test]
fn test_yaml_block_syntax() {
let mut index = HashMap::new();
// 使用折叠语法 `>` 的规则
let folded_rule = serde_yaml::from_str::<MockSource>(
r#"
name: "folded_api"
request:
method: "POST"
path: "/api/folded"
body: >
{"username":"admin","password":"123456"}
response:
status: 200
body: "ok"
"#,
)
.unwrap()
.flatten();
// 使用字面量语法 `|` 的规则
let literal_rule = serde_yaml::from_str::<MockSource>(
r#"
name: "literal_api"
request:
method: "POST"
path: "/api/literal"
body: |
{"username":"test","password":"abcdef"}
response:
status: 200
body: "ok"
"#,
)
.unwrap()
.flatten();
index.insert("api".to_string(), vec![folded_rule[0].clone(), literal_rule[0].clone()]);
let router = MockRouter::new(index);
// 测试折叠语法YAML 中 `>` 会把内容合并成一行,但仍是字符串
// 程序应该能将字符串解析为 JSON 后匹配
let folded_payload = Payload::Json(json!({"username":"admin","password":"123456"}));
let folded_matched = router.match_rule("POST", "/api/folded", &HashMap::new(), &HashMap::new(), &folded_payload);
assert!(folded_matched.is_some(), "折叠语法应该匹配 JSON 请求");
assert_eq!(folded_matched.unwrap().name, "folded_api");
// 测试字面量语法YAML 中 `|` 保留换行
let literal_payload = Payload::Json(json!({"username":"test","password":"abcdef"}));
let literal_matched = router.match_rule("POST", "/api/literal", &HashMap::new(), &HashMap::new(), &literal_payload);
assert!(literal_matched.is_some(), "字面量语法应该匹配 JSON 请求");
assert_eq!(literal_matched.unwrap().name, "literal_api");
// 测试不匹配的情况
let wrong_payload = Payload::Json(json!({"username":"wrong","password":"wrong"}));
let wrong_matched = router.match_rule("POST", "/api/folded", &HashMap::new(), &HashMap::new(), &wrong_payload);
assert!(wrong_matched.is_none(), "错误的 body 不应该匹配");
}
/// 模块八:验证 XML 格式化匹配
/// 模块七:验证 XML 格式化匹配
#[test]
fn test_xml_normalized_matching() {
let mut index = HashMap::new();
// YAML 中的 XML格式
let xml_rule = serde_yaml::from_str::<MockSource>(
r#"
name: "xml_api"
request:
method: "POST"
path: "/api/xml"
body: |
<user>
<id>1001</id>
<name>张三</name>
</user>
response:
status: 200
body: "ok"
"#,
)
.unwrap()
.flatten();
// JSON 中的 XML紧凑格式)
let xml_rule: MockRule = serde_json::from_str(r#"{
"name": "xml_api",
"request": {
"method": "POST",
"path": "/api/xml",
"body": "<user><id>1001</id><name>张三</name></user>"
},
"response": { "status": 200, "body": "ok" }
}"#).unwrap();
index.insert("api".to_string(), vec![xml_rule[0].clone()]);
index.insert("api".to_string(), vec![xml_rule]);
let router = MockRouter::new(index);
// 测试 1完全相同的格式 XML
let formatted_xml = Payload::Xml("<user>\n <id>1001</id>\n <name>张三</name>\n</user>".to_string());
let matched1 = router.match_rule("POST", "/api/xml", &HashMap::new(), &HashMap::new(), &formatted_xml);
assert!(matched1.is_some(), "格式化 XML 应该匹配");
// 测试 2紧凑格式的 XML
// 测试 1紧凑格式 XML
let compact_xml = Payload::Xml("<user><id>1001</id><name>张三</name></user>".to_string());
let matched2 = router.match_rule("POST", "/api/xml", &HashMap::new(), &HashMap::new(), &compact_xml);
assert!(matched2.is_some(), "紧凑格式 XML 应该匹配");
let matched1 = router.match_rule("POST", "/api/xml", &HashMap::new(), &HashMap::new(), &compact_xml);
assert!(matched1.is_some(), "紧凑格式 XML 应该匹配");
// 测试 3:带 XML 声明的请求
// 测试 2:带 XML 声明的请求
let xml_with_decl = Payload::Xml("<?xml version=\"1.0\" encoding=\"UTF-8\"?><user><id>1001</id><name>张三</name></user>".to_string());
let matched3 = router.match_rule("POST", "/api/xml", &HashMap::new(), &HashMap::new(), &xml_with_decl);
assert!(matched3.is_some(), "带声明的 XML 应该匹配");
let matched2 = router.match_rule("POST", "/api/xml", &HashMap::new(), &HashMap::new(), &xml_with_decl);
assert!(matched2.is_some(), "带声明的 XML 应该匹配");
// 测试 4:内容不同,不应该匹配
// 测试 3:内容不同,不应该匹配
let wrong_xml = Payload::Xml("<user><id>1002</id><name>李四</name></user>".to_string());
let matched4 = router.match_rule("POST", "/api/xml", &HashMap::new(), &HashMap::new(), &wrong_xml);
assert!(matched4.is_none(), "内容不同的 XML 不应该匹配");
let matched3 = router.match_rule("POST", "/api/xml", &HashMap::new(), &HashMap::new(), &wrong_xml);
assert!(matched3.is_none(), "内容不同的 XML 不应该匹配");
}

View File

@@ -6,37 +6,39 @@ use mock_server::loader::MockLoader;
use mock_server::router::MockRouter;
/// 加载 v1 目录的 mock 规则
fn load_v2_mocks() -> HashMap<String, Vec<mock_server::models::MockRule>> {
let v2_path = Path::new("../mocks/v1");
MockLoader::load_all_from_dir(v2_path)
fn load_v1_mocks() -> HashMap<String, Vec<mock_server::models::MockRule>> {
let v1_path = Path::new("./mocks/v1");
MockLoader::load_all_from_dir(v1_path)
}
// ========== 模块一:验证所有 YAML 文件正确加载 ==========
// ========== 模块一:验证所有 JSON 文件正确加载 ==========
#[test]
fn test_v2_load_all_mocks() {
let index = load_v2_mocks();
fn test_v1_load_all_mocks() {
let index = load_v1_mocks();
// 验证索引键存在
assert!(index.contains_key("v1"), "应包含 'v2' 索引键");
assert!(index.contains_key("v1"), "应包含 'v1' 索引键");
// 验证规则总数
let total: usize = index.values().map(|v| v.len()).sum();
assert_eq!(total, 9, "v2 目录应有 9 个 mock 规则");
assert!(total >= 10, "v1 目录应有至少 10 个 mock 规则");
}
// ========== 模块二JSON Payload 测试 ==========
#[test]
fn test_v2_json_login() {
let index = load_v2_mocks();
fn test_v1_json_login() {
let index = load_v1_mocks();
let router = MockRouter::new(index);
let mut headers = HashMap::new();
headers.insert("Content-Type".to_string(), "application/json".to_string());
headers.insert("Authorization".to_string(), "eyJhbGciOiJIUzI1NiIsInR5cCI6".to_string());
headers.insert("host".to_string(), "127.0.0.1:8080".to_string());
let payload = Payload::Json(json!({
"username": "admin",
"username": "user001",
"password": "password123"
}));
@@ -44,13 +46,13 @@ fn test_v2_json_login() {
assert!(matched.is_some(), "JSON 登录应匹配成功");
let rule = matched.unwrap();
assert_eq!(rule.name, "v2_user_login");
assert_eq!(rule.name, "user_login_001");
assert_eq!(rule.response.status, 200);
}
#[test]
fn test_v2_json_register() {
let index = load_v2_mocks();
fn test_v1_json_register() {
let index = load_v1_mocks();
let router = MockRouter::new(index);
let mut headers = HashMap::new();
@@ -66,15 +68,15 @@ fn test_v2_json_register() {
assert!(matched.is_some(), "JSON 注册应匹配成功");
let rule = matched.unwrap();
assert_eq!(rule.name, "v2_user_register");
assert_eq!(rule.name, "user_register");
assert_eq!(rule.response.status, 201);
}
// ========== 模块三Form Payload 测试 ==========
#[test]
fn test_v2_form_login() {
let index = load_v2_mocks();
fn test_v1_form_login() {
let index = load_v1_mocks();
let router = MockRouter::new(index);
let mut headers = HashMap::new();
@@ -88,74 +90,74 @@ fn test_v2_form_login() {
let matched = router.match_rule("POST", "/v1/user/login/form", &HashMap::new(), &headers, &payload);
assert!(matched.is_some(), "Form 登录应匹配成功");
assert_eq!(matched.unwrap().name, "v2_user_login_form");
assert_eq!(matched.unwrap().name, "_user_login_form");
}
// ========== 模块四Text Payload 测试 ==========
#[test]
fn test_v2_text_echo() {
let index = load_v2_mocks();
fn test_v1_text_echo() {
let index = load_v1_mocks();
let router = MockRouter::new(index);
let mut headers = HashMap::new();
headers.insert("Content-Type".to_string(), "text/plain".to_string());
let payload = Payload::Text("Hello V2 Mock Server".to_string());
let payload = Payload::Text("Hello V1 Mock Server".to_string());
let matched = router.match_rule("POST", "/v1/user/echo", &HashMap::new(), &headers, &payload);
assert!(matched.is_some(), "Text 回显应匹配成功");
let rule = matched.unwrap();
assert_eq!(rule.name, "v2_user_echo");
assert!(rule.response.body.contains("Echo from V2"));
assert_eq!(rule.name, "user_echo");
assert!(rule.response.body.contains("Echo from V1"));
}
// ========== 模块五XML Payload 测试 ==========
#[test]
fn test_v2_xml_export() {
let index = load_v2_mocks();
fn test_v1_xml_export() {
let index = load_v1_mocks();
let router = MockRouter::new(index);
let mut headers = HashMap::new();
headers.insert("Content-Type".to_string(), "application/xml".to_string());
let xml_body = r#"<request><userId>10001</userId><format>xml</format></request>"#;
let xml_body = r#"<?xml version="1.0" encoding="UTF-8"?><request><userId>10001</userId><format>xml</format></request>"#;
let payload = Payload::Xml(xml_body.to_string());
let matched = router.match_rule("POST", "/v1/data/export", &HashMap::new(), &headers, &payload);
assert!(matched.is_some(), "XML 导出应匹配成功");
assert_eq!(matched.unwrap().name, "v2_data_export");
assert_eq!(matched.unwrap().name, "data_export");
}
// ========== 模块六Multipart Payload 测试 ==========
#[test]
fn test_v2_multipart_upload() {
let index = load_v2_mocks();
fn test_v1_multipart_upload() {
let index = load_v1_mocks();
let router = MockRouter::new(index);
let mut headers = HashMap::new();
headers.insert("Content-Type".to_string(), "multipart/form-data".to_string());
let mut multipart_data = HashMap::new();
multipart_data.insert("avatar".to_string(), "file_content".to_string());
multipart_data.insert("description".to_string(), "user avatar".to_string());
multipart_data.insert("avatar1".to_string(), "file_content".to_string());
multipart_data.insert("description1".to_string(), "user avatar".to_string());
let payload = Payload::Multipart(multipart_data);
let matched = router.match_rule("POST", "/v1/user/avatar", &HashMap::new(), &headers, &payload);
assert!(matched.is_some(), "Multipart 上传应匹配成功");
assert_eq!(matched.unwrap().name, "v2_user_upload_avatar");
assert_eq!(matched.unwrap().name, "user_upload_avatar_001");
}
// ========== 模块七None Payload 测试 (GET 无 Body) ==========
#[test]
fn test_v2_health_check() {
let index = load_v2_mocks();
fn test_v1_health_check() {
let index = load_v1_mocks();
let router = MockRouter::new(index);
let payload = Payload::None;
@@ -164,31 +166,31 @@ fn test_v2_health_check() {
assert!(matched.is_some(), "健康检查应匹配成功");
let rule = matched.unwrap();
assert_eq!(rule.name, "v2_health_check");
assert_eq!(rule.name, "health_check");
assert_eq!(rule.response.status, 200);
}
#[test]
fn test_v2_get_profile() {
let index = load_v2_mocks();
fn test_v1_get_profile() {
let index = load_v1_mocks();
let router = MockRouter::new(index);
let mut headers = HashMap::new();
headers.insert("Authorization".to_string(), "Bearer v2_test_token".to_string());
headers.insert("Authorization".to_string(), "Bearer v1_test_token".to_string());
let payload = Payload::None;
let matched = router.match_rule("GET", "/v1/user/profile", &HashMap::new(), &headers, &payload);
assert!(matched.is_some(), "获取用户信息应匹配成功");
assert_eq!(matched.unwrap().name, "v2_user_profile");
assert_eq!(matched.unwrap().name, "user_profile");
}
// ========== 模块八Query Params 测试 ==========
#[test]
fn test_v2_query_params() {
let index = load_v2_mocks();
fn test_v1_query_params() {
let index = load_v1_mocks();
let router = MockRouter::new(index);
let mut query = HashMap::new();
@@ -200,15 +202,15 @@ fn test_v2_query_params() {
assert!(matched.is_some(), "带 query params 的下载应匹配成功");
let rule = matched.unwrap();
assert_eq!(rule.name, "v2_user_download");
assert_eq!(rule.name, "user_download");
assert!(rule.response.body.starts_with("file://"));
}
// ========== 模块九Header 匹配测试 ==========
#[test]
fn test_v2_header_required() {
let index = load_v2_mocks();
fn test_v1_header_required() {
let index = load_v1_mocks();
let router = MockRouter::new(index);
// 无 Authorization header 不应匹配
@@ -218,21 +220,7 @@ fn test_v2_header_required() {
// 有正确的 Authorization header 应匹配
let mut headers = HashMap::new();
headers.insert("Authorization".to_string(), "Bearer v2_test_token".to_string());
headers.insert("Authorization".to_string(), "Bearer v1_test_token".to_string());
let matched = router.match_rule("GET", "/v1/user/profile", &HashMap::new(), &headers, &payload);
assert!(matched.is_some(), "有 Authorization 应匹配");
}
// ========== 模块十name 字段验证 ==========
#[test]
fn test_v2_all_rules_have_name() {
let index = load_v2_mocks();
for (key, rules) in &index {
for rule in rules {
assert!(!rule.name.is_empty(), "规则 name 不能为空");
assert!(rule.name.starts_with("v2_"), "v2 规则 name 应以 'v2_' 开头");
}
}
}