#![cfg(all(feature = "macros", feature = "json"))]
use std::panic::AssertUnwindSafe;
use kynos::{
Router,
http::StatusCode,
openapi::{Method as OpenApiMethod, PathTemplate},
response::status::NoContent,
router::{endpoint::builder::EndpointBuilder, group::Group, service::Service},
};
#[path = "support/mod.rs"]
mod support;
use support::get;
#[kynos::get("/boom")]
async fn boom() -> NoContent {
panic!("the handler failed");
}
#[kynos::get("/boom")]
async fn also_boom() -> NoContent {
panic!("the handler failed");
}
#[kynos::get("/fine")]
async fn fine() -> NoContent {
NoContent
}
fn outcome(service: &Service<()>, path: &'static str) -> Result<StatusCode, ()> {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("a runtime");
let hook = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let caught = std::panic::catch_unwind(AssertUnwindSafe(|| {
runtime.block_on(async { get(service, path).call().await.status })
}));
std::panic::set_hook(hook);
caught.map_err(|_| ())
}
#[test]
fn a_router_that_asked_for_recovery_answers_a_panic_with_a_500() {
let service = Router::<()>::new()
.catch_panics()
.mount(kynos::routes![boom, fine])
.build(())
.expect("a describable router");
assert_eq!(
outcome(&service, "/boom"),
Ok(StatusCode::INTERNAL_SERVER_ERROR)
);
assert_eq!(outcome(&service, "/fine"), Ok(StatusCode::NO_CONTENT));
}
#[test]
fn a_router_that_did_not_ask_for_recovery_lets_a_panic_through() {
let service = Router::<()>::new()
.mount(kynos::routes![boom, fine])
.build(())
.expect("a describable router");
assert_eq!(
outcome(&service, "/boom"),
Err(()),
"a panic was recovered by something that was never asked to catch one"
);
assert_eq!(outcome(&service, "/fine"), Ok(StatusCode::NO_CONTENT));
}
#[test]
fn recovery_asked_for_on_a_group_covers_that_group_alone() {
let service = Router::<()>::new()
.group(
Group::<()>::new("/guarded")
.catch_panics()
.mount(kynos::routes![boom]),
)
.group(Group::<()>::new("/bare").mount(kynos::routes![also_boom]))
.build(())
.expect("a describable router");
assert_eq!(
outcome(&service, "/guarded/boom"),
Ok(StatusCode::INTERNAL_SERVER_ERROR)
);
assert_eq!(outcome(&service, "/bare/boom"), Err(()));
}
async fn guarded_endpoint() -> NoContent {
panic!("the handler failed");
}
async fn bare_endpoint() -> NoContent {
panic!("the handler failed");
}
#[test]
fn recovery_asked_for_on_one_endpoint_covers_that_endpoint_alone() {
let at = |path: &str| PathTemplate::parse(path).expect("a usable path template");
let service = Router::<()>::new()
.mount((
EndpointBuilder::<(), _, _>::new(OpenApiMethod::Get, at("/guarded"), guarded_endpoint)
.catch_panics(),
EndpointBuilder::<(), _, _>::new(OpenApiMethod::Get, at("/bare"), bare_endpoint),
))
.build(())
.expect("a describable router");
assert_eq!(
outcome(&service, "/guarded"),
Ok(StatusCode::INTERNAL_SERVER_ERROR)
);
assert_eq!(outcome(&service, "/bare"), Err(()));
}
#[test]
fn a_recovered_operation_declares_the_status_recovery_produces() {
let guarded = Router::<()>::new()
.catch_panics()
.mount(kynos::routes![fine])
.openapi()
.expect("a describable router");
let bare = Router::<()>::new()
.mount(kynos::routes![fine])
.openapi()
.expect("a describable router");
let declares_500 = |document: &kynos::openapi::Document| {
document.paths.items["/fine"]
.get
.as_ref()
.expect("a GET operation")
.responses
.responses
.contains_key("500")
};
assert!(declares_500(&guarded));
assert!(
!declares_500(&bare),
"an operation with no recovery declared a status only recovery produces"
);
}