// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// An async application tested in-process with `testing`, no socket:
///
/// moon run src/examples/15-testing --target native
///
/// Lifespan startup stores a greeting, an HTTP request reads it back from its scope, a
/// WebSocket echoes, `communicate` steps through one exchange event by event, and a failing
/// route shows its error reaching the test.
#coverage.skip
async fn main {
tour() catch {
e => println("failed: \{e}")
}
}
///|
#coverage.skip
async fn tour() -> Unit {
let app : @spec.AsgiApp = async fn(scope, receive, send) {
match scope {
Lifespan(s) =>
while true {
match receive() {
LifespanStartup => {
s.state["greeting"] = "hello"
send(LifespanStartupComplete)
}
_ => {
send(LifespanShutdownComplete)
break
}
}
}
Http(h) if h.path == "/fail" => raise Failure::Failure("route /fail")
Http(h) => {
ignore(receive())
let greeting = h.state.get("greeting").unwrap_or("none")
send(HttpResponseStart(status=200, headers=[], trailers=false))
send(
HttpResponseBody(
body=@utf8.encode(greeting.stringify()),
more_body=false,
),
)
}
WebSocket(_) => {
ignore(receive())
send(WebSocketAccept(subprotocol=None, headers=[]))
while receive() is WebSocketReceive(text=Some(t), ..) {
send(WebSocketSendText(t))
}
}
}
}
let client = @testing.Client::new(app)
client.lifespan(() => {
let r = client.get("/")
println("GET /: \{r.status} \{r.text()}")
client.websocket(path="/echo", ws => {
ws.send_text("ping")
println("echo: \{ws.receive_text()}")
})
})
@testing.communicate(
app,
Http(@spec.HttpScope::new(http_method="GET", path="/")),
c => {
c.send(HttpRequest(body=b"", more_body=false))
while true {
let event = c.receive() catch {
@testing.Finished => break
e => raise e
}
match event {
HttpResponseStart(status~, ..) => println("event: start \{status}")
HttpResponseBody(body~, ..) =>
println("event: body \{body.length()} bytes")
_ => ()
}
}
},
)
(client.get("/fail") |> ignore) catch {
e => println("GET /fail raised: \{e}")
}
}