use std::sync::atomic::{AtomicU16, Ordering};
use std::time::Duration;
use tokio::time::sleep;
static PORT_COUNTER: AtomicU16 = AtomicU16::new(18000);
fn get_test_port() -> u16 {
PORT_COUNTER.fetch_add(1, Ordering::Relaxed)
}
#[cfg(test)]
mod tests {
use super::*;
use potato::{Headers, HttpRequest, HttpResponse, HttpServer, Session, Websocket, WsFrame};
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
use std::sync::{LazyLock, Mutex};
#[tokio::test]
async fn test_http_server_basic() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
let mut server = HttpServer::new(&server_addr);
#[potato::http_get("/hello")]
async fn hello() -> HttpResponse {
HttpResponse::html("hello world")
}
let server_handle = tokio::spawn(async move {
let _ = server.serve_http().await;
});
sleep(Duration::from_millis(300)).await;
let url = format!("http://{server_addr}/hello");
match potato::get(&url, vec![]).await {
Ok(res) => {
println!("HTTP Server /hello response: {}", res.http_code);
let body = match &res.body {
potato::HttpResponseBody::Data(data) => {
String::from_utf8(data.clone()).unwrap_or_default()
}
potato::HttpResponseBody::Stream(_) => "stream response".to_string(),
};
println!("Response body: {}", body);
assert!(res.http_code == 200);
assert!(body.contains("hello world"));
}
Err(e) => {
println!("HTTP Server request error: {e}");
}
}
server_handle.abort();
println!("✅ Basic HTTP server test completed");
Ok(())
}
#[tokio::test]
async fn test_handler_args_server() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
let mut server = HttpServer::new(&server_addr);
#[potato::http_get("/hello")]
async fn hello(req: &mut HttpRequest) -> anyhow::Result<HttpResponse> {
let _addr = req.get_client_addr().await?;
Ok(HttpResponse::html("hello client"))
}
#[potato::http_get("/hello_user")]
async fn hello_user(name: String) -> HttpResponse {
HttpResponse::html(format!("hello {name}"))
}
let server_handle = tokio::spawn(async move {
let _ = server.serve_http().await;
});
sleep(Duration::from_millis(300)).await;
let url = format!("http://{server_addr}/hello");
match potato::get(&url, vec![]).await {
Ok(res) => {
println!("Handler with HttpRequest: {}", res.http_code);
}
Err(e) => {
println!("HttpRequest handler error: {e}");
}
}
let url = format!("http://{server_addr}/hello_user?name=World");
match potato::get(&url, vec![]).await {
Ok(res) => {
println!("Handler with String param: {}", res.http_code);
let body = match &res.body {
potato::HttpResponseBody::Data(data) => {
String::from_utf8(data.clone()).unwrap_or_default()
}
potato::HttpResponseBody::Stream(_) => "stream response".to_string(),
};
if res.http_code == 200 {
assert!(body.contains("hello World"));
}
}
Err(e) => {
println!("String param handler error: {e}");
}
}
server_handle.abort();
println!("✅ Handler args test completed");
Ok(())
}
#[tokio::test]
async fn test_preprocess_postprocess_hooks() -> anyhow::Result<()> {
static HOOK_ORDER_LOG: LazyLock<Mutex<Vec<&'static str>>> =
LazyLock::new(|| Mutex::new(vec![]));
static PRE_ASYNC_RESULT_UNIT_COUNT: AtomicUsize = AtomicUsize::new(0);
static PRE_ASYNC_RESULT_OPTION_COUNT: AtomicUsize = AtomicUsize::new(0);
static PRE_SYNC_UNIT_COUNT: AtomicUsize = AtomicUsize::new(0);
static PRE_SYNC_OPTION_COUNT: AtomicUsize = AtomicUsize::new(0);
static PRE_ASYNC_OPTION_COUNT: AtomicUsize = AtomicUsize::new(0);
static POST_SYNC_UNIT_COUNT: AtomicUsize = AtomicUsize::new(0);
static POST_ASYNC_RESULT_UNIT_COUNT: AtomicUsize = AtomicUsize::new(0);
static HANDLER_COUNT: AtomicUsize = AtomicUsize::new(0);
PRE_ASYNC_RESULT_UNIT_COUNT.store(0, AtomicOrdering::Relaxed);
PRE_ASYNC_RESULT_OPTION_COUNT.store(0, AtomicOrdering::Relaxed);
PRE_SYNC_UNIT_COUNT.store(0, AtomicOrdering::Relaxed);
PRE_SYNC_OPTION_COUNT.store(0, AtomicOrdering::Relaxed);
PRE_ASYNC_OPTION_COUNT.store(0, AtomicOrdering::Relaxed);
POST_SYNC_UNIT_COUNT.store(0, AtomicOrdering::Relaxed);
POST_ASYNC_RESULT_UNIT_COUNT.store(0, AtomicOrdering::Relaxed);
HANDLER_COUNT.store(0, AtomicOrdering::Relaxed);
HOOK_ORDER_LOG.lock().unwrap().clear();
fn body_to_string(res: &potato::HttpResponse) -> String {
match &res.body {
potato::HttpResponseBody::Data(data) => String::from_utf8(data.clone()).unwrap(),
potato::HttpResponseBody::Stream(_) => "stream response".to_string(),
}
}
fn append_body(res: &mut HttpResponse, value: &str) {
if let potato::HttpResponseBody::Data(body) = &mut res.body {
body.extend_from_slice(value.as_bytes());
}
}
#[potato::preprocess]
async fn pre_async_result_unit(_req: &mut potato::HttpRequest) -> anyhow::Result<()> {
PRE_ASYNC_RESULT_UNIT_COUNT.fetch_add(1, AtomicOrdering::Relaxed);
HOOK_ORDER_LOG.lock().unwrap().push("pre_async_result_unit");
Ok(())
}
#[potato::preprocess]
async fn pre_async_result_option(
req: &mut potato::HttpRequest,
) -> anyhow::Result<Option<potato::HttpResponse>> {
PRE_ASYNC_RESULT_OPTION_COUNT.fetch_add(1, AtomicOrdering::Relaxed);
HOOK_ORDER_LOG
.lock()
.unwrap()
.push("pre_async_result_option");
let mode = req
.url_query
.get(&potato::hipstr::LocalHipStr::from("mode"))
.map(|v| v.as_str())
.unwrap_or_default();
if mode == "skip2" {
Ok(Some(potato::HttpResponse::text("pre_skip2")))
} else {
Ok(None)
}
}
#[potato::preprocess]
fn pre_sync_unit(_req: &mut potato::HttpRequest) {
PRE_SYNC_UNIT_COUNT.fetch_add(1, AtomicOrdering::Relaxed);
HOOK_ORDER_LOG.lock().unwrap().push("pre_sync_unit");
}
#[potato::preprocess]
fn pre_sync_option(req: &mut potato::HttpRequest) -> Option<potato::HttpResponse> {
PRE_SYNC_OPTION_COUNT.fetch_add(1, AtomicOrdering::Relaxed);
HOOK_ORDER_LOG.lock().unwrap().push("pre_sync_option");
let mode = req
.url_query
.get(&potato::hipstr::LocalHipStr::from("mode"))
.map(|v| v.as_str())
.unwrap_or_default();
if mode == "skip" {
Some(potato::HttpResponse::text("pre_skip"))
} else {
None
}
}
#[potato::preprocess]
async fn pre_async_option(_req: &mut potato::HttpRequest) -> Option<potato::HttpResponse> {
PRE_ASYNC_OPTION_COUNT.fetch_add(1, AtomicOrdering::Relaxed);
HOOK_ORDER_LOG.lock().unwrap().push("pre_async_option");
None
}
#[potato::postprocess]
fn post_sync_unit(_req: &mut potato::HttpRequest, res: &mut potato::HttpResponse) {
POST_SYNC_UNIT_COUNT.fetch_add(1, AtomicOrdering::Relaxed);
HOOK_ORDER_LOG.lock().unwrap().push("post_sync_unit");
append_body(res, "|post_sync");
}
#[potato::postprocess]
async fn post_async_result_unit(
_req: &mut potato::HttpRequest,
res: &mut potato::HttpResponse,
) -> anyhow::Result<()> {
POST_ASYNC_RESULT_UNIT_COUNT.fetch_add(1, AtomicOrdering::Relaxed);
HOOK_ORDER_LOG
.lock()
.unwrap()
.push("post_async_result_unit");
append_body(res, "|post_async");
Ok(())
}
#[potato::http_get("/pipeline_hooks")]
#[potato::preprocess(pre_async_result_unit, pre_async_result_option)]
#[potato::preprocess(pre_sync_unit)]
#[potato::preprocess(pre_sync_option, pre_async_option)]
#[potato::postprocess(post_sync_unit)]
#[potato::postprocess(post_async_result_unit)]
async fn pipeline_hooks() -> potato::HttpResponse {
HANDLER_COUNT.fetch_add(1, AtomicOrdering::Relaxed);
HOOK_ORDER_LOG.lock().unwrap().push("handler");
potato::HttpResponse::text("handler")
}
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
let mut server = HttpServer::new(&server_addr);
let server_handle = tokio::spawn(async move {
let _ = server.serve_http().await;
});
sleep(Duration::from_millis(300)).await;
let normal = potato::get(&format!("http://{server_addr}/pipeline_hooks"), vec![]).await?;
assert_eq!(normal.http_code, 200);
assert_eq!(body_to_string(&normal), "handler|post_sync|post_async");
assert_eq!(
HOOK_ORDER_LOG.lock().unwrap().clone(),
vec![
"pre_async_result_unit",
"pre_async_result_option",
"pre_sync_unit",
"pre_sync_option",
"pre_async_option",
"handler",
"post_sync_unit",
"post_async_result_unit",
]
);
HOOK_ORDER_LOG.lock().unwrap().clear();
let short = potato::get(
&format!("http://{}/pipeline_hooks?mode=skip", server_addr),
vec![],
)
.await?;
assert_eq!(short.http_code, 200);
assert_eq!(body_to_string(&short), "pre_skip|post_sync|post_async");
assert_eq!(
HOOK_ORDER_LOG.lock().unwrap().clone(),
vec![
"pre_async_result_unit",
"pre_async_result_option",
"pre_sync_unit",
"pre_sync_option",
"post_sync_unit",
"post_async_result_unit",
]
);
HOOK_ORDER_LOG.lock().unwrap().clear();
let short2 = potato::get(
&format!("http://{}/pipeline_hooks?mode=skip2", server_addr),
vec![],
)
.await?;
assert_eq!(short2.http_code, 200);
assert_eq!(body_to_string(&short2), "pre_skip2|post_sync|post_async");
assert_eq!(
HOOK_ORDER_LOG.lock().unwrap().clone(),
vec![
"pre_async_result_unit",
"pre_async_result_option",
"post_sync_unit",
"post_async_result_unit",
]
);
assert_eq!(HANDLER_COUNT.load(AtomicOrdering::Relaxed), 1);
assert_eq!(PRE_ASYNC_RESULT_UNIT_COUNT.load(AtomicOrdering::Relaxed), 3);
assert_eq!(
PRE_ASYNC_RESULT_OPTION_COUNT.load(AtomicOrdering::Relaxed),
3
);
assert_eq!(PRE_SYNC_UNIT_COUNT.load(AtomicOrdering::Relaxed), 2);
assert_eq!(PRE_SYNC_OPTION_COUNT.load(AtomicOrdering::Relaxed), 2);
assert_eq!(PRE_ASYNC_OPTION_COUNT.load(AtomicOrdering::Relaxed), 1);
assert_eq!(POST_SYNC_UNIT_COUNT.load(AtomicOrdering::Relaxed), 3);
assert_eq!(
POST_ASYNC_RESULT_UNIT_COUNT.load(AtomicOrdering::Relaxed),
3
);
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_http_methods_server() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
let mut server = HttpServer::new(&server_addr);
#[potato::http_get("/get")]
async fn get() -> HttpResponse {
HttpResponse::html("get method")
}
#[potato::http_post("/post")]
async fn post() -> HttpResponse {
HttpResponse::html("post method")
}
#[potato::http_put("/put")]
async fn put() -> HttpResponse {
HttpResponse::html("put method")
}
#[potato::http_options("/options")]
async fn options() -> HttpResponse {
HttpResponse::html("options")
}
#[potato::http_head("/head")]
async fn head() -> HttpResponse {
HttpResponse::html("head")
}
#[potato::http_delete("/delete")]
async fn delete() -> HttpResponse {
HttpResponse::html("delete")
}
let server_handle = tokio::spawn(async move {
let _ = server.serve_http().await;
});
sleep(Duration::from_millis(300)).await;
let url = format!("http://{}/get", server_addr);
match potato::get(&url, vec![]).await {
Ok(res) => {
println!("GET: {}", res.http_code);
assert_eq!(res.http_code, 200);
}
Err(e) => println!("GET error: {e}"),
}
let url = format!("http://{}/post", server_addr);
match potato::post(&url, vec![], vec![]).await {
Ok(res) => {
println!("POST: {}", res.http_code);
assert_eq!(res.http_code, 200);
}
Err(e) => println!("POST error: {e}"),
}
let url = format!("http://{}/put", server_addr);
match potato::put(&url, vec![], vec![]).await {
Ok(res) => {
println!("PUT: {}", res.http_code);
assert_eq!(res.http_code, 200);
}
Err(e) => println!("PUT error: {e}"),
}
let url = format!("http://{}/delete", server_addr);
match potato::delete(&url, vec![]).await {
Ok(res) => {
println!("DELETE: {}", res.http_code);
assert_eq!(res.http_code, 200);
}
Err(e) => println!("DELETE error: {e}"),
}
let url = format!("http://{}/head", server_addr);
match potato::head(&url, vec![]).await {
Ok(res) => {
println!("HEAD: {}", res.http_code);
assert_eq!(res.http_code, 200);
}
Err(e) => println!("HEAD error: {e}"),
}
let url = format!("http://{}/options", server_addr);
match potato::options(&url, vec![]).await {
Ok(res) => {
println!("OPTIONS: {}", res.http_code);
}
Err(e) => println!("OPTIONS error: {e}"),
}
server_handle.abort();
println!("✅ HTTP methods test completed");
Ok(())
}
#[tokio::test]
async fn test_shutdown_server() -> anyhow::Result<()> {
use std::sync::LazyLock;
use tokio::sync::{oneshot, Mutex};
static SHUTDOWN_SIGNAL: LazyLock<Mutex<Option<oneshot::Sender<()>>>> =
LazyLock::new(|| Mutex::new(None));
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
let mut server = HttpServer::new(&server_addr);
#[potato::http_get("/shutdown")]
async fn shutdown() -> HttpResponse {
if let Some(signal) = SHUTDOWN_SIGNAL.lock().await.take() {
let _ = signal.send(());
}
HttpResponse::html("shutdown!")
}
if let Some(tx) = server.shutdown_signal() {
*SHUTDOWN_SIGNAL.lock().await = Some(tx);
}
let server_handle = tokio::spawn(async move {
let _ = server.serve_http().await;
});
sleep(Duration::from_millis(300)).await;
let url = format!("http://{}/shutdown", server_addr);
match potato::get(&url, vec![]).await {
Ok(res) => {
println!("Shutdown endpoint: {}", res.http_code);
}
Err(e) => {
println!("Shutdown triggered (expected): {e}");
}
}
let _ = tokio::time::timeout(Duration::from_secs(2), server_handle).await;
println!("✅ Shutdown server test completed");
Ok(())
}
#[tokio::test]
async fn test_client_basic() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
let mut server = HttpServer::new(&server_addr);
server.configure(|ctx| {
ctx.use_handlers();
});
let server_handle = tokio::spawn(async move {
let _ = server.serve_http().await;
});
sleep(Duration::from_millis(300)).await;
let url = format!("http://{}/test", server_addr);
match potato::get(&url, vec![]).await {
Ok(res) => {
println!("Client GET response: {}", res.http_code);
assert!(res.http_code > 0);
}
Err(e) => {
println!("Client GET error: {e}");
}
}
server_handle.abort();
println!("✅ Client basic test completed");
Ok(())
}
#[tokio::test]
async fn test_client_with_args() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
let mut server = HttpServer::new(&server_addr);
server.configure(|ctx| {
ctx.use_handlers();
});
let server_handle = tokio::spawn(async move {
let _ = server.serve_http().await;
});
sleep(Duration::from_millis(300)).await;
let url = format!("http://{}/test", server_addr);
let headers = vec![Headers::User_Agent("test-client/1.0".into())];
match potato::get(&url, headers).await {
Ok(res) => {
println!("Client with args response: {}", res.http_code);
assert!(res.http_code > 0);
}
Err(e) => {
println!("Client with args error: {e}");
}
}
server_handle.abort();
println!("✅ Client with args test completed");
Ok(())
}
#[tokio::test]
async fn test_client_session() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
let mut server = HttpServer::new(&server_addr);
server.configure(|ctx| {
ctx.use_handlers();
});
let server_handle = tokio::spawn(async move {
let _ = server.serve_http().await;
});
sleep(Duration::from_millis(300)).await;
let mut session = Session::new();
let url = format!("http://{}/path1", server_addr);
match session.get(&url, vec![]).await {
Ok(res) => {
println!("Session request 1: {}", res.http_code);
}
Err(e) => {
println!("Session request 1 error: {e}");
}
}
let url = format!("http://{}/path2", server_addr);
match session.get(&url, vec![]).await {
Ok(res) => {
println!("Session request 2: {}", res.http_code);
}
Err(e) => {
println!("Session request 2 error: {e}");
}
}
server_handle.abort();
println!("✅ Client session test completed");
Ok(())
}
#[cfg(feature = "openapi")]
#[tokio::test]
async fn test_openapi_server() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
let mut server = HttpServer::new(&server_addr);
server.configure(|ctx| {
ctx.use_handlers();
ctx.use_openapi("/doc/");
});
let server_handle = tokio::spawn(async move {
let _ = server.serve_http().await;
});
sleep(Duration::from_millis(300)).await;
let url = format!("http://{}/doc/", server_addr);
match potato::get(&url, vec![]).await {
Ok(res) => {
println!("OpenAPI response status: {}", res.http_code);
assert!(res.http_code == 200 || res.http_code == 404);
}
Err(e) => {
println!("OpenAPI request error: {e}");
}
}
let url = format!("http://{}/doc/index.html", server_addr);
match potato::get(&url, vec![]).await {
Ok(res) => {
println!("Swagger index response status: {}", res.http_code);
}
Err(e) => {
println!("Swagger index error: {e}");
}
}
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_location_route_server() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
let mut server = HttpServer::new(&server_addr);
server.configure(|ctx| {
ctx.use_location_route("/", ".", false);
});
let shutdown_signal = server
.shutdown_signal()
.expect("Failed to get shutdown signal");
let server_handle = tokio::spawn(async move {
let _ = server.serve_http().await;
});
sleep(Duration::from_millis(300)).await;
let url = format!("http://{}/Cargo.toml", server_addr);
match potato::get(&url, vec![]).await {
Ok(res) => {
println!("Static file response status: {}", res.http_code);
assert!(res.http_code == 200 || res.http_code == 404 || res.http_code == 500);
}
Err(e) => {
println!("Static file request error: {e}");
}
}
_ = shutdown_signal.send(());
let _ = tokio::time::timeout(Duration::from_secs(3), server_handle).await;
Ok(())
}
#[tokio::test]
async fn test_embed_route_server() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
let mut server = HttpServer::new(&server_addr);
server.configure(|ctx| {
ctx.use_handlers();
});
let server_handle = tokio::spawn(async move {
let _ = server.serve_http().await;
});
sleep(Duration::from_millis(300)).await;
let url = format!("http://{server_addr}/");
match potato::get(&url, vec![]).await {
Ok(res) => {
println!("Embedded route test response status: {}", res.http_code);
}
Err(e) => {
println!("Embedded route test error: {e}");
}
}
println!("✅ Embedded route API available (embed_dir! requires proc_macro)");
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_jwt_auth_server() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
potato::ServerConfig::set_jwt_secret("test_secret_key_12345").await;
let mut server = HttpServer::new(&server_addr);
server.configure(|ctx| {
ctx.use_handlers();
});
let server_handle = tokio::spawn(async move {
let _ = server.serve_http().await;
});
sleep(Duration::from_millis(300)).await;
let payload = "test_user_data";
let token =
potato::ServerAuth::jwt_issue(payload.to_string(), Duration::from_secs(3600)).await?;
println!("✅ JWT token issued: {}", &token[..token.len().min(50)]);
let result = potato::ServerAuth::jwt_check(&token).await;
match result {
Ok(verified_payload) => {
println!("✅ JWT token verified, payload: {}", verified_payload);
assert_eq!(verified_payload, payload);
}
Err(e) => {
println!("JWT verification error: {e}");
}
}
let invalid_result = potato::ServerAuth::jwt_check("invalid_token").await;
assert!(invalid_result.is_err());
println!("✅ Invalid JWT token rejected");
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_websocket_server_and_client() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
let mut server = HttpServer::new(&server_addr);
server.configure(|ctx| {
ctx.use_handlers();
});
#[potato::http_get("/ws")]
async fn ws_handler(req: &mut HttpRequest) -> anyhow::Result<()> {
let mut ws = req.upgrade_websocket().await?;
ws.send_ping().await?;
loop {
match ws.recv().await? {
WsFrame::Text(text) => ws.send_text(&text).await?,
WsFrame::Binary(bin) => ws.send_binary(bin).await?,
}
}
}
let server_handle = tokio::spawn(async move {
let _ = server.serve_http().await;
});
sleep(Duration::from_millis(300)).await;
let ws_url = format!("ws://{}/ws", server_addr);
match Websocket::connect(&ws_url, vec![]).await {
Ok(mut ws) => {
ws.send_ping().await?;
println!("✅ WebSocket ping sent");
ws.send_text("hello world").await?;
println!("✅ WebSocket text message sent");
match ws.recv().await {
Ok(WsFrame::Text(text)) => {
println!("✅ WebSocket received text: {}", text);
assert_eq!(text, "hello world");
}
_ => {}
}
let test_data = vec![1, 2, 3, 4, 5];
ws.send_binary(test_data.clone()).await?;
println!("✅ WebSocket binary sent");
match ws.recv().await {
Ok(WsFrame::Binary(data)) => {
println!("✅ WebSocket received binary: {:?}", data);
assert_eq!(data, test_data);
}
_ => {}
}
}
Err(e) => {
println!("WebSocket connection failed: {e}");
}
}
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_custom_middleware() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
let mut server = HttpServer::new(&server_addr);
use std::sync::Arc;
use tokio::sync::Mutex;
let middleware_called = Arc::new(Mutex::new(false));
let middleware_called_clone = middleware_called.clone();
server.configure(move |ctx| {
let called = middleware_called_clone.clone();
ctx.use_custom_sync(move |req| {
if req.url_path == "/sync" {
return Some(HttpResponse::text("custom middleware sync"));
}
None
});
ctx.use_custom(move |_req| {
let called = called.clone();
Box::pin(async move {
let mut flag = called.lock().await;
*flag = true;
Some(HttpResponse::text("custom middleware async"))
})
});
ctx.use_handlers();
});
let server_handle = tokio::spawn(async move {
let _ = server.serve_http().await;
});
sleep(Duration::from_millis(300)).await;
let sync_url = format!("http://{}/sync", server_addr);
match potato::get(&sync_url, vec![]).await {
Ok(res) => {
println!("Custom sync middleware response: {}", res.http_code);
let body = match &res.body {
potato::HttpResponseBody::Data(data) => {
String::from_utf8(data.clone()).unwrap_or_default()
}
potato::HttpResponseBody::Stream(_) => "stream response".to_string(),
};
println!("Response body: {}", body);
assert!(res.http_code == 200);
assert!(body.contains("custom middleware sync"));
}
Err(e) => {
println!("Custom sync middleware request error: {e}");
}
}
let url = format!("http://{}/any_path", server_addr);
match potato::get(&url, vec![]).await {
Ok(res) => {
println!("Custom async middleware response: {}", res.http_code);
let body = match &res.body {
potato::HttpResponseBody::Data(data) => {
String::from_utf8(data.clone()).unwrap_or_default()
}
potato::HttpResponseBody::Stream(_) => "stream response".to_string(),
};
println!("Response body: {}", body);
assert!(res.http_code == 200);
assert!(body.contains("custom middleware async"));
}
Err(e) => {
println!("Custom async middleware request error: {e}");
}
}
let called = middleware_called.lock().await;
assert!(*called, "Middleware should have been called");
println!("✅ Custom middleware was called");
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_response_types() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
let mut server = HttpServer::new(&server_addr);
server.configure(|ctx| {
ctx.use_handlers();
});
let server_handle = tokio::spawn(async move {
let _ = server.serve_http().await;
});
sleep(Duration::from_millis(300)).await;
let base_url = format!("http://{}", server_addr);
let paths = vec!["/", "/test", "/another/path"];
for path in paths {
let url = format!("{}{}", base_url, path);
match potato::get(&url, vec![]).await {
Ok(res) => {
println!("Path {}: status {}", path, res.http_code);
assert!(res.http_code > 0);
}
Err(e) => {
println!("Path {} error: {}", path, e);
}
}
}
let url = format!("{}?key1=value1&key2=value2", base_url);
match potato::get(&url, vec![]).await {
Ok(res) => {
println!("Query string test: status {}", res.http_code);
}
Err(e) => {
println!("Query string error: {e}");
}
}
let headers = vec![Headers::Custom((
"X-Test-Header".to_string(),
"test-value".to_string(),
))];
let url = format!("{base_url}");
match potato::get(&url, headers).await {
Ok(res) => {
println!("Custom headers test: status {}", res.http_code);
}
Err(e) => {
println!("Custom headers error: {e}");
}
}
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_concurrent_requests() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
let mut server = HttpServer::new(&server_addr);
server.configure(|ctx| {
ctx.use_handlers();
});
let server_handle = tokio::spawn(async move {
let _ = server.serve_http().await;
});
sleep(Duration::from_millis(300)).await;
let base_url = format!("http://{}", server_addr);
let handles: Vec<_> = (0..10)
.map(|i| {
let url = format!("{}/path{}", base_url, i);
tokio::spawn(async move { potato::get(&url, vec![]).await })
})
.collect();
for (i, handle) in handles.into_iter().enumerate() {
match handle.await {
Ok(Ok(res)) => {
println!("Request {}: status {}", i, res.http_code);
}
Ok(Err(e)) => {
println!("Request {} error: {}", i, e);
}
Err(e) => {
println!("Join error: {e}");
}
}
}
println!("✅ Concurrent requests test completed");
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_large_body_handling() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
let mut server = HttpServer::new(&server_addr);
server.configure(|ctx| {
ctx.use_handlers();
});
let server_handle = tokio::spawn(async move {
let _ = server.serve_http().await;
});
sleep(Duration::from_millis(300)).await;
let url = format!("http://{}/upload", server_addr);
let small_body = b"hello".to_vec();
match potato::post(&url, small_body, vec![]).await {
Ok(res) => {
println!("Small body response: {}", res.http_code);
}
Err(e) => {
println!("Small body error: {e}");
}
}
let medium_body = vec![0u8; 1024];
match potato::post(&url, medium_body, vec![]).await {
Ok(res) => {
println!("Medium body response: {}", res.http_code);
}
Err(e) => {
println!("Medium body error: {e}");
}
}
let large_body = vec![0u8; 100 * 1024];
match potato::post(&url, large_body, vec![]).await {
Ok(res) => {
println!("Large body response: {}", res.http_code);
}
Err(e) => {
println!("Large body error: {e}");
}
}
println!("✅ Large body handling test completed");
server_handle.abort();
Ok(())
}
}