test: add functioning tests
This commit is contained in:
parent
c459800fca
commit
57f5404fde
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -362,18 +362,18 @@ checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
version = "1.0.218"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
version = "1.0.218"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -403,7 +403,12 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"bytes",
|
||||
"flate2",
|
||||
"http-body-util",
|
||||
"static-serve-macro",
|
||||
"tokio",
|
||||
"tower",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -14,5 +14,12 @@ static-serve-macro = { path = "../static-serve-macro", version = "0.1.0" }
|
||||
axum = { version = "0.8", default-features = false }
|
||||
bytes = "1.10"
|
||||
|
||||
[dev-dependencies]
|
||||
http-body-util = "0.1"
|
||||
tokio = { version = "1.44", features = ["rt", "macros"] }
|
||||
tower = { version = "0.5", features = ["util"] }
|
||||
zstd = "0.13"
|
||||
flate2 = "1.1"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
@ -127,66 +127,3 @@ where
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
#[test]
|
||||
fn router_created_with_test_routes_lit_str() {
|
||||
embed_assets!("test_assets/small", compress = false);
|
||||
let router: Router<()> = static_router();
|
||||
assert!(router.has_routes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn router_created_not_compressed_because_not_worthwhile() {
|
||||
embed_assets!("test_assets/small", compress = true);
|
||||
let router: Router<()> = static_router();
|
||||
assert!(router.has_routes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn router_created_compressed() {
|
||||
embed_assets!("test_assets/big", compress = true);
|
||||
let router: Router<()> = static_router();
|
||||
assert!(router.has_routes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn router_created_with_test_routes_ident() {
|
||||
embed_assets!(test_assets, compress = true);
|
||||
let router: Router<()> = static_router();
|
||||
assert!(router.has_routes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn router_created_ignore_dirs_one() {
|
||||
embed_assets!(test_assets, ignore_dirs = ["test_assets/big"]);
|
||||
let router: Router<()> = static_router();
|
||||
assert!(router.has_routes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn router_created_ignore_dirs_two() {
|
||||
embed_assets!(
|
||||
test_assets,
|
||||
ignore_dirs = ["test_assets/big", "test_assets/small"]
|
||||
);
|
||||
let router: Router<()> = static_router();
|
||||
// all directories ignored, so router has no routes
|
||||
assert!(!router.has_routes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn router_created_ignore_dirs_with_defaults() {
|
||||
// TODO: actually create one of the default ignore directories
|
||||
// in `test_assets` to make sure this works
|
||||
embed_assets!(
|
||||
test_assets,
|
||||
ignore_dirs = ["test_assets/big"],
|
||||
use_default_ignore_dirs = true
|
||||
);
|
||||
let router: Router<()> = static_router();
|
||||
assert!(router.has_routes());
|
||||
}
|
||||
}
|
||||
|
342
static-serve/tests/tests.rs
Normal file
342
static-serve/tests/tests.rs
Normal file
@ -0,0 +1,342 @@
|
||||
//! Integration tests for static-serve and macro
|
||||
use std::io::Read;
|
||||
|
||||
use axum::{
|
||||
body::Body,
|
||||
http::{
|
||||
header::{ACCEPT_ENCODING, CONTENT_ENCODING, IF_NONE_MATCH},
|
||||
HeaderValue, Request, Response, StatusCode,
|
||||
},
|
||||
Router,
|
||||
};
|
||||
use http_body_util::BodyExt;
|
||||
use tower::ServiceExt;
|
||||
|
||||
use static_serve_macro::embed_assets;
|
||||
|
||||
enum Compression {
|
||||
Zstd,
|
||||
Gzip,
|
||||
Both,
|
||||
None,
|
||||
}
|
||||
|
||||
async fn get_response(
|
||||
router: Router<()>,
|
||||
request: Request<axum::body::Body>,
|
||||
) -> Response<axum::body::Body> {
|
||||
router
|
||||
.into_service()
|
||||
.oneshot(request)
|
||||
.await
|
||||
.expect("sending request")
|
||||
}
|
||||
|
||||
fn create_request(route: &str, compression: &Compression) -> Request<axum::body::Body> {
|
||||
let accept_encoding_header = match compression {
|
||||
Compression::Both => Some(HeaderValue::from_static("zstd, gzip")),
|
||||
Compression::Zstd => Some(HeaderValue::from_static("zstd")),
|
||||
Compression::Gzip => Some(HeaderValue::from_static("gzip")),
|
||||
Compression::None => None,
|
||||
};
|
||||
match accept_encoding_header {
|
||||
Some(v) => Request::builder()
|
||||
.uri(route)
|
||||
.header(ACCEPT_ENCODING, v)
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
None => Request::builder().uri(route).body(Body::empty()).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn decompress_zstd(compressed_body: &[u8]) -> Vec<u8> {
|
||||
let mut decoder = zstd::Decoder::new(compressed_body).expect("failed to create decoder");
|
||||
let mut decompressed_body = Vec::new();
|
||||
std::io::copy(&mut decoder, &mut decompressed_body).expect("failed to decompress");
|
||||
decompressed_body
|
||||
}
|
||||
|
||||
fn decompress_gzip(compressed_body: &[u8]) -> Vec<u8> {
|
||||
let mut decompressed_body = Vec::new();
|
||||
let mut decoder = flate2::bufread::GzDecoder::new(compressed_body);
|
||||
decoder
|
||||
.read_to_end(&mut decompressed_body)
|
||||
.expect("can't decode body");
|
||||
decompressed_body
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn router_created_with_lit_str() {
|
||||
embed_assets!("../static-serve/test_assets/small", compress = false);
|
||||
let router: Router<()> = static_router();
|
||||
assert!(router.has_routes());
|
||||
let request = create_request("/app.js", &Compression::None);
|
||||
let response = get_response(router, request).await;
|
||||
let (parts, body) = response.into_parts();
|
||||
assert!(parts.status.is_success());
|
||||
assert_eq!(
|
||||
parts.headers.get("content-type").unwrap(),
|
||||
"text/javascript"
|
||||
);
|
||||
assert!(parts.headers.contains_key("etag"));
|
||||
|
||||
let collected_body_bytes = body.into_data_stream().collect().await.unwrap().to_bytes();
|
||||
let expected_body_bytes = include_bytes!("../../test_assets/small/app.js");
|
||||
assert_eq!(*collected_body_bytes, *expected_body_bytes);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn router_created_uncompressed_because_not_worthwhile() {
|
||||
embed_assets!("../static-serve/test_assets/small", compress = true);
|
||||
let router: Router<()> = static_router();
|
||||
assert!(router.has_routes());
|
||||
|
||||
let request = create_request("/app.js", &Compression::Zstd);
|
||||
let response = get_response(router, request).await;
|
||||
let (parts, body) = response.into_parts();
|
||||
assert!(parts.status.is_success());
|
||||
assert_eq!(
|
||||
parts.headers.get("content-type").unwrap(),
|
||||
"text/javascript"
|
||||
);
|
||||
assert!(parts.headers.contains_key("etag"));
|
||||
|
||||
let collected_body_bytes = body.into_data_stream().collect().await.unwrap().to_bytes();
|
||||
// Response should not be compressed since the benefit is insignificant
|
||||
let expected_body_bytes = include_bytes!("../../test_assets/small/app.js");
|
||||
assert_eq!(*collected_body_bytes, *expected_body_bytes);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn router_created_compressed_zstd_only() {
|
||||
embed_assets!("../static-serve/test_assets/big", compress = true);
|
||||
let router: Router<()> = static_router();
|
||||
assert!(router.has_routes());
|
||||
|
||||
let request = create_request("/app.js", &Compression::Zstd);
|
||||
let response = get_response(router, request).await;
|
||||
let (parts, body) = response.into_parts();
|
||||
assert!(parts.status.is_success());
|
||||
assert_eq!(
|
||||
parts.headers.get(CONTENT_ENCODING),
|
||||
Some(&HeaderValue::from_str("zstd").unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
parts.headers.get("content-type").unwrap(),
|
||||
"text/javascript"
|
||||
);
|
||||
assert!(parts.headers.contains_key("etag"));
|
||||
|
||||
let collected_body_bytes = body.into_data_stream().collect().await.unwrap().to_bytes();
|
||||
|
||||
// Decompress the response body
|
||||
let decompressed_body = decompress_zstd(&collected_body_bytes);
|
||||
assert_eq!(
|
||||
decompressed_body,
|
||||
include_bytes!("../../test_assets/big/app.js")
|
||||
);
|
||||
|
||||
// Expect the compressed version
|
||||
let expected_body_bytes = include_bytes!("../../test_assets/dist/app.js.zst");
|
||||
assert_eq!(*collected_body_bytes, *expected_body_bytes);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn router_created_compressed_gzip_only() {
|
||||
embed_assets!("../static-serve/test_assets/big", compress = true);
|
||||
let router: Router<()> = static_router();
|
||||
assert!(router.has_routes());
|
||||
|
||||
let request = create_request("/app.js", &Compression::Gzip);
|
||||
let response = get_response(router, request).await;
|
||||
let (parts, body) = response.into_parts();
|
||||
assert!(parts.status.is_success());
|
||||
assert_eq!(
|
||||
parts.headers.get(CONTENT_ENCODING),
|
||||
Some(&HeaderValue::from_str("gzip").unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
parts.headers.get("content-type").unwrap(),
|
||||
"text/javascript"
|
||||
);
|
||||
assert!(parts.headers.contains_key("etag"));
|
||||
|
||||
let collected_body_bytes = body.into_data_stream().collect().await.unwrap().to_bytes();
|
||||
let decompressed_body = decompress_gzip(&collected_body_bytes);
|
||||
|
||||
assert_eq!(
|
||||
decompressed_body,
|
||||
include_bytes!("../../test_assets/big/app.js"),
|
||||
"decompressed body is not as expected"
|
||||
);
|
||||
|
||||
// Expect the compressed version
|
||||
let expected_body_bytes = include_bytes!("../../test_assets/dist/app.js.gz");
|
||||
assert_eq!(*collected_body_bytes, *expected_body_bytes);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn router_created_compressed_zstd_or_gzip_accepted() {
|
||||
embed_assets!("../static-serve/test_assets/big", compress = true);
|
||||
let router: Router<()> = static_router();
|
||||
assert!(router.has_routes());
|
||||
|
||||
let request = create_request("/app.js", &Compression::Both);
|
||||
let response = get_response(router, request).await;
|
||||
let (parts, body) = response.into_parts();
|
||||
assert!(parts.status.is_success());
|
||||
assert_eq!(
|
||||
parts.headers.get(CONTENT_ENCODING),
|
||||
Some(&HeaderValue::from_str("zstd").unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
parts.headers.get("content-type").unwrap(),
|
||||
"text/javascript"
|
||||
);
|
||||
assert!(parts.headers.contains_key("etag"));
|
||||
|
||||
let collected_body_bytes = body.into_data_stream().collect().await.unwrap().to_bytes();
|
||||
let decompressed_body = decompress_zstd(&collected_body_bytes);
|
||||
assert_eq!(
|
||||
decompressed_body,
|
||||
include_bytes!("../../test_assets/big/app.js")
|
||||
);
|
||||
|
||||
// Expect the compressed version
|
||||
let expected_body_bytes = include_bytes!("../../test_assets/dist/app.js.zst");
|
||||
assert_eq!(*collected_body_bytes, *expected_body_bytes);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn router_created_ignore_dirs_one() {
|
||||
embed_assets!("../static-serve/test_assets", ignore_dirs = ["dist"]);
|
||||
let router: Router<()> = static_router();
|
||||
assert!(router.has_routes());
|
||||
|
||||
let request = create_request("/small/app.js", &Compression::None);
|
||||
let response = get_response(router, request).await;
|
||||
let (parts, body) = response.into_parts();
|
||||
assert!(parts.status.is_success());
|
||||
|
||||
let collected_body_bytes = body.into_data_stream().collect().await.unwrap().to_bytes();
|
||||
let expected_body_bytes = include_bytes!("../../test_assets/small/app.js");
|
||||
|
||||
assert_eq!(
|
||||
parts.headers.get("content-type").unwrap(),
|
||||
"text/javascript"
|
||||
);
|
||||
assert!(parts.headers.contains_key("etag"));
|
||||
assert_eq!(*collected_body_bytes, *expected_body_bytes);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn router_created_ignore_dirs_three() {
|
||||
embed_assets!(
|
||||
"../static-serve/test_assets",
|
||||
ignore_dirs = ["big", "small", "dist"]
|
||||
);
|
||||
let router: Router<()> = static_router();
|
||||
// all directories ignored, so router has no routes
|
||||
assert!(!router.has_routes());
|
||||
|
||||
let request = create_request("/app.js", &Compression::None);
|
||||
let response = get_response(router, request).await;
|
||||
let (parts, body) = response.into_parts();
|
||||
let collected_body_bytes = body.into_data_stream().collect().await.unwrap().to_bytes();
|
||||
|
||||
// Expect a 404 Not Found with empty body
|
||||
assert_eq!(parts.status, StatusCode::NOT_FOUND);
|
||||
assert!(collected_body_bytes.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handles_conditional_requests_same_etag() {
|
||||
embed_assets!("../static-serve/test_assets/big", compress = true);
|
||||
let router: Router<()> = static_router();
|
||||
assert!(router.has_routes());
|
||||
|
||||
let request = create_request("/app.js", &Compression::Zstd);
|
||||
let response = get_response(router.clone(), request).await;
|
||||
let (parts, body) = response.into_parts();
|
||||
assert!(parts.status.is_success());
|
||||
assert_eq!(
|
||||
parts.headers.get(CONTENT_ENCODING),
|
||||
Some(&HeaderValue::from_str("zstd").unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
parts.headers.get("content-type").unwrap(),
|
||||
"text/javascript"
|
||||
);
|
||||
let etag = parts
|
||||
.headers
|
||||
.get("etag")
|
||||
.expect("no etag header when there should be one!");
|
||||
|
||||
let collected_body_bytes = body.into_data_stream().collect().await.unwrap().to_bytes();
|
||||
let decompressed_body = decompress_zstd(&collected_body_bytes);
|
||||
assert_eq!(
|
||||
decompressed_body,
|
||||
include_bytes!("../../test_assets/big/app.js")
|
||||
);
|
||||
|
||||
// Expect the compressed version
|
||||
let expected_body_bytes = include_bytes!("../../test_assets/dist/app.js.zst");
|
||||
assert_eq!(*collected_body_bytes, *expected_body_bytes);
|
||||
|
||||
let request = Request::builder()
|
||||
.uri("/app.js")
|
||||
.header(IF_NONE_MATCH, etag)
|
||||
.header(ACCEPT_ENCODING, "zstd")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
let response = get_response(router, request).await;
|
||||
let (parts, body) = response.into_parts();
|
||||
assert_eq!(parts.status, StatusCode::NOT_MODIFIED);
|
||||
assert_eq!(
|
||||
parts
|
||||
.headers
|
||||
.get("content-length")
|
||||
.expect("no content-length header!"),
|
||||
"0"
|
||||
);
|
||||
let collected_body_bytes = body.into_data_stream().collect().await.unwrap().to_bytes();
|
||||
assert!(collected_body_bytes.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handles_conditional_requests_different_etag() {
|
||||
embed_assets!("../static-serve/test_assets/big", compress = true);
|
||||
let router: Router<()> = static_router();
|
||||
assert!(router.has_routes());
|
||||
|
||||
let request = Request::builder()
|
||||
.uri("/app.js")
|
||||
.header(IF_NONE_MATCH, "n0t4r34l3t4g")
|
||||
.header(ACCEPT_ENCODING, "zstd")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
let response = get_response(router, request).await;
|
||||
|
||||
let (parts, body) = response.into_parts();
|
||||
assert_eq!(parts.status, StatusCode::OK);
|
||||
assert_ne!(
|
||||
parts
|
||||
.headers
|
||||
.get("content-length")
|
||||
.expect("no content-length header!"),
|
||||
"0",
|
||||
"content length is unexpectedly zero!"
|
||||
);
|
||||
|
||||
let collected_body_bytes = body.into_data_stream().collect().await.unwrap().to_bytes();
|
||||
assert!(!collected_body_bytes.is_empty());
|
||||
let decompressed_body = decompress_zstd(&collected_body_bytes);
|
||||
assert_eq!(
|
||||
decompressed_body,
|
||||
include_bytes!("../../test_assets/big/app.js")
|
||||
);
|
||||
|
||||
// Expect the compressed version
|
||||
let expected_body_bytes = include_bytes!("../../test_assets/dist/app.js.zst");
|
||||
assert_eq!(*collected_body_bytes, *expected_body_bytes);
|
||||
}
|
20
test_assets/big/app.js
Normal file
20
test_assets/big/app.js
Normal file
@ -0,0 +1,20 @@
|
||||
console.log("Hello, world!");
|
||||
console.log("Hello, world!");
|
||||
console.log("Hello, world!");
|
||||
console.log("Hello, world!");
|
||||
console.log("Hello, world!");
|
||||
console.log("Hello, world!");
|
||||
console.log("Hello, world!");
|
||||
console.log("Hello, world!");
|
||||
console.log("Hello, world!");
|
||||
console.log("Hello, world!");
|
||||
console.log("Hello, world!");
|
||||
console.log("Hello, world!");
|
||||
console.log("Hello, world!");
|
||||
console.log("Hello, world!");
|
||||
console.log("Hello, world!");
|
||||
console.log("Hello, world!");
|
||||
console.log("Hello, world!");
|
||||
console.log("Hello, world!");
|
||||
console.log("Hello, world!");
|
||||
console.log("Hello, world!");
|
50
test_assets/big/styles.css
Normal file
50
test_assets/big/styles.css
Normal file
@ -0,0 +1,50 @@
|
||||
|
||||
|
||||
|
||||
|
||||
body {
|
||||
|
||||
|
||||
background: black ;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
body {
|
||||
|
||||
|
||||
background: black ;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
body {
|
||||
|
||||
|
||||
background: black ;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
body {
|
||||
|
||||
|
||||
background: black ;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
body {
|
||||
|
||||
|
||||
background: black ;
|
||||
|
||||
}
|
BIN
test_assets/dist/app.js.gz
vendored
Normal file
BIN
test_assets/dist/app.js.gz
vendored
Normal file
Binary file not shown.
BIN
test_assets/dist/app.js.zst
vendored
Normal file
BIN
test_assets/dist/app.js.zst
vendored
Normal file
Binary file not shown.
1
test_assets/dist/ignore_me_plz.txt
vendored
Normal file
1
test_assets/dist/ignore_me_plz.txt
vendored
Normal file
@ -0,0 +1 @@
|
||||
This file should only get included and served when the default ignore list is disabled.
|
1
test_assets/small/app.js
Normal file
1
test_assets/small/app.js
Normal file
@ -0,0 +1 @@
|
||||
console.log("Hello, world!");
|
1
test_assets/small/styles.css
Normal file
1
test_assets/small/styles.css
Normal file
@ -0,0 +1 @@
|
||||
body { background: black; }
|
Loading…
x
Reference in New Issue
Block a user