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:
@@ -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_' 开头");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user