Files
mock-server/tests/loader_test.rs
CNWei d364307131 feat: mock配置迁移至JSON格式并修复body匹配
- 将mock配置从YAML格式迁移到JSON格式
- 修复JSON字符串格式body匹配失败问题
- 添加MCP功能模块
- 更新mock-spec.md规范文档

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 09:43:11 +08:00

319 lines
12 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 std::fs::{self, File};
use std::io::Write;
use tempfile::tempdir;
use serde_json::json;
use mock_server::models::{MockRule, Payload};
use mock_server::loader::MockLoader;
use mock_server::router::MockRouter;
use std::collections::HashMap;
/// 模块一:验证 Config 反序列化逻辑
#[test]
fn test_config_parsing_scenarios() {
// 场景 A: 验证单接口配置 (增加 body 结构化校验)
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: 验证 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 递归扫描与索引构建
#[test]
fn test_loader_recursive_indexing() {
let temp_root = tempdir().expect("无法创建临时目录");
let root_path = temp_root.path();
let auth_path = root_path.join("v1/auth");
fs::create_dir_all(&auth_path).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("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);
assert!(index.contains_key("api"));
assert!(index.contains_key("health"));
let total: usize = index.values().map(|v| v.len()).sum();
assert_eq!(total, 2);
}
/// 模块三:验证 Router 匹配逻辑(使用新的 Payload 类型)
#[test]
fn test_router_matching_logic() {
let mut index = HashMap::new();
// 1. 准备带有 Body 的规则
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.clone()]);
let router = MockRouter::new(index);
// 2. 测试场景 AJSON 完全匹配
let mut headers = HashMap::new();
headers.insert("Content-Type".to_string(), "application/json".to_string());
let payload = Payload::Json(json!({ "code": 123 }));
let matched = router.match_rule(
"POST",
"/api/v1/login",
&HashMap::new(),
&headers,
&payload,
);
assert!(matched.is_some());
assert_eq!(matched.unwrap().name, "auth_v1");
// 3. 测试场景 BBody 不匹配
let wrong_payload = Payload::Json(json!({ "code": 456 }));
let matched_fail = router.match_rule(
"POST",
"/api/v1/login",
&HashMap::new(),
&headers,
&wrong_payload,
);
assert!(matched_fail.is_none(), "Body 不一致时不应匹配成功");
// 4. 测试场景 C末尾斜杠兼容性测试
let matched_slash = router.match_rule(
"POST",
"/api/v1/login/",
&HashMap::new(),
&headers,
&payload,
);
assert!(matched_slash.is_some());
}
/// 模块四:验证不同 Payload 类型的匹配
#[test]
fn test_payload_type_matching() {
let mut index = HashMap::new();
// XML 规则
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: 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: 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, form_rule, text_rule]);
let router = MockRouter::new(index);
// 测试 XML 匹配
let xml_payload = Payload::Xml("<root><name>test</name></root>".to_string());
let xml_matched = router.match_rule("POST", "/api/xml", &HashMap::new(), &HashMap::new(), &xml_payload);
assert!(xml_matched.is_some(), "XML 应该匹配");
assert_eq!(xml_matched.unwrap().name, "xml_api");
// 测试 Form 匹配
let mut form_data = HashMap::new();
form_data.insert("username".to_string(), "admin".to_string());
form_data.insert("password".to_string(), "123456".to_string());
let form_payload = Payload::Form(form_data);
let form_matched = router.match_rule("POST", "/api/form", &HashMap::new(), &HashMap::new(), &form_payload);
assert!(form_matched.is_some(), "Form 应该匹配");
assert_eq!(form_matched.unwrap().name, "form_api");
// 测试 Text 匹配
let text_payload = Payload::Text("plain text content".to_string());
let text_matched = router.match_rule("POST", "/api/text", &HashMap::new(), &HashMap::new(), &text_payload);
assert!(text_matched.is_some(), "Text 应该匹配");
assert_eq!(text_matched.unwrap().name, "text_api");
// 测试 None 不匹配
let none_payload = Payload::None;
let none_matched = router.match_rule("POST", "/api/text", &HashMap::new(), &HashMap::new(), &none_payload);
assert!(none_matched.is_none(), "None 不应该匹配有 body 的规则");
}
/// 模块五:验证 Payload 方法
#[test]
fn test_payload_methods() {
// 测试 content_type_name
assert_eq!(Payload::Json(json!({})).content_type_name(), "application/json");
assert_eq!(Payload::Xml("".to_string()).content_type_name(), "application/xml");
assert_eq!(Payload::Form(HashMap::new()).content_type_name(), "application/x-www-form-urlencoded");
assert_eq!(Payload::Multipart(HashMap::new()).content_type_name(), "multipart/form-data");
assert_eq!(Payload::Text("".to_string()).content_type_name(), "text/plain");
assert_eq!(Payload::None.content_type_name(), "none");
// 测试 to_compare_string
let json_payload = Payload::Json(json!({"a": 1, "b": 2}));
assert!(json_payload.to_compare_string().contains("\"a\":1"));
let text_payload = Payload::Text("hello world".to_string());
assert_eq!(text_payload.to_compare_string(), "hello world");
let none_payload = Payload::None;
assert_eq!(none_payload.to_compare_string(), "");
// 测试 Form 排序后的字符串
let mut form_map = HashMap::new();
form_map.insert("b".to_string(), "2".to_string());
form_map.insert("a".to_string(), "1".to_string());
let form_payload = Payload::Form(form_map);
assert_eq!(form_payload.to_compare_string(), "a=1&b=2");
// 测试 Multipart 排序后的字符串
let mut multipart_map = HashMap::new();
multipart_map.insert("b".to_string(), "2".to_string());
multipart_map.insert("a".to_string(), "1".to_string());
let multipart_payload = Payload::Multipart(multipart_map);
assert_eq!(multipart_payload.to_compare_string(), "a=1&b=2");
}
/// 模块六:验证 Multipart 匹配
#[test]
fn test_multipart_matching() {
let mut index = HashMap::new();
// 数组形式:只匹配字段名
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: 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, object_rule]);
let router = MockRouter::new(index);
// 测试数组形式:匹配字段名
let mut full_data = HashMap::new();
full_data.insert("file".to_string(), "file_content".to_string());
full_data.insert("description".to_string(), "test file".to_string());
let full_payload = Payload::Multipart(full_data);
let matched = router.match_rule("POST", "/api/upload/array", &HashMap::new(), &HashMap::new(), &full_payload);
assert!(matched.is_some(), "包含所有字段应该匹配");
// 测试数组形式:缺少字段
let mut partial_data = HashMap::new();
partial_data.insert("file".to_string(), "file_content".to_string());
let partial_payload = Payload::Multipart(partial_data);
let not_matched = router.match_rule("POST", "/api/upload/array", &HashMap::new(), &HashMap::new(), &partial_payload);
assert!(not_matched.is_none(), "缺少字段不应该匹配");
// 测试对象形式:键名匹配(值被忽略)
let mut correct_data = HashMap::new();
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);
assert!(object_matched.is_some(), "键名匹配应该成功");
// 测试对象形式:键名不匹配
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());
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(), "缺少键名不应该成功");
}
/// 模块七:验证 XML 格式化匹配
#[test]
fn test_xml_normalized_matching() {
let mut index = HashMap::new();
// 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]);
let router = MockRouter::new(index);
// 测试 1紧凑格式的 XML
let compact_xml = Payload::Xml("<user><id>1001</id><name>张三</name></user>".to_string());
let matched1 = router.match_rule("POST", "/api/xml", &HashMap::new(), &HashMap::new(), &compact_xml);
assert!(matched1.is_some(), "紧凑格式 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 matched2 = router.match_rule("POST", "/api/xml", &HashMap::new(), &HashMap::new(), &xml_with_decl);
assert!(matched2.is_some(), "带声明的 XML 应该匹配");
// 测试 3内容不同不应该匹配
let wrong_xml = Payload::Xml("<user><id>1002</id><name>李四</name></user>".to_string());
let matched3 = router.match_rule("POST", "/api/xml", &HashMap::new(), &HashMap::new(), &wrong_xml);
assert!(matched3.is_none(), "内容不同的 XML 不应该匹配");
}