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::loader::MockLoader; use mock_server::router::MockRouter; use std::collections::HashMap; /// 模块一:验证 Config 反序列化逻辑 #[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"); // 场景 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://")); } /// 模块二:验证 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("single_login.yaml")).unwrap(); writeln!(f1, "name: 'l1'\nrequest: {{ method: 'POST', path: '/api/v1/login' }}\nresponse: {{ 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 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_auth = serde_yaml::from_str::( 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(); index.insert("api".to_string(), vec![rule_auth[0].clone()]); let router = MockRouter::new(index); // 2. 测试场景 A:JSON 完全匹配 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. 测试场景 B:Body 不匹配 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 = serde_yaml::from_str::( r#" name: "xml_api" request: method: "POST" path: "/api/xml" body: "test" response: { status: 200, body: "ok" } "#, ) .unwrap() .flatten(); // Form 规则 let form_rule = serde_yaml::from_str::( r#" name: "form_api" request: method: "POST" path: "/api/form" body: { "username": "admin", "password": "123456" } response: { status: 200, body: "login_ok" } "#, ) .unwrap() .flatten(); // Text 规则 let text_rule = serde_yaml::from_str::( r#" name: "text_api" request: method: "POST" path: "/api/text" body: "plain text content" response: { status: 200, body: "text_ok" } "#, ) .unwrap() .flatten(); index.insert("api".to_string(), vec![xml_rule[0].clone(), form_rule[0].clone(), text_rule[0].clone()]); let router = MockRouter::new(index); // 测试 XML 匹配 let xml_payload = Payload::Xml("test".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 = serde_yaml::from_str::( r#" name: "upload_array" request: method: "POST" path: "/api/upload/array" body: ["file", "description"] response: { status: 200, body: "ok" } "#, ) .unwrap() .flatten(); // 对象形式:匹配键值对 let object_rule = serde_yaml::from_str::( r#" name: "upload_object" request: method: "POST" path: "/api/upload/object" body: { "username": "admin", "role": "user" } response: { status: 200, body: "ok" } "#, ) .unwrap() .flatten(); index.insert("api".to_string(), vec![array_rule[0].clone(), object_rule[0].clone()]); 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()); // 缺少 role 字段 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::( 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::( 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 格式化匹配 #[test] fn test_xml_normalized_matching() { let mut index = HashMap::new(); // YAML 中的 XML(带格式化) let xml_rule = serde_yaml::from_str::( r#" name: "xml_api" request: method: "POST" path: "/api/xml" body: | 1001 张三 response: status: 200 body: "ok" "#, ) .unwrap() .flatten(); index.insert("api".to_string(), vec![xml_rule[0].clone()]); let router = MockRouter::new(index); // 测试 1:完全相同的格式化 XML let formatted_xml = Payload::Xml("\n 1001\n 张三\n".to_string()); let matched1 = router.match_rule("POST", "/api/xml", &HashMap::new(), &HashMap::new(), &formatted_xml); assert!(matched1.is_some(), "格式化 XML 应该匹配"); // 测试 2:紧凑格式的 XML let compact_xml = Payload::Xml("1001张三".to_string()); let matched2 = router.match_rule("POST", "/api/xml", &HashMap::new(), &HashMap::new(), &compact_xml); assert!(matched2.is_some(), "紧凑格式 XML 应该匹配"); // 测试 3:带 XML 声明的请求 let xml_with_decl = Payload::Xml("1001张三".to_string()); let matched3 = router.match_rule("POST", "/api/xml", &HashMap::new(), &HashMap::new(), &xml_with_decl); assert!(matched3.is_some(), "带声明的 XML 应该匹配"); // 测试 4:内容不同,不应该匹配 let wrong_xml = Payload::Xml("1002李四".to_string()); let matched4 = router.match_rule("POST", "/api/xml", &HashMap::new(), &HashMap::new(), &wrong_xml); assert!(matched4.is_none(), "内容不同的 XML 不应该匹配"); }