use std::sync::atomic::{AtomicU16, Ordering};
use std::time::Duration;
use tokio::time::sleep;
static PORT_COUNTER: AtomicU16 = AtomicU16::new(31000);
fn get_test_port() -> u16 {
PORT_COUNTER.fetch_add(1, Ordering::Relaxed)
}
#[cfg(test)]
mod tests {
use super::*;
use potato::{HttpRequest, HttpResponse, HttpServer};
#[tokio::test]
async fn test_http_get_macro() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
#[potato::http_get("/test_get")]
async fn test_get_handler() -> HttpResponse {
HttpResponse::text("GET response")
}
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 url = format!("http://{}/test_get", server_addr);
let res = potato::get(&url, vec![]).await?;
assert_eq!(res.http_code, 200);
println!("✅ GET macro works");
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_http_post_macro() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
#[potato::http_post("/test_post")]
async fn test_post_handler() -> HttpResponse {
HttpResponse::text("POST response")
}
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 url = format!("http://{}/test_post", server_addr);
let res = potato::post(&url, vec![], vec![]).await?;
assert_eq!(res.http_code, 200);
println!("✅ POST macro works");
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_http_put_macro() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
#[potato::http_put("/test_put")]
async fn test_put_handler() -> HttpResponse {
HttpResponse::text("PUT response")
}
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 url = format!("http://{}/test_put", server_addr);
let res = potato::put(&url, vec![], vec![]).await?;
assert_eq!(res.http_code, 200);
println!("✅ PUT macro works");
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_http_delete_macro() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
#[potato::http_delete("/test_delete")]
async fn test_delete_handler() -> HttpResponse {
HttpResponse::text("DELETE response")
}
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 url = format!("http://{}/test_delete", server_addr);
let res = potato::delete(&url, vec![]).await?;
assert_eq!(res.http_code, 200);
println!("✅ DELETE macro works");
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_header_macro_standard() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
#[potato::http_get("/header_standard")]
#[potato::header(Cache_Control = "no-store, no-cache")]
#[potato::header(Content_Type = "application/json")]
async fn test_header_standard() -> HttpResponse {
HttpResponse::text("with headers")
}
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 url = format!("http://{}/header_standard", server_addr);
let res = potato::get(&url, vec![]).await?;
assert_eq!(res.http_code, 200);
assert!(res.headers.get("Cache-Control").is_some());
println!("✅ Standard header macro works");
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_header_macro_custom() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
#[potato::http_get("/header_custom")]
#[potato::header(Custom("X-Custom-Header") = "custom-value")]
async fn test_header_custom() -> HttpResponse {
HttpResponse::text("custom headers")
}
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 url = format!("http://{}/header_custom", server_addr);
let res = potato::get(&url, vec![]).await?;
assert_eq!(res.http_code, 200);
assert!(res.headers.get("X-Custom-Header").is_some());
println!("✅ Custom header macro works");
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_cors_macro_default() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
#[potato::http_get("/cors_default")]
#[potato::cors]
async fn test_cors_default() -> HttpResponse {
HttpResponse::text("cors default")
}
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 url = format!("http://{}/cors_default", server_addr);
let res = potato::get(&url, vec![]).await?;
assert_eq!(res.http_code, 200);
assert!(res.headers.get("Access-Control-Allow-Origin").is_some());
println!("✅ Default CORS macro works");
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_cors_macro_custom() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
#[potato::http_post("/cors_custom")]
#[potato::cors(origin = "https://example.com", credentials = true)]
async fn test_cors_custom() -> HttpResponse {
HttpResponse::text("cors custom")
}
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 url = format!("http://{}/cors_custom", server_addr);
let res = potato::post(&url, vec![], vec![]).await?;
assert_eq!(res.http_code, 200);
assert!(res.headers.get("Access-Control-Allow-Origin").is_some());
println!("✅ Custom CORS macro works");
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_max_concurrency_macro() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
#[potato::http_get("/max_concurrency")]
#[potato::max_concurrency(5)]
async fn test_max_concurrency() -> HttpResponse {
HttpResponse::text("max concurrency")
}
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 url = format!("http://{}/max_concurrency", server_addr);
let res = potato::get(&url, vec![]).await?;
assert_eq!(res.http_code, 200);
println!("✅ Max concurrency macro works");
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_preprocess_postprocess_macros() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
#[potato::preprocess]
async fn test_preprocess(req: &mut HttpRequest) {
req.set_header("X-Preprocessed", "true");
}
#[potato::postprocess]
async fn test_postprocess(_req: &mut HttpRequest, res: &mut HttpResponse) {
res.add_header("X-Postprocessed".into(), "true".into());
}
#[potato::http_get("/hooks_test")]
#[potato::preprocess(test_preprocess)]
#[potato::postprocess(test_postprocess)]
async fn test_hooks_handler(req: &mut HttpRequest) -> HttpResponse {
let preprocessed = req.get_header("X-Preprocessed").unwrap_or("false");
HttpResponse::text(format!("Hooks test - {}", preprocessed))
}
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 url = format!("http://{}/hooks_test", server_addr);
let res = potato::get(&url, vec![]).await?;
assert_eq!(res.http_code, 200);
assert!(res.headers.get("X-Postprocessed").is_some());
println!("✅ Preprocess/Postprocess macros work");
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_handle_error_macro() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
#[potato::handle_error]
async fn custom_error_handler(req: &mut HttpRequest, err: anyhow::Error) -> HttpResponse {
let path = req.url_path.clone();
HttpResponse::text(format!("Custom error: {} at {}", err, path))
}
#[potato::http_get("/test_error")]
async fn handler_with_error() -> anyhow::Result<HttpResponse> {
anyhow::bail!("Test error");
}
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;
println!("✅ Handle error macro compiles and works");
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_limit_size_macro() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
#[potato::http_post("/limit_size")]
#[potato::limit_size(1024)]
async fn test_limit_size(req: &mut HttpRequest) -> HttpResponse {
let len = req.body.len();
HttpResponse::text(format!("Received {} bytes", len))
}
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 url = format!("http://{}/limit_size", server_addr);
let small_body = vec![0u8; 512];
let res = potato::post(&url, small_body, vec![]).await?;
assert_eq!(res.http_code, 200);
println!("✅ Limit size macro works");
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_different_return_types() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
#[potato::http_get("/return_string")]
async fn return_string() -> String {
"String return".to_string()
}
#[potato::http_get("/return_static_str")]
async fn return_static_str() -> &'static str {
"&'static str return"
}
#[potato::http_get("/return_result")]
async fn return_result() -> anyhow::Result<HttpResponse> {
Ok(HttpResponse::text("Result return"))
}
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 base_url = format!("http://{}", server_addr);
for endpoint in &["/return_string", "/return_static_str", "/return_result"] {
let url = format!("{}{}", base_url, endpoint);
let res = potato::get(&url, vec![]).await?;
assert_eq!(res.http_code, 200, "Failed for endpoint: {}", endpoint);
println!("✅ Return type works: {}", endpoint);
}
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_path_parameters() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
#[potato::http_get("/params/{id}/{name}")]
async fn test_params(id: u32, name: String) -> HttpResponse {
HttpResponse::text(format!("ID: {}, Name: {}", id, name))
}
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;
println!("✅ Path parameter macro compiles and registers correctly");
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_complex_attribute_combination() -> anyhow::Result<()> {
let port = get_test_port();
let server_addr = format!("127.0.0.1:{port}");
#[potato::http_post("/complex")]
#[potato::header(Cache_Control = "no-cache")]
#[potato::header(Custom("X-API-Version") = "2.0")]
#[potato::cors(origin = "https://example.com")]
#[potato::max_concurrency(10)]
#[potato::limit_size(2048)]
async fn test_complex(req: &mut HttpRequest) -> HttpResponse {
let len = req.body.len();
HttpResponse::text(format!("Complex handler - {} bytes", len))
}
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 url = format!("http://{}/complex", server_addr);
let body = vec![0u8; 100];
let res = potato::post(&url, body, vec![]).await?;
assert_eq!(res.http_code, 200);
assert!(
res.headers.get("Cache-Control").is_some()
|| res.headers.get("cache-control").is_some()
);
println!("✅ Complex attribute combination works");
server_handle.abort();
Ok(())
}
}