use axum::{
body::Body,
http::{header::CONTENT_TYPE, Request, StatusCode},
};
use tower::ServiceExt;
use super::build_app;
#[tokio::test]
async fn build_app_serves_client() {
let app = build_app(crate::routines::new_store());
let resp = app
.oneshot(
Request::builder()
.uri("/client")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let ctype = resp.headers().get(CONTENT_TYPE).unwrap();
assert!(ctype.to_str().unwrap().starts_with("text/html"));
}
#[tokio::test]
async fn build_app_serves_client_with_etag() {
let app = build_app(crate::routines::new_store());
let resp = app
.oneshot(
Request::builder()
.uri("/client")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let etag = resp
.headers()
.get(axum::http::header::ETAG)
.expect("ETag header present")
.to_str()
.unwrap()
.to_owned();
assert!(etag.starts_with('"') && etag.ends_with('"'));
assert_eq!(
resp.headers()
.get(axum::http::header::CACHE_CONTROL)
.unwrap(),
"no-cache"
);
}
#[tokio::test]
async fn build_app_returns_304_for_client_when_if_none_match_matches() {
let app = build_app(crate::routines::new_store());
let first = app
.clone()
.oneshot(
Request::builder()
.uri("/client")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
let etag = first
.headers()
.get(axum::http::header::ETAG)
.unwrap()
.to_str()
.unwrap()
.to_owned();
let resp = app
.oneshot(
Request::builder()
.uri("/client")
.header(axum::http::header::IF_NONE_MATCH, &etag)
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
let body = axum::body::to_bytes(resp.into_body(), usize::MAX)
.await
.unwrap();
assert!(body.is_empty(), "304 response must not carry a body");
}
#[tokio::test]
async fn build_app_client_fallback_serves_client_on_nested_routes() {
let app = build_app(crate::routines::new_store());
let resp = app
.oneshot(
Request::builder()
.uri("/client/routines")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let ctype = resp.headers().get(CONTENT_TYPE).unwrap();
assert!(ctype.to_str().unwrap().starts_with("text/html"));
}
#[tokio::test]
async fn build_app_client_and_root_serve_distinct_bundles() {
let app = build_app(crate::routines::new_store());
let root = app
.clone()
.oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
.await
.unwrap();
let client = app
.oneshot(
Request::builder()
.uri("/client")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
let root_etag = root
.headers()
.get(axum::http::header::ETAG)
.unwrap()
.clone();
let client_etag = client
.headers()
.get(axum::http::header::ETAG)
.unwrap()
.clone();
assert_ne!(root_etag, client_etag);
}
#[tokio::test]
async fn build_app_root_fallback_unaffected_by_client_route() {
let app = build_app(crate::routines::new_store());
let root = app
.clone()
.oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
.await
.unwrap();
let root_etag = root
.headers()
.get(axum::http::header::ETAG)
.unwrap()
.clone();
let unmatched = app
.oneshot(
Request::builder()
.uri("/some/unmatched/path")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(unmatched.status(), StatusCode::OK);
assert_eq!(
unmatched.headers().get(axum::http::header::ETAG).unwrap(),
&root_etag
);
}