mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
Merge pull request #30 from yashbhutwala/codex/brk-website-static-serving-tests
[codex] add brk_website static serving tests
This commit is contained in:
@@ -8,6 +8,7 @@ use axum::{
|
|||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
/// Website error type that maps to HTTP status codes.
|
/// Website error type that maps to HTTP status codes.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Error(StatusCode, String);
|
pub struct Error(StatusCode, String);
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
|
|||||||
@@ -73,3 +73,139 @@ fn sanitize(path: &str) -> String {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("/")
|
.join("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::{future::Future, sync::OnceLock};
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
body::{Body, to_bytes},
|
||||||
|
http::{HeaderMap, StatusCode, header},
|
||||||
|
response::IntoResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{sanitize, serve};
|
||||||
|
use crate::Website;
|
||||||
|
|
||||||
|
fn block_on<F: Future>(future: F) -> F::Output {
|
||||||
|
static RUNTIME: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
|
||||||
|
|
||||||
|
RUNTIME
|
||||||
|
.get_or_init(|| {
|
||||||
|
tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
.block_on(future)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn body_bytes(response: axum::http::Response<Body>) -> Vec<u8> {
|
||||||
|
block_on(async {
|
||||||
|
to_bytes(response.into_body(), 2 * 1024 * 1024)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.to_vec()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sanitize_removes_empty_and_traversal_segments() {
|
||||||
|
assert_eq!(sanitize("../styles/reset.css"), "styles/reset.css");
|
||||||
|
assert_eq!(sanitize("./styles//reset.css"), "styles/reset.css");
|
||||||
|
assert_eq!(sanitize(""), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serves_index_html_for_root_and_spa_routes() {
|
||||||
|
let request_headers = HeaderMap::new();
|
||||||
|
|
||||||
|
let root = serve(&Website::Default, "", &request_headers).unwrap();
|
||||||
|
assert_eq!(root.status(), StatusCode::OK);
|
||||||
|
assert_eq!(
|
||||||
|
root.headers()
|
||||||
|
.get(header::CONTENT_TYPE)
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"text/html",
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
root.headers()
|
||||||
|
.get(header::CACHE_CONTROL)
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"public, max-age=1, must-revalidate",
|
||||||
|
);
|
||||||
|
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
assert!(root.headers().get(header::ETAG).is_none());
|
||||||
|
} else {
|
||||||
|
assert!(root.headers().get(header::ETAG).is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
let root_body = body_bytes(root);
|
||||||
|
let spa_body =
|
||||||
|
body_bytes(serve(&Website::Default, "charts/price", &request_headers).unwrap());
|
||||||
|
|
||||||
|
assert_eq!(root_body, spa_body);
|
||||||
|
assert!(
|
||||||
|
String::from_utf8(root_body)
|
||||||
|
.unwrap()
|
||||||
|
.contains("<!doctype html>")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serves_static_assets_with_expected_headers() {
|
||||||
|
let response = serve(&Website::Default, "styles/reset.css", &HeaderMap::new()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
|
assert_eq!(
|
||||||
|
response
|
||||||
|
.headers()
|
||||||
|
.get(header::CONTENT_TYPE)
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"text/css",
|
||||||
|
);
|
||||||
|
|
||||||
|
let expected_cache_control = if cfg!(debug_assertions) {
|
||||||
|
"public, max-age=1, must-revalidate"
|
||||||
|
} else {
|
||||||
|
"public, max-age=31536000, immutable"
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
response
|
||||||
|
.headers()
|
||||||
|
.get(header::CACHE_CONTROL)
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
expected_cache_control,
|
||||||
|
);
|
||||||
|
assert!(!body_bytes(response).is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn traversal_like_paths_resolve_to_sanitized_assets() {
|
||||||
|
let direct =
|
||||||
|
body_bytes(serve(&Website::Default, "styles/reset.css", &HeaderMap::new()).unwrap());
|
||||||
|
let sanitized =
|
||||||
|
body_bytes(serve(&Website::Default, "../styles/reset.css", &HeaderMap::new()).unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sanitized, direct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_files_with_extensions_return_not_found() {
|
||||||
|
let response = serve(&Website::Default, "styles/missing.css", &HeaderMap::new())
|
||||||
|
.unwrap_err()
|
||||||
|
.into_response();
|
||||||
|
|
||||||
|
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user