- 更新 handler_test.rs 使用 RwLock<MockRouter> - 确保所有 43 个测试通过 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
442 lines
11 KiB
Rust
442 lines
11 KiB
Rust
use std::collections::HashMap;
|
|
use std::sync::{Arc, RwLock};
|
|
use axum::{
|
|
body::Body,
|
|
extract::Request,
|
|
http::{HeaderMap, HeaderValue, Method, StatusCode},
|
|
response::IntoResponse,
|
|
};
|
|
use mock_server::config::MockSource;
|
|
use mock_server::handler::{AppState, mock_handler};
|
|
use mock_server::router::MockRouter;
|
|
use tempfile::tempdir;
|
|
use std::fs::{self, File};
|
|
use std::io::Write;
|
|
use tokio::runtime::Runtime;
|
|
|
|
/// 创建带有 Mock 规则的 AppState
|
|
fn create_app_state_with_rules(rules: Vec<&str>) -> Arc<AppState> {
|
|
let temp_dir = tempdir().expect("无法创建临时目录");
|
|
let root_path = temp_dir.path();
|
|
|
|
let mut all_rules = Vec::new();
|
|
for (i, yaml_content) in rules.iter().enumerate() {
|
|
let file_path = root_path.join(format!("rule_{}.yaml", i));
|
|
let mut file = File::create(&file_path).unwrap();
|
|
writeln!(file, "{}", yaml_content).unwrap();
|
|
|
|
let content = fs::read_to_string(&file_path).unwrap();
|
|
if let Ok(source) = serde_yaml::from_str::<MockSource>(&content) {
|
|
all_rules.extend(source.flatten());
|
|
}
|
|
}
|
|
|
|
let mut index = HashMap::new();
|
|
for rule in all_rules {
|
|
let key = rule.request.path.trim_start_matches('/')
|
|
.split('/')
|
|
.next()
|
|
.unwrap_or("root")
|
|
.to_string();
|
|
index.entry(key).or_insert_with(Vec::new).push(rule);
|
|
}
|
|
|
|
let router = MockRouter::new(index);
|
|
Arc::new(AppState { router: RwLock::new(router) })
|
|
}
|
|
|
|
#[test]
|
|
fn test_handler_basic_request() {
|
|
let rt = Runtime::new().unwrap();
|
|
let yaml = r#"
|
|
id: "basic_test"
|
|
request:
|
|
method: "GET"
|
|
path: "/api/test"
|
|
response:
|
|
status: 200
|
|
body: "test response"
|
|
"#;
|
|
|
|
let state = create_app_state_with_rules(vec![yaml]);
|
|
|
|
rt.block_on(async {
|
|
let request = Request::builder()
|
|
.method(Method::GET)
|
|
.uri("/api/test")
|
|
.body(Body::empty())
|
|
.unwrap();
|
|
|
|
let response = mock_handler(
|
|
axum::extract::State(state),
|
|
Method::GET,
|
|
HeaderMap::new(),
|
|
axum::extract::Query(HashMap::new()),
|
|
request,
|
|
)
|
|
.await
|
|
.into_response();
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_handler_with_query_params() {
|
|
let rt = Runtime::new().unwrap();
|
|
let yaml = r#"
|
|
id: "query_test"
|
|
request:
|
|
method: "GET"
|
|
path: "/api/search"
|
|
query_params:
|
|
q: "rust"
|
|
response:
|
|
status: 200
|
|
body: "search results"
|
|
"#;
|
|
|
|
let state = create_app_state_with_rules(vec![yaml]);
|
|
|
|
rt.block_on(async {
|
|
let request = Request::builder()
|
|
.method(Method::GET)
|
|
.uri("/api/search?q=rust")
|
|
.body(Body::empty())
|
|
.unwrap();
|
|
|
|
let mut query_params = HashMap::new();
|
|
query_params.insert("q".to_string(), "rust".to_string());
|
|
|
|
let response = mock_handler(
|
|
axum::extract::State(state),
|
|
Method::GET,
|
|
HeaderMap::new(),
|
|
axum::extract::Query(query_params),
|
|
request,
|
|
)
|
|
.await
|
|
.into_response();
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_handler_with_headers() {
|
|
let rt = Runtime::new().unwrap();
|
|
let yaml = r#"
|
|
id: "header_test"
|
|
request:
|
|
method: "POST"
|
|
path: "/api/data"
|
|
headers:
|
|
Authorization: "Bearer token123"
|
|
Content-Type: "application/json"
|
|
response:
|
|
status: 200
|
|
body: "data response"
|
|
"#;
|
|
|
|
let state = create_app_state_with_rules(vec![yaml]);
|
|
|
|
rt.block_on(async {
|
|
let request = Request::builder()
|
|
.method(Method::POST)
|
|
.uri("/api/data")
|
|
.body(Body::empty())
|
|
.unwrap();
|
|
|
|
let mut headers = HeaderMap::new();
|
|
headers.insert("authorization", HeaderValue::from_static("Bearer token123"));
|
|
headers.insert("content-type", HeaderValue::from_static("application/json"));
|
|
|
|
let response = mock_handler(
|
|
axum::extract::State(state),
|
|
Method::POST,
|
|
headers,
|
|
axum::extract::Query(HashMap::new()),
|
|
request,
|
|
)
|
|
.await
|
|
.into_response();
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_handler_with_body() {
|
|
let rt = Runtime::new().unwrap();
|
|
let yaml = r#"
|
|
id: "body_test"
|
|
request:
|
|
method: "POST"
|
|
path: "/api/users"
|
|
body:
|
|
name: "John"
|
|
age: 30
|
|
response:
|
|
status: 201
|
|
body: "user created"
|
|
"#;
|
|
|
|
let state = create_app_state_with_rules(vec![yaml]);
|
|
|
|
rt.block_on(async {
|
|
let body_json = serde_json::json!({ "name": "John", "age": 30 });
|
|
let body_bytes = serde_json::to_vec(&body_json).unwrap();
|
|
|
|
let request = Request::builder()
|
|
.method(Method::POST)
|
|
.uri("/api/users")
|
|
.header("content-type", "application/json")
|
|
.body(Body::from(body_bytes))
|
|
.unwrap();
|
|
|
|
let response = mock_handler(
|
|
axum::extract::State(state),
|
|
Method::POST,
|
|
HeaderMap::new(),
|
|
axum::extract::Query(HashMap::new()),
|
|
request,
|
|
)
|
|
.await
|
|
.into_response();
|
|
|
|
assert_eq!(response.status(), StatusCode::CREATED);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_handler_no_match() {
|
|
let rt = Runtime::new().unwrap();
|
|
let yaml = r#"
|
|
id: "existing_rule"
|
|
request:
|
|
method: "GET"
|
|
path: "/api/existing"
|
|
response:
|
|
status: 200
|
|
body: "exists"
|
|
"#;
|
|
|
|
let state = create_app_state_with_rules(vec![yaml]);
|
|
|
|
rt.block_on(async {
|
|
let request = Request::builder()
|
|
.method(Method::GET)
|
|
.uri("/api/nonexistent")
|
|
.body(Body::empty())
|
|
.unwrap();
|
|
|
|
let response = mock_handler(
|
|
axum::extract::State(state),
|
|
Method::GET,
|
|
HeaderMap::new(),
|
|
axum::extract::Query(HashMap::new()),
|
|
request,
|
|
)
|
|
.await
|
|
.into_response();
|
|
|
|
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_handler_response_headers() {
|
|
let rt = Runtime::new().unwrap();
|
|
let yaml = r#"
|
|
id: "headers_test"
|
|
request:
|
|
method: "GET"
|
|
path: "/api/with-headers"
|
|
response:
|
|
status: 200
|
|
headers:
|
|
Content-Type: "application/json"
|
|
Cache-Control: "no-cache"
|
|
body: '{"key": "value"}'
|
|
"#;
|
|
|
|
let state = create_app_state_with_rules(vec![yaml]);
|
|
|
|
rt.block_on(async {
|
|
let request = Request::builder()
|
|
.method(Method::GET)
|
|
.uri("/api/with-headers")
|
|
.body(Body::empty())
|
|
.unwrap();
|
|
|
|
let response = mock_handler(
|
|
axum::extract::State(state),
|
|
Method::GET,
|
|
HeaderMap::new(),
|
|
axum::extract::Query(HashMap::new()),
|
|
request,
|
|
)
|
|
.await
|
|
.into_response();
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
assert_eq!(
|
|
response.headers().get("content-type").unwrap(),
|
|
"application/json"
|
|
);
|
|
assert_eq!(
|
|
response.headers().get("cache-control").unwrap(),
|
|
"no-cache"
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_handler_different_status_codes() {
|
|
let rt = Runtime::new().unwrap();
|
|
let yaml1 = r#"
|
|
id: "success"
|
|
request:
|
|
method: "GET"
|
|
path: "/api/success"
|
|
response:
|
|
status: 200
|
|
body: "ok"
|
|
"#;
|
|
|
|
let yaml2 = r#"
|
|
id: "created"
|
|
request:
|
|
method: "POST"
|
|
path: "/api/created"
|
|
response:
|
|
status: 201
|
|
body: "created"
|
|
"#;
|
|
|
|
let yaml3 = r#"
|
|
id: "error"
|
|
request:
|
|
method: "GET"
|
|
path: "/api/error"
|
|
response:
|
|
status: 500
|
|
body: "server error"
|
|
"#;
|
|
|
|
let state = create_app_state_with_rules(vec![yaml1, yaml2, yaml3]);
|
|
|
|
rt.block_on(async {
|
|
// 测试 200
|
|
let request1 = Request::builder()
|
|
.method(Method::GET)
|
|
.uri("/api/success")
|
|
.body(Body::empty())
|
|
.unwrap();
|
|
|
|
let response1 = mock_handler(
|
|
axum::extract::State(state.clone()),
|
|
Method::GET,
|
|
HeaderMap::new(),
|
|
axum::extract::Query(HashMap::new()),
|
|
request1,
|
|
)
|
|
.await
|
|
.into_response();
|
|
|
|
assert_eq!(response1.status(), StatusCode::OK);
|
|
|
|
// 测试 201
|
|
let request2 = Request::builder()
|
|
.method(Method::POST)
|
|
.uri("/api/created")
|
|
.body(Body::empty())
|
|
.unwrap();
|
|
|
|
let response2 = mock_handler(
|
|
axum::extract::State(state.clone()),
|
|
Method::POST,
|
|
HeaderMap::new(),
|
|
axum::extract::Query(HashMap::new()),
|
|
request2,
|
|
)
|
|
.await
|
|
.into_response();
|
|
|
|
assert_eq!(response2.status(), StatusCode::CREATED);
|
|
|
|
// 测试 500
|
|
let request3 = Request::builder()
|
|
.method(Method::GET)
|
|
.uri("/api/error")
|
|
.body(Body::empty())
|
|
.unwrap();
|
|
|
|
let response3 = mock_handler(
|
|
axum::extract::State(state),
|
|
Method::GET,
|
|
HeaderMap::new(),
|
|
axum::extract::Query(HashMap::new()),
|
|
request3,
|
|
)
|
|
.await
|
|
.into_response();
|
|
|
|
assert_eq!(response3.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_handler_complex_matching() {
|
|
let rt = Runtime::new().unwrap();
|
|
let yaml = r#"
|
|
id: "complex"
|
|
request:
|
|
method: "PUT"
|
|
path: "/api/users/123"
|
|
query_params:
|
|
update: "true"
|
|
headers:
|
|
Authorization: "Bearer secret"
|
|
body:
|
|
name: "Updated Name"
|
|
response:
|
|
status: 200
|
|
body: "updated"
|
|
"#;
|
|
|
|
let state = create_app_state_with_rules(vec![yaml]);
|
|
|
|
rt.block_on(async {
|
|
let body_json = serde_json::json!({ "name": "Updated Name" });
|
|
let body_bytes = serde_json::to_vec(&body_json).unwrap();
|
|
|
|
let mut headers = HeaderMap::new();
|
|
headers.insert("content-type", HeaderValue::from_static("application/json"));
|
|
headers.insert("authorization", HeaderValue::from_static("Bearer secret"));
|
|
|
|
let request = Request::builder()
|
|
.method(Method::PUT)
|
|
.uri("/api/users/123")
|
|
.header("content-type", "application/json")
|
|
.header("authorization", "Bearer secret")
|
|
.body(Body::from(body_bytes))
|
|
.unwrap();
|
|
|
|
let mut query_params = HashMap::new();
|
|
query_params.insert("update".to_string(), "true".to_string());
|
|
|
|
let response = mock_handler(
|
|
axum::extract::State(state),
|
|
Method::PUT,
|
|
headers,
|
|
axum::extract::Query(query_params),
|
|
request,
|
|
)
|
|
.await
|
|
.into_response();
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
});
|
|
}
|