#![cfg(all(feature = "unchecked", feature = "macros", feature = "json"))]
use kynos::{
Router,
http::{Method, Request, Response, StatusCode, body::Body},
middleware::request_id::RequestId,
openapi::OpaqueRoute,
};
#[path = "support/mod.rs"]
mod support;
use support::get;
async fn echo_path(request: Request) -> Response {
let mut response = Response::new(Body::from_bytes(bytes::Bytes::from(
request.uri().path().to_owned(),
)));
*response.status_mut() = StatusCode::OK;
response
}
fn with_catch_all() -> Router<()> {
Router::<()>::new().route_unchecked([Method::GET], "/static/{*path}", echo_path)
}
#[tokio::test]
async fn a_catch_all_serves_every_path_beneath_its_prefix() {
let service = with_catch_all().build(()).expect("a buildable router");
for path in [
"/static/app.css",
"/static/css/app.css",
"/static/a/b/c.png",
] {
let reply = get(&service, path).call().await;
assert_eq!(reply.status, StatusCode::OK, "{path}");
assert_eq!(reply.text(), path);
}
}
#[tokio::test]
async fn a_catch_all_does_not_answer_outside_its_prefix() {
let service = with_catch_all().build(()).expect("a buildable router");
assert_eq!(
get(&service, "/elsewhere").call().await.status,
StatusCode::NOT_FOUND
);
}
#[tokio::test]
async fn a_catch_all_matches_neither_its_own_prefix_nor_a_bare_trailing_slash() {
let service = with_catch_all().build(()).expect("a buildable router");
for path in ["/static", "/static/"] {
assert_eq!(
get(&service, path).call().await.status,
StatusCode::NOT_FOUND,
"{path}"
);
}
}
#[tokio::test]
async fn a_catch_all_hands_its_handler_what_it_captured() {
async fn echo_capture(request: Request) -> Response {
let captured = kynos::unchecked::captured(&request, "path")
.map_or_else(|| "absent".to_owned(), std::borrow::Cow::into_owned);
Response::new(Body::from_bytes(bytes::Bytes::from(captured)))
}
let service = Router::<()>::new()
.route_unchecked([Method::GET], "/static/{*path}", echo_capture)
.build(())
.expect("a buildable router");
assert_eq!(
get(&service, "/static/css/app.css").call().await.text(),
"css/app.css"
);
}
#[tokio::test]
async fn a_capture_is_decoded_the_way_a_described_one_is() {
async fn echo_capture(request: Request) -> Response {
let captured = kynos::unchecked::captured(&request, "path")
.map_or_else(|| "absent".to_owned(), std::borrow::Cow::into_owned);
Response::new(Body::from_bytes(bytes::Bytes::from(captured)))
}
let service = Router::<()>::new()
.route_unchecked([Method::GET], "/static/{*path}", echo_capture)
.build(())
.expect("a buildable router");
assert_eq!(
get(&service, "/static/a%20b/c%2Ed").call().await.text(),
"a b/c.d"
);
}
#[tokio::test]
async fn a_router_interceptor_covers_an_unchecked_route() {
let service = with_catch_all()
.intercept(RequestId::new())
.build(())
.expect("a buildable router");
let reply = get(&service, "/static/app.css").call().await;
assert_eq!(reply.status, StatusCode::OK);
assert!(
reply.field("x-request-id").is_some(),
"the router's interceptor did not cover the unchecked route"
);
}
#[test]
fn an_unchecked_route_is_recorded_where_a_generator_cannot_act_on_it() {
let document = with_catch_all().openapi().expect("a describable router");
assert!(
document.paths.items.is_empty(),
"a catch-all took a `paths` key, which is a claim about a path it does not honour"
);
let recorded = OpaqueRoute::all(&document).expect("a readable record");
assert_eq!(recorded.len(), 1);
assert_eq!(recorded[0].pattern, "/static/{*path}");
assert_eq!(recorded[0].prefix.as_deref(), Some("/static"));
assert_eq!(recorded[0].methods, ["GET"]);
assert!(!document.is_authoritative());
}
#[test]
fn a_router_that_waives_nothing_reports_nothing() {
let router = Router::<()>::new();
assert!(!router.has_unchecked());
assert!(router.unchecked_reasons().is_empty());
}
#[test]
fn a_gate_can_tolerate_one_waiver_and_still_catch_the_rest() {
let assets = with_catch_all();
assert!(assets.has_unchecked());
assert_eq!(
assets.unchecked_reasons(),
[kynos::openapi::OpaqueReason::UntypedRoute]
);
let both = with_catch_all().upgrade_unchecked("/ws", echo_path);
assert_eq!(
both.unchecked_reasons(),
[
kynos::openapi::OpaqueReason::UntypedRoute,
kynos::openapi::OpaqueReason::ProtocolUpgrade,
]
);
}
#[test]
fn one_reason_is_reported_once_however_many_routes_took_it() {
let router = Router::<()>::new()
.route_unchecked([Method::GET], "/static/{*path}", echo_path)
.route_unchecked([Method::GET], "/media/{*path}", echo_path);
assert_eq!(
router.unchecked_reasons(),
[kynos::openapi::OpaqueReason::UntypedRoute]
);
}