use hyper::StatusCode;
use mini_serve::{RouteBuilder, handler};
#[tokio::test]
async fn method_dispatch_returns_405_with_allow_header() {
let app = RouteBuilder::stateless()
.get("/api/items", handler(|_req, _state| async {
mini_serve::json(StatusCode::OK, &serde_json::json!({"items": []}))
}))
.seal();
let port = app.bind_ephemeral().await.expect("failed to bind");
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
let client = reqwest::Client::new();
let resp = client
.post(&format!("http://127.0.0.1:{}/api/items", port))
.send()
.await
.expect("request failed");
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let allow_header = resp
.headers()
.get("allow")
.and_then(|h| h.to_str().ok())
.expect("missing Allow header");
assert!(allow_header.contains("GET"), "Allow header should contain GET: {}", allow_header);
assert!(allow_header.contains("HEAD"), "Allow header should contain HEAD: {}", allow_header);
}
#[tokio::test]
async fn multiple_methods_on_same_path() {
let app = RouteBuilder::stateless()
.get("/resource", handler(|_req, _state| async {
mini_serve::json(StatusCode::OK, &serde_json::json!({"method": "GET"}))
}))
.post("/resource", handler(|_req, _state| async {
mini_serve::json(StatusCode::CREATED, &serde_json::json!({"method": "POST"}))
}))
.put("/resource", handler(|_req, _state| async {
mini_serve::json(StatusCode::OK, &serde_json::json!({"method": "PUT"}))
}))
.seal();
let port = app.bind_ephemeral().await.expect("failed to bind");
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
let client = reqwest::Client::new();
let get_resp = client
.get(&format!("http://127.0.0.1:{}/resource", port))
.send()
.await
.expect("GET failed");
assert_eq!(get_resp.status(), StatusCode::OK);
let post_resp = client
.post(&format!("http://127.0.0.1:{}/resource", port))
.send()
.await
.expect("POST failed");
assert_eq!(post_resp.status(), StatusCode::CREATED);
let put_resp = client
.put(&format!("http://127.0.0.1:{}/resource", port))
.send()
.await
.expect("PUT failed");
assert_eq!(put_resp.status(), StatusCode::OK);
let delete_resp = client
.delete(&format!("http://127.0.0.1:{}/resource", port))
.send()
.await
.expect("DELETE failed");
assert_eq!(delete_resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let allow_header = delete_resp
.headers()
.get("allow")
.and_then(|h| h.to_str().ok())
.expect("missing Allow header");
let methods: Vec<&str> = allow_header.split(", ").collect();
assert!(methods.contains(&"GET"), "Allow should contain GET");
assert!(methods.contains(&"HEAD"), "Allow should contain HEAD");
assert!(methods.contains(&"POST"), "Allow should contain POST");
assert!(methods.contains(&"PUT"), "Allow should contain PUT");
assert!(!methods.contains(&"DELETE"), "Allow should not contain DELETE");
}
#[tokio::test]
async fn unregistered_path_returns_404_without_allow() {
let app = RouteBuilder::stateless()
.get("/exists", handler(|_req, _state| async {
mini_serve::json(StatusCode::OK, &serde_json::json!({}))
}))
.seal();
let port = app.bind_ephemeral().await.expect("failed to bind");
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
let resp = reqwest::get(&format!("http://127.0.0.1:{}/does-not-exist", port))
.await
.expect("request failed");
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
assert!(resp.headers().get("allow").is_none(), "404 should not have Allow header");
}