use std::fs::{self, File}; use std::io::Write; use tempfile::tempdir; // 确保 Cargo.toml 中有 serde_json use serde_json::json; use mock_server::config::{MockRule, MockSource}; 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#" id: "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#" - id: "api_1" request: { method: "GET", path: "/health" } response: { status: 200, body: "ok" } - id: "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#" id: "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 递归扫描与索引构建 (不涉及 Matcher 逻辑,基本保持不变) #[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.yaml")).unwrap(); writeln!(f1, "id: '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, "- id: '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); } #[test] fn test_router_matching_logic() { let mut index = HashMap::new(); // 1. 准备带有 Body 的规则 let rule_auth = serde_yaml::from_str::( r#" id: "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:完全匹配 (包括 Body) let mut headers = HashMap::new(); headers.insert("Content-Type".to_string(), "application/json".to_string()); // 构造请求 Body let incoming_body = Some(json!({ "code": 123 })); let matched = router.match_rule( "POST", "/api/v1/login", &HashMap::new(), &headers, &incoming_body // 传入新参数 ); assert!(matched.is_some()); assert_eq!(matched.unwrap().id, "auth_v1"); // 3. 测试场景 B:Body 不匹配 let wrong_body = Some(json!({ "code": 456 })); let matched_fail = router.match_rule( "POST", "/api/v1/login", &HashMap::new(), &headers, &wrong_body ); assert!(matched_fail.is_none(), "Body 不一致时不应匹配成功"); // 4. 测试场景 C:智能字符串转换验证 (YAML 是字符串,请求是对象) let rule_str_body = serde_yaml::from_str::( r#" id: "str_match" request: method: "POST" path: "/api/str" body: '{"type": "json_in_string"}' # 这里 YAML 解析为 Value::String response: { status: 200, body: "ok" } "#).unwrap().flatten(); let mut index2 = HashMap::new(); index2.insert("api".to_string(), vec![rule_str_body[0].clone()]); let router2 = MockRouter::new(index2); let incoming_obj = Some(json!({ "type": "json_in_string" })); // 请求是 JSON 对象 let matched_str = router2.match_rule( "POST", "/api/str", &HashMap::new(), &HashMap::new(), &incoming_obj ); assert!(matched_str.is_some(), "应该支持将 YAML 字符串 body 转换为对象进行匹配"); // 5. 测试场景 D:末尾斜杠兼容性测试 let matched_slash = router.match_rule( "POST", "/api/v1/login/", &HashMap::new(), &headers, &incoming_body ); assert!(matched_slash.is_some()); }