- 新增 upload.rs 模块,支持 multipart/form-data 文件上传 - 文件按日期存储在 storage/YYYY-MM-DD/ 目录下 - 使用 UUID 生成唯一文件名,保留原始扩展名 - 添加 axum-extra, uuid, chrono 依赖 新增测试用例: - config_test.rs: 6 个测试 (配置结构验证) - router_test.rs: 11 个测试 (路由匹配逻辑) - handler_test.rs: 8 个测试 (请求处理) - upload_test.rs: 13 个测试 (文件上传功能) 其他改进: - 优化 handler.rs 代码注释 - 更新 .gitignore 忽略 storage/ 和 .claude/ - 添加 CLAUDE.md 项目指南文档 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
355 lines
10 KiB
Rust
355 lines
10 KiB
Rust
use mock_server::config::MockSource;
|
|
use mock_server::router::MockRouter;
|
|
use std::collections::HashMap;
|
|
use serde_json::json;
|
|
|
|
#[test]
|
|
fn test_router_basic_method_path_matching() {
|
|
let mut index = HashMap::new();
|
|
|
|
let rule = serde_yaml::from_str::<MockSource>(
|
|
r#"
|
|
id: "basic_test"
|
|
request:
|
|
method: "GET"
|
|
path: "/api/test"
|
|
response: { status: 200, body: "ok" }
|
|
"#,
|
|
)
|
|
.unwrap()
|
|
.flatten();
|
|
|
|
index.insert("api".to_string(), vec![rule[0].clone()]);
|
|
let router = MockRouter::new(index);
|
|
|
|
// 测试精确匹配
|
|
let matched = router.match_rule("GET", "/api/test", &HashMap::new(), &HashMap::new(), &None);
|
|
assert!(matched.is_some());
|
|
assert_eq!(matched.unwrap().id, "basic_test");
|
|
|
|
// 测试方法不匹配
|
|
let not_matched = router.match_rule("POST", "/api/test", &HashMap::new(), &HashMap::new(), &None);
|
|
assert!(not_matched.is_none());
|
|
|
|
// 测试路径不匹配
|
|
let not_matched = router.match_rule("GET", "/api/other", &HashMap::new(), &HashMap::new(), &None);
|
|
assert!(not_matched.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_router_case_insensitive_method() {
|
|
let mut index = HashMap::new();
|
|
|
|
let rule = serde_yaml::from_str::<MockSource>(
|
|
r#"
|
|
id: "case_test"
|
|
request:
|
|
method: "GET"
|
|
path: "/api/data"
|
|
response: { status: 200, body: "data" }
|
|
"#,
|
|
)
|
|
.unwrap()
|
|
.flatten();
|
|
|
|
index.insert("api".to_string(), vec![rule[0].clone()]);
|
|
let router = MockRouter::new(index);
|
|
|
|
// 测试不同大小写的方法匹配
|
|
assert!(router.match_rule("get", "/api/data", &HashMap::new(), &HashMap::new(), &None).is_some());
|
|
assert!(router.match_rule("GET", "/api/data", &HashMap::new(), &HashMap::new(), &None).is_some());
|
|
assert!(router.match_rule("Get", "/api/data", &HashMap::new(), &HashMap::new(), &None).is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn test_router_path_trailing_slash() {
|
|
let mut index = HashMap::new();
|
|
|
|
let rule = serde_yaml::from_str::<MockSource>(
|
|
r#"
|
|
id: "slash_test"
|
|
request:
|
|
method: "POST"
|
|
path: "/api/users"
|
|
response: { status: 200, body: "created" }
|
|
"#,
|
|
)
|
|
.unwrap()
|
|
.flatten();
|
|
|
|
index.insert("api".to_string(), vec![rule[0].clone()]);
|
|
let router = MockRouter::new(index);
|
|
|
|
// 测试带和不带斜杠的路径匹配
|
|
assert!(router.match_rule("POST", "/api/users", &HashMap::new(), &HashMap::new(), &None).is_some());
|
|
assert!(router.match_rule("POST", "/api/users/", &HashMap::new(), &HashMap::new(), &None).is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn test_router_query_params_matching() {
|
|
let mut index = HashMap::new();
|
|
|
|
let rule = serde_yaml::from_str::<MockSource>(
|
|
r#"
|
|
id: "query_test"
|
|
request:
|
|
method: "GET"
|
|
path: "/api/search"
|
|
query_params:
|
|
q: "rust"
|
|
limit: "10"
|
|
response: { status: 200, body: "results" }
|
|
"#,
|
|
)
|
|
.unwrap()
|
|
.flatten();
|
|
|
|
index.insert("api".to_string(), vec![rule[0].clone()]);
|
|
let router = MockRouter::new(index);
|
|
|
|
// 测试查询参数完全匹配
|
|
let mut queries = HashMap::new();
|
|
queries.insert("q".to_string(), "rust".to_string());
|
|
queries.insert("limit".to_string(), "10".to_string());
|
|
queries.insert("extra".to_string(), "value".to_string()); // 额外参数应该忽略
|
|
|
|
assert!(router.match_rule("GET", "/api/search", &queries, &HashMap::new(), &None).is_some());
|
|
|
|
// 测试查询参数不匹配
|
|
let mut wrong_queries = HashMap::new();
|
|
wrong_queries.insert("q".to_string(), "python".to_string());
|
|
|
|
assert!(router.match_rule("GET", "/api/search", &wrong_queries, &HashMap::new(), &None).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_router_headers_matching() {
|
|
let mut index = HashMap::new();
|
|
|
|
let rule = serde_yaml::from_str::<MockSource>(
|
|
r#"
|
|
id: "header_test"
|
|
request:
|
|
method: "GET"
|
|
path: "/api/data"
|
|
headers:
|
|
Authorization: "Bearer token123"
|
|
Content-Type: "application/json"
|
|
response: { status: 200, body: "data" }
|
|
"#,
|
|
)
|
|
.unwrap()
|
|
.flatten();
|
|
|
|
index.insert("api".to_string(), vec![rule[0].clone()]);
|
|
let router = MockRouter::new(index);
|
|
|
|
// 测试头部完全匹配(大小写不敏感)
|
|
let mut headers = HashMap::new();
|
|
headers.insert("authorization".to_string(), "Bearer token123".to_string());
|
|
headers.insert("content-type".to_string(), "application/json".to_string());
|
|
headers.insert("extra-header".to_string(), "value".to_string());
|
|
|
|
assert!(router.match_rule("GET", "/api/data", &HashMap::new(), &headers, &None).is_some());
|
|
|
|
// 测试头部不匹配
|
|
let mut wrong_headers = HashMap::new();
|
|
wrong_headers.insert("authorization".to_string(), "Bearer wrong".to_string());
|
|
|
|
assert!(router.match_rule("GET", "/api/data", &HashMap::new(), &wrong_headers, &None).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_router_body_matching() {
|
|
let mut index = HashMap::new();
|
|
|
|
let rule = serde_yaml::from_str::<MockSource>(
|
|
r#"
|
|
id: "body_test"
|
|
request:
|
|
method: "POST"
|
|
path: "/api/users"
|
|
body:
|
|
name: "John"
|
|
age: 30
|
|
response: { status: 200, body: "created" }
|
|
"#,
|
|
)
|
|
.unwrap()
|
|
.flatten();
|
|
|
|
index.insert("api".to_string(), vec![rule[0].clone()]);
|
|
let router = MockRouter::new(index);
|
|
|
|
// 测试 Body 完全匹配
|
|
let body = Some(json!({ "name": "John", "age": 30 }));
|
|
assert!(router.match_rule("POST", "/api/users", &HashMap::new(), &HashMap::new(), &body).is_some());
|
|
|
|
// 测试 Body 不匹配
|
|
let wrong_body = Some(json!({ "name": "Jane", "age": 25 }));
|
|
assert!(router.match_rule("POST", "/api/users", &HashMap::new(), &HashMap::new(), &wrong_body).is_none());
|
|
|
|
// 测试请求无 Body
|
|
assert!(router.match_rule("POST", "/api/users", &HashMap::new(), &HashMap::new(), &None).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_router_body_string_to_json() {
|
|
let mut index = HashMap::new();
|
|
|
|
// YAML 中 body 是字符串,但内容是 JSON
|
|
let rule = serde_yaml::from_str::<MockSource>(
|
|
r#"
|
|
id: "str_body_test"
|
|
request:
|
|
method: "POST"
|
|
path: "/api/login"
|
|
body: '{"username": "admin", "password": "secret"}'
|
|
response: { status: 200, body: "token" }
|
|
"#,
|
|
)
|
|
.unwrap()
|
|
.flatten();
|
|
|
|
index.insert("api".to_string(), vec![rule[0].clone()]);
|
|
let router = MockRouter::new(index);
|
|
|
|
// 请求中 body 是 JSON 对象,应该匹配
|
|
let body = Some(json!({ "username": "admin", "password": "secret" }));
|
|
assert!(router.match_rule("POST", "/api/login", &HashMap::new(), &HashMap::new(), &body).is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn test_router_multiple_rules_same_segment() {
|
|
let mut index = HashMap::new();
|
|
|
|
let rule1 = serde_yaml::from_str::<MockSource>(
|
|
r#"
|
|
id: "rule1"
|
|
request:
|
|
method: "GET"
|
|
path: "/api/users"
|
|
response: { status: 200, body: "users" }
|
|
"#,
|
|
)
|
|
.unwrap()
|
|
.flatten();
|
|
|
|
let rule2 = serde_yaml::from_str::<MockSource>(
|
|
r#"
|
|
id: "rule2"
|
|
request:
|
|
method: "POST"
|
|
path: "/api/users"
|
|
response: { status: 201, body: "created" }
|
|
"#,
|
|
)
|
|
.unwrap()
|
|
.flatten();
|
|
|
|
index.insert("api".to_string(), vec![rule1[0].clone(), rule2[0].clone()]);
|
|
let router = MockRouter::new(index);
|
|
|
|
// 测试不同方法匹配不同规则
|
|
let get_matched = router.match_rule("GET", "/api/users", &HashMap::new(), &HashMap::new(), &None);
|
|
assert!(get_matched.is_some());
|
|
assert_eq!(get_matched.unwrap().id, "rule1");
|
|
|
|
let post_matched = router.match_rule("POST", "/api/users", &HashMap::new(), &HashMap::new(), &None);
|
|
assert!(post_matched.is_some());
|
|
assert_eq!(post_matched.unwrap().id, "rule2");
|
|
}
|
|
|
|
#[test]
|
|
fn test_router_complex_matching_scenario() {
|
|
let mut index = HashMap::new();
|
|
|
|
let complex_rule = serde_yaml::from_str::<MockSource>(
|
|
r#"
|
|
id: "complex_rule"
|
|
request:
|
|
method: "PUT"
|
|
path: "/api/users/123"
|
|
query_params:
|
|
update: "true"
|
|
headers:
|
|
Authorization: "Bearer token456"
|
|
Content-Type: "application/json"
|
|
body:
|
|
name: "Updated"
|
|
response: { status: 200, body: "updated" }
|
|
"#,
|
|
)
|
|
.unwrap()
|
|
.flatten();
|
|
|
|
index.insert("api".to_string(), vec![complex_rule[0].clone()]);
|
|
let router = MockRouter::new(index);
|
|
|
|
// 测试完全匹配所有条件
|
|
let mut queries = HashMap::new();
|
|
queries.insert("update".to_string(), "true".to_string());
|
|
|
|
let mut headers = HashMap::new();
|
|
headers.insert("authorization".to_string(), "Bearer token456".to_string());
|
|
headers.insert("content-type".to_string(), "application/json".to_string());
|
|
|
|
let body = Some(json!({ "name": "Updated" }));
|
|
|
|
let matched = router.match_rule("PUT", "/api/users/123", &queries, &headers, &body);
|
|
assert!(matched.is_some());
|
|
assert_eq!(matched.unwrap().id, "complex_rule");
|
|
|
|
// 测试缺少任何条件都不匹配
|
|
let no_body_matched = router.match_rule("PUT", "/api/users/123", &queries, &headers, &None);
|
|
assert!(no_body_matched.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_router_root_path_handling() {
|
|
let mut index = HashMap::new();
|
|
|
|
let root_rule = serde_yaml::from_str::<MockSource>(
|
|
r#"
|
|
id: "root_rule"
|
|
request:
|
|
method: "GET"
|
|
path: "/"
|
|
response: { status: 200, body: "root" }
|
|
"#,
|
|
)
|
|
.unwrap()
|
|
.flatten();
|
|
|
|
// 根路径会被提取为空字符串 key
|
|
index.insert("".to_string(), vec![root_rule[0].clone()]);
|
|
let router = MockRouter::new(index);
|
|
|
|
// 测试根路径匹配
|
|
assert!(router.match_rule("GET", "/", &HashMap::new(), &HashMap::new(), &None).is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn test_router_nonexistent_key() {
|
|
let mut index = HashMap::new();
|
|
|
|
let rule = serde_yaml::from_str::<MockSource>(
|
|
r#"
|
|
id: "test_rule"
|
|
request:
|
|
method: "GET"
|
|
path: "/api/data"
|
|
response: { status: 200, body: "data" }
|
|
"#,
|
|
)
|
|
.unwrap()
|
|
.flatten();
|
|
|
|
index.insert("api".to_string(), vec![rule[0].clone()]);
|
|
let router = MockRouter::new(index);
|
|
|
|
// 测试请求不同段路径,应该不匹配
|
|
assert!(router.match_rule("GET", "/health", &HashMap::new(), &HashMap::new(), &None).is_none());
|
|
assert!(router.match_rule("GET", "/v2/data", &HashMap::new(), &HashMap::new(), &None).is_none());
|
|
}
|