#[cfg(test)]
mod tests {
use std::{
net::SocketAddr,
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
time::Duration,
};
use aex::{
connection::context::{Context, TypeMapExt},
exe,
http::{
meta::HttpMetadata,
protocol::{header::HeaderKey, status::StatusCode},
router::{NodeType, Router},
types::{Executor, to_executor},
},
server::{HTTPServer, Server},
tcp::types::{Command, RawCodec},
};
use futures::FutureExt;
use tokio::time::sleep;
#[test]
fn test_header_key_standard_match() {
let cases = [
("Content-Type", HeaderKey::ContentType),
("Host", HeaderKey::Host),
("Authorization", HeaderKey::Authorization),
];
for (input, expected_variant) in cases {
let parsed = HeaderKey::from_str(input).unwrap();
assert_eq!(parsed, expected_variant);
assert_eq!(parsed.as_str(), input);
}
}
#[test]
fn test_header_key_case_insensitivity() {
let mixed_cases = ["content-type", "CONTENT-TYPE", "CoNtEnT-TyPe"];
for input in mixed_cases {
let parsed = HeaderKey::from_str(input).unwrap();
assert_eq!(parsed, HeaderKey::ContentType);
assert_eq!(parsed.as_str(), "Content-Type");
}
}
#[test]
fn test_header_key_custom_fallback() {
let custom_name = "X-My-Custom-Header";
let parsed = HeaderKey::from_str(custom_name).unwrap();
match &parsed {
HeaderKey::Custom(s) => assert_eq!(s, custom_name),
_ => panic!("应当匹配为 Custom 变体"),
}
assert_eq!(parsed.as_str(), custom_name);
}
#[test]
fn test_header_key_display_trait() {
let key = HeaderKey::UserAgent;
assert_eq!(format!("{}", key), "User-Agent");
let custom = HeaderKey::Custom("X-Foo".to_string());
assert_eq!(format!("{}", custom), "X-Foo");
}
#[test]
fn test_header_key_roundtrip() {
let raw_standard = "Accept-Encoding";
let key = HeaderKey::from_str(raw_standard).unwrap();
assert_eq!(key.as_str(), raw_standard);
let raw_custom = "X-Tracing-Id";
let key_custom = HeaderKey::from_str(raw_custom).unwrap();
assert_eq!(key_custom.as_str(), raw_custom);
}
#[test]
fn test_header_key_trimming() {
let input = " Content-Type ";
let parsed = HeaderKey::from_str(input).unwrap();
assert_eq!(parsed, HeaderKey::ContentType);
}
#[tokio::test]
async fn test_server_auto_parsing_and_routing() {
let mut hr = Router::new(NodeType::Static("root".into()));
hr.insert(
"/api/user/:id",
Some("GET"),
Arc::new(|ctx: &mut Context| {
async move {
let mut meta = ctx.local.get_value::<HttpMetadata>().unwrap();
let user_id = meta
.params
.as_ref()
.and_then(|p| p.data.as_ref())
.and_then(|d| d.get("id"))
.cloned()
.unwrap_or_default();
let custom_key = HeaderKey::from_str("X-Aex-Auth").unwrap();
let auth_val = meta.headers.get(&custom_key).cloned().unwrap_or_default();
meta.status = StatusCode::Ok;
meta.body = format!("User:{}, Auth:{}", user_id, auth_val).into_bytes();
ctx.local.set_value(meta);
true
}
.boxed()
}),
None,
);
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
let actual_addr = listener.local_addr().unwrap();
drop(listener);
let server = Server::new(actual_addr, None);
let server = server.http(hr).clone();
tokio::spawn(async move {
if let Err(e) = server
.start()
.await
{
eprintln!("Server exit: {}", e);
}
});
let client = reqwest::Client::new();
let mut res = None;
let url = format!("http://{}/api/user/9527", actual_addr);
for _ in 0..10 {
tokio::time::sleep(Duration::from_millis(100)).await;
match client
.get(&url)
.header("X-Aex-Auth", "secret-token-v3")
.send()
.await
{
Ok(response) => {
res = Some(response);
break;
}
Err(_) => continue, }
}
let res = res.expect("服务器在重试多次后仍未就绪 (Connection Refused)");
assert_eq!(res.status().as_u16(), 200);
let body = res.text().await.unwrap();
assert_eq!(body, "User:9527, Auth:secret-token-v3");
println!("✅ Server 全链路 Router 自动化集成测试通过!");
}
#[tokio::test]
async fn test_full_stack_wildcard_and_middleware() {
let mw_exec_order = Arc::new(std::sync::Mutex::new(Vec::new()));
let wildcard_hit_count = Arc::new(AtomicUsize::new(0));
let mut hr = Router::new(NodeType::Static("root".into()));
let t1 = mw_exec_order.clone();
let t2 = mw_exec_order.clone();
let mw_a: Arc<Executor> = Arc::new(move |_ctx| {
let t = t1.clone();
async move {
t.lock().unwrap().push("MW_A");
true
}
.boxed()
});
let mw_b: Arc<Executor> = Arc::new(move |_ctx| {
let t = t2.clone();
async move {
t.lock().unwrap().push("MW_B");
true
}
.boxed()
});
let count = wildcard_hit_count.clone();
hr.insert(
"/assets/*",
Some("GET"),
Arc::new(move |ctx: &mut Context| {
let c = count.clone();
async move {
c.fetch_add(1, Ordering::SeqCst);
let mut meta = ctx.local.get_value::<HttpMetadata>().unwrap();
meta.status = StatusCode::Ok;
meta.body = b"Wildcard Matched".to_vec();
ctx.local.set_value(meta);
true
}
.boxed()
}),
Some(vec![mw_a, mw_b]), );
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
let actual_addr = listener.local_addr().unwrap();
drop(listener);
let server = Server::new(actual_addr, None);
let server = server.http(hr).clone();
tokio::spawn(async move {
let _ = server
.start()
.await;
});
let client = reqwest::Client::new();
let mut res = None;
for _ in 0..10 {
sleep(Duration::from_millis(100)).await;
if let Ok(r) = client
.get(format!("http://{}/assets/css/main.css", actual_addr))
.send()
.await
{
res = Some(r);
break;
}
}
let response = res.expect("Server failed to respond");
assert_eq!(response.status().as_u16(), 200);
assert_eq!(response.text().await.unwrap(), "Wildcard Matched");
assert_eq!(wildcard_hit_count.load(Ordering::SeqCst), 1);
let order = mw_exec_order.lock().unwrap();
assert_eq!(*order, vec!["MW_A", "MW_B"]);
}
#[tokio::test]
async fn test_middleware_interruption_full_stack() {
let mut hr = Router::new(NodeType::Static("root".into()));
let handler_executed = Arc::new(AtomicUsize::new(0));
let mw_blocker: Arc<Executor> = Arc::new(|ctx: &mut Context| {
async move {
let mut meta = ctx.local.get_value::<HttpMetadata>().unwrap();
meta.status = StatusCode::Forbidden; meta.body = b"Blocked".to_vec();
ctx.local.set_value(meta);
false }
.boxed()
});
let h_count = handler_executed.clone();
hr.insert(
"/admin",
Some("GET"),
Arc::new(move |_| {
let c = h_count.clone();
async move {
c.fetch_add(1, Ordering::SeqCst);
true
}
.boxed()
}),
Some(vec![mw_blocker]),
);
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let actual_addr = tokio::net::TcpListener::bind(addr)
.await
.unwrap()
.local_addr()
.unwrap();
let server = HTTPServer::new(actual_addr, None);
let server = server.http(hr).clone();
tokio::spawn(async move {
let _ = server
.start()
.await;
});
sleep(Duration::from_millis(200)).await;
let res = reqwest::get(format!("http://{}/admin", actual_addr))
.await
.unwrap();
assert_eq!(res.status().as_u16(), 403);
let text = res.text().await.unwrap();
assert!(text.contains("Blocked"));
assert_eq!(handler_executed.load(Ordering::SeqCst), 0); }
#[tokio::test]
async fn test_router_404_not_found() {
let hr = Router::new(NodeType::Static("root".into()));
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let actual_addr = tokio::net::TcpListener::bind(addr)
.await
.unwrap()
.local_addr()
.unwrap();
let server = Server::new(actual_addr, None);
let server = server.http(hr).clone();
tokio::spawn(async move {
let _ = server
.start()
.await;
});
tokio::time::sleep(Duration::from_millis(200)).await;
let res = reqwest::get(format!("http://{}/undefined/path", actual_addr))
.await
.unwrap();
assert_eq!(res.status().as_u16(), 404);
}
#[tokio::test]
async fn test_router_404_fallback() {
let hr = Router::new(NodeType::Static("root".into()));
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let actual_addr = tokio::net::TcpListener::bind(addr)
.await
.unwrap()
.local_addr()
.unwrap();
let server = HTTPServer::new(actual_addr, None);
let server = server.http(hr).clone();
tokio::spawn(async move {
let _ = server
.start()
.await;
});
tokio::time::sleep(Duration::from_millis(200)).await;
let res = reqwest::get(format!("http://{}/not_exists", actual_addr))
.await
.unwrap();
assert_eq!(res.status().as_u16(), 404);
}
#[tokio::test]
async fn test_form_body_auto_parsing() {
let mut hr = Router::new(NodeType::Static("root".into()));
hr.insert(
"/submit",
Some("POST"),
Arc::new(|ctx: &mut Context| {
async move {
let mut meta = ctx.local.get_value::<HttpMetadata>().unwrap();
println!("meta.params = {:?}", meta.params);
let user = meta
.params
.as_ref()
.and_then(|p| p.form.as_ref())
.and_then(|f| f.get("user"))
.cloned()
.unwrap_or_default();
meta.status = StatusCode::Ok;
meta.body = format!("User:{}", user.get(0).unwrap()).into_bytes();
println!("meta = {:?}", meta);
ctx.local.set_value(meta);
true
}
.boxed()
}),
None,
);
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let actual_addr = tokio::net::TcpListener::bind(addr)
.await
.unwrap()
.local_addr()
.unwrap();
let server = HTTPServer::new(actual_addr, None);
let server = server.http(hr).clone();
tokio::spawn(async move {
let _ = server
.start()
.await;
});
sleep(Duration::from_millis(200)).await;
let client = reqwest::Client::new();
let body_str = "user=Gemini&age=20";
let res = client
.post(format!("http://{}/submit", actual_addr))
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Content-Length", body_str.len().to_string())
.body(body_str)
.send()
.await
.expect("Request failed");
assert_eq!(res.status().as_u16(), 200);
let text = res.text().await.unwrap();
assert_eq!(text, "User:Gemini");
}
#[tokio::test]
async fn test_wildcard_method_matching() {
let mut hr = Router::new(NodeType::Static("root".into()));
hr.insert(
"/universal",
None, Arc::new(|ctx: &mut Context| {
async move {
let mut meta = ctx.local.get_value::<HttpMetadata>().unwrap();
let method = meta.method.to_str().to_owned();
meta.status = StatusCode::Ok;
meta.body = format!("Method:{} handled by *", method).into_bytes();
let cl_key = HeaderKey::from_str("Content-Length").unwrap();
meta.headers.insert(cl_key, meta.body.len().to_string());
ctx.local.set_value(meta);
true
}
.boxed()
}),
None,
);
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let actual_addr = tokio::net::TcpListener::bind(addr)
.await
.unwrap()
.local_addr()
.unwrap();
let server = HTTPServer::new(actual_addr, None);
let server = server.http(hr).clone();
tokio::spawn(async move {
let _ = server
.start()
.await;
});
tokio::time::sleep(Duration::from_millis(200)).await;
let client = reqwest::Client::new();
let res_put = client
.put(format!("http://{}/universal", actual_addr))
.send()
.await
.unwrap();
assert_eq!(res_put.status().as_u16(), 200);
assert_eq!(res_put.text().await.unwrap(), "Method:PUT handled by *");
let res_delete = client
.delete(format!("http://{}/universal", actual_addr))
.send()
.await
.unwrap();
assert_eq!(
res_delete.text().await.unwrap(),
"Method:DELETE handled by *"
);
}
#[tokio::test]
async fn test_wildcard_method_middleware() {
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let actual_addr = tokio::net::TcpListener::bind(addr)
.await
.unwrap()
.local_addr()
.unwrap();
let mut hr = Router::new(NodeType::Static("root".into()));
let mw_hit_count = Arc::new(AtomicUsize::new(0));
let mut server = HTTPServer::new(actual_addr, None);
let count = mw_hit_count.clone();
let mw_any: Arc<Executor> = Arc::new(move |_| {
let c = count.clone();
async move {
c.fetch_add(1, Ordering::SeqCst);
true
}
.boxed()
});
hr.insert(
"/api",
None,
Arc::new(|_| async { true }.boxed()),
Some(vec![mw_any]),
);
let server = server.http(hr);
tokio::spawn(async move {
let _ = server
.start()
.await;
});
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
let client = reqwest::Client::new();
let res_get = client
.get(format!("http://{}/api", actual_addr))
.send()
.await;
match res_get {
Ok(_) => println!("GET request sent successfully"),
Err(e) => println!("GET request failed: {:?}", e),
}
let _ = client
.post(format!("http://{}/api", actual_addr))
.send()
.await;
assert_eq!(mw_hit_count.load(Ordering::SeqCst), 2);
}
#[tokio::test]
async fn test_wildcard_method_middleware_with_executor() {
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let actual_addr = tokio::net::TcpListener::bind(addr)
.await
.unwrap()
.local_addr()
.unwrap();
let mut hr = Router::new(NodeType::Static("root".into()));
let mw_hit_count = Arc::new(AtomicUsize::new(0));
let count = mw_hit_count.clone();
let mw_counter = to_executor(move |_| {
let c = count.clone();
async move {
c.fetch_add(1, Ordering::SeqCst);
true
}
.boxed()
});
let mw_header_check = to_executor(|_ctx| {
async move {
println!("Middleware 2: Checking request...");
true
}
.boxed()
});
hr.insert(
"/api",
None, Arc::new(|_| async { true }.boxed()),
Some(vec![mw_counter, mw_header_check]), );
let server = HTTPServer::new(actual_addr, None).http(hr).clone();
tokio::spawn(async move {
let _ = server
.start()
.await;
});
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
let client = reqwest::Client::new();
let _ = client
.get(format!("http://{}/api", actual_addr))
.send()
.await;
let _ = client
.post(format!("http://{}/api", actual_addr))
.send()
.await;
assert_eq!(mw_hit_count.load(Ordering::SeqCst), 2);
}
#[tokio::test]
async fn test_full_macros_suite() {
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let actual_addr = tokio::net::TcpListener::bind(addr)
.await
.unwrap()
.local_addr()
.unwrap();
let mut hr = Router::new(NodeType::Static("root".into()));
let mw_info = exe!(
|ctx, info| {
async move { true }.await;
true
},
|_ctx| { "info".to_string() }
);
let handler: Arc<Executor> = exe!(|ctx| {
let mut meta = ctx.local.get_value::<HttpMetadata>().unwrap();
meta.body = b"Macro OK".to_vec();
ctx.local.set_value(meta);
true
});
hr.all("/api/all", handler.clone()).middleware(mw_info.clone()).register();
hr.get("/api/specific", handler).middleware(mw_info).register();
let server = HTTPServer::new(actual_addr, None).http(hr).clone();
tokio::spawn(async move {
let _ = server
.start()
.await;
});
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
let client = reqwest::Client::new();
let _ = client
.post(format!("http://{}/api/all", actual_addr))
.send()
.await;
let _ = client
.get(format!("http://{}/api/specific", actual_addr))
.send()
.await;
}
#[tokio::test]
async fn test_full_macros_suite1() {
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let actual_addr = tokio::net::TcpListener::bind(addr)
.await
.unwrap()
.local_addr()
.unwrap();
let mut hr = Router::new(NodeType::Static("root".into()));
let mw_info = exe!(
|ctx, info| {
let mut meta = ctx.local.get_value::<HttpMetadata>().unwrap();
meta.headers
.insert(HeaderKey::from_str("X-Macro-Info").unwrap(), info);
ctx.local.set_value(meta);
async move { true }.await;
true
},
|_ctx| {
"processed-by-macro".to_string()
}
);
let handler: Arc<Executor> = exe!(|ctx| {
let mut meta = ctx.local.get_value::<HttpMetadata>().unwrap();
meta.body = b"Macro OK".to_vec();
ctx.local.set_value(meta);
true
});
hr.all("/api/all", handler.clone()).middleware(mw_info.clone()).register();
hr.get("/api/specific", handler).middleware(mw_info).register();
let server = HTTPServer::new(actual_addr, None).http(hr).clone();
tokio::spawn(async move {
let _ = server
.start()
.await;
});
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
let client = reqwest::Client::new();
let resp_all = client
.post(format!("http://{}/api/all", actual_addr))
.send()
.await
.expect("POST request failed");
assert_eq!(
resp_all.headers().get("X-Macro-Info").unwrap(),
"processed-by-macro"
);
assert_eq!(resp_all.text().await.unwrap(), "Macro OK");
let resp_get = client
.get(format!("http://{}/api/specific", actual_addr))
.send()
.await
.expect("GET request failed");
assert_eq!(resp_get.status(), 200);
assert_eq!(resp_get.text().await.unwrap(), "Macro OK");
println!("Full macro suite test passed!");
}
#[tokio::test]
async fn test_router_put_method() {
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let actual_addr = tokio::net::TcpListener::bind(addr)
.await
.unwrap()
.local_addr()
.unwrap();
let mut hr = Router::new(NodeType::Static("root".into()));
hr.put("/data", Arc::new(|ctx: &mut Context| {
async move {
let mut meta = ctx.local.get_value::<HttpMetadata>().unwrap();
meta.status = StatusCode::Ok;
meta.body = b"PUT OK".to_vec();
ctx.local.set_value(meta);
true
}
.boxed()
})).register();
let server = HTTPServer::new(actual_addr, None).http(hr).clone();
tokio::spawn(async move {
let _ = server.start().await;
});
tokio::time::sleep(Duration::from_millis(200)).await;
let client = reqwest::Client::new();
let res = client.put(format!("http://{}/data", actual_addr)).send().await.unwrap();
assert_eq!(res.status().as_u16(), 200);
}
#[tokio::test]
async fn test_router_delete_method() {
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let actual_addr = tokio::net::TcpListener::bind(addr)
.await
.unwrap()
.local_addr()
.unwrap();
let mut hr = Router::new(NodeType::Static("root".into()));
hr.delete("/item/:id", Arc::new(|ctx: &mut Context| {
async move {
let mut meta = ctx.local.get_value::<HttpMetadata>().unwrap();
meta.status = StatusCode::Ok;
meta.body = b"DELETED".to_vec();
ctx.local.set_value(meta);
true
}
.boxed()
})).register();
let server = HTTPServer::new(actual_addr, None).http(hr).clone();
tokio::spawn(async move {
let _ = server.start().await;
});
tokio::time::sleep(Duration::from_millis(200)).await;
let client = reqwest::Client::new();
let res = client.delete(format!("http://{}/item/123", actual_addr)).send().await.unwrap();
assert_eq!(res.status().as_u16(), 200);
assert_eq!(res.text().await.unwrap(), "DELETED");
}
#[tokio::test]
async fn test_router_patch_method() {
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let actual_addr = tokio::net::TcpListener::bind(addr)
.await
.unwrap()
.local_addr()
.unwrap();
let mut hr = Router::new(NodeType::Static("root".into()));
hr.patch("/update", Arc::new(|ctx: &mut Context| {
async move {
let mut meta = ctx.local.get_value::<HttpMetadata>().unwrap();
meta.status = StatusCode::Ok;
meta.body = b"PATCHED".to_vec();
ctx.local.set_value(meta);
true
}
.boxed()
})).register();
let server = HTTPServer::new(actual_addr, None).http(hr).clone();
tokio::spawn(async move {
let _ = server.start().await;
});
tokio::time::sleep(Duration::from_millis(200)).await;
let client = reqwest::Client::new();
let res = client.patch(format!("http://{}/update", actual_addr)).send().await.unwrap();
assert_eq!(res.status().as_u16(), 200);
}
#[tokio::test]
async fn test_router_options_method() {
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let actual_addr = tokio::net::TcpListener::bind(addr)
.await
.unwrap()
.local_addr()
.unwrap();
let mut hr = Router::new(NodeType::Static("root".into()));
hr.options("/api", Arc::new(|ctx: &mut Context| {
async move {
let mut meta = ctx.local.get_value::<HttpMetadata>().unwrap();
meta.status = StatusCode::Ok;
meta.body = b"OPTIONS OK".to_vec();
ctx.local.set_value(meta);
true
}
.boxed()
})).register();
let server = HTTPServer::new(actual_addr, None).http(hr).clone();
tokio::spawn(async move {
let _ = server.start().await;
});
tokio::time::sleep(Duration::from_millis(200)).await;
let client = reqwest::Client::new();
let res = client.request(reqwest::Method::OPTIONS, format!("http://{}/api", actual_addr)).send().await.unwrap();
assert_eq!(res.status().as_u16(), 200);
}
#[tokio::test]
async fn test_router_head_method() {
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let actual_addr = tokio::net::TcpListener::bind(addr)
.await
.unwrap()
.local_addr()
.unwrap();
let mut hr = Router::new(NodeType::Static("root".into()));
hr.head("/head-test", Arc::new(|ctx: &mut Context| {
async move {
let mut meta = ctx.local.get_value::<HttpMetadata>().unwrap();
meta.status = StatusCode::Ok;
ctx.local.set_value(meta);
true
}
.boxed()
})).register();
let server = HTTPServer::new(actual_addr, None).http(hr).clone();
tokio::spawn(async move {
let _ = server.start().await;
});
tokio::time::sleep(Duration::from_millis(200)).await;
let client = reqwest::Client::new();
let res = client.head(format!("http://{}/head-test", actual_addr)).send().await.unwrap();
assert_eq!(res.status().as_u16(), 200);
}
}