///|
/// Request-contract conformance suite.
///
/// These tests exercise the shared `dispatch_http` entry point, which every
/// backend (native, C/mongoose, JS) now funnels through, so the guarantees
/// below hold identically on the `+js` and `+native` targets (the workspace
/// runs the tests on both).
async test "query string does not break path routing" {
let app = new()
app.get("/hello", _ => "hello")
let response = dispatch_http(app, "GET", "/hello?x=1", {}, b"")
@test.assert_eq(response.status_code.to_int(), 200)
let body : String = response.read_body()
@test.assert_eq(body, "hello")
}
///|
async test "query string is exposed separately from the route path" {
let app = new()
let captured : Array[String] = []
app.get("/hello/:name", event => {
captured.push(event.req.url)
captured.push(event.req.query)
"ok"
})
ignore(dispatch_http(app, "GET", "/hello/moonbit?x=1&y=2", {}, b""))
@test.assert_eq(captured, ["/hello/moonbit", "x=1&y=2"])
}
///|
async test "fragment is stripped from the query string" {
let app = new()
let captured : Array[String] = []
app.get("/hello", event => {
captured.push(event.req.query)
"ok"
})
ignore(dispatch_http(app, "GET", "/hello?a=1#frag", {}, b""))
@test.assert_eq(captured, ["a=1"])
}
///|
async test "route with no query string yields an empty query" {
let app = new()
let captured : Array[String] = []
app.get("/plain", event => {
captured.push(event.req.query)
"ok"
})
ignore(dispatch_http(app, "GET", "/plain", {}, b""))
@test.assert_eq(captured, [""])
}
///|
async test "query parameters are URL-decoded and exposed via HttpRequest::query" {
let app = new()
let captured : Array[String] = []
app.get("/search", event => {
let q = event.req.query()
captured.push(q.get("q").unwrap_or(""))
captured.push(q.get("page").unwrap_or(""))
captured.push(q.get("tag").unwrap_or(""))
"ok"
})
ignore(
dispatch_http(app, "GET", "/search?q=moon&page=2&tag=hello+world", {}, b""),
)
@test.assert_eq(captured, ["moon", "2", "hello world"])
}
///|
async test "PUT and PATCH bodies are delivered to the handler" {
let app = new()
let captured : Array[String] = []
app.put("/raw1", event => {
let text : String = event.req.body()
captured.push(text)
"accepted"
})
app.patch("/raw2", event => {
let text : String = event.req.body()
captured.push(text)
"accepted"
})
ignore(dispatch_http(app, "PUT", "/raw1", {}, b"payload-put"))
ignore(dispatch_http(app, "PATCH", "/raw2", {}, b"payload-patch"))
@test.assert_eq(captured, ["payload-put", "payload-patch"])
}
///|
async test "request headers are case-insensitive" {
let app = new()
let captured : Array[StringView?] = []
app.get("/hdr", event => {
captured.push(event.req.headers.get("X-Test"))
captured.push(event.req.headers.get("x-test"))
captured.push(event.req.headers.get("X-TEST"))
"ok"
})
let headers : Map[@http.CaseInsensitiveString, StringView] = Map([])
headers["X-Test"] = "1"
ignore(dispatch_http(app, "GET", "/hdr", headers, b""))
@test.assert_eq(captured, [Some("1"), Some("1"), Some("1")])
}