#![cfg(test)]
use sha2::{Digest, Sha256};
use std::fmt::Write as _;
trait SyncHandlerExt {
fn call_sync(&self, req: crate::web::extract::Request) -> crate::web::response::Response;
}
impl SyncHandlerExt for crate::web::static_files::StaticFilesHandler {
fn call_sync(&self, req: crate::web::extract::Request) -> crate::web::response::Response {
futures_lite::future::block_on(crate::web::handler::Handler::call(
self,
&crate::Cx::for_testing(),
req,
))
}
}
#[test]
fn audit_etag_generation_strong_content_based() {
let content1 = b"Hello, world!";
let content2 = b"Hello, World!"; let content3 = b"Hello, world!";
let etag1 = crate::web::static_files::generate_etag_test_access(content1);
let etag2 = crate::web::static_files::generate_etag_test_access(content2);
let etag3 = crate::web::static_files::generate_etag_test_access(content3);
assert!(
!etag1.starts_with("W/"),
"Strong ETag must not have W/ prefix"
);
assert!(
!etag2.starts_with("W/"),
"Strong ETag must not have W/ prefix"
);
assert!(
etag1.starts_with('"') && etag1.ends_with('"'),
"Strong ETag must be quoted"
);
assert!(
etag2.starts_with('"') && etag2.ends_with('"'),
"Strong ETag must be quoted"
);
assert_ne!(
etag1, etag2,
"Different content must produce different strong ETags"
);
assert_eq!(
etag1, etag3,
"Identical content must produce identical strong ETags"
);
let expected_hash = {
let digest = Sha256::digest(content1);
let mut etag = String::with_capacity(2 + digest.len() * 2);
etag.push('"');
for &byte in &digest {
let _ = write!(etag, "{byte:02x}");
}
etag.push('"');
etag
};
assert_eq!(etag1, expected_hash, "ETag must be SHA256 of content");
let same_len_diff = b"Hello, earth!"; let etag_same_len = crate::web::static_files::generate_etag_test_access(same_len_diff);
assert_ne!(
etag1, etag_same_len,
"Same-length different content must produce different ETags"
);
}
#[test]
fn audit_etag_matching_handles_weak_client_etags() {
let server_etag = r#""abc123def456""#; let client_weak_etag = r#"W/"abc123def456""#; let client_strong_etag = r#""abc123def456""#; let client_different_etag = r#""different123""#;
assert!(
crate::web::static_files::etag_matches_test_access(client_weak_etag, server_etag),
"Server strong ETag must match client weak ETag for same content"
);
assert!(
crate::web::static_files::etag_matches_test_access(client_strong_etag, server_etag),
"Server strong ETag must match client strong ETag for same content"
);
assert!(
!crate::web::static_files::etag_matches_test_access(client_different_etag, server_etag),
"Different ETags must not match"
);
assert!(
crate::web::static_files::etag_matches_test_access("*", server_etag),
"Wildcard ETag must always match"
);
}
#[test]
fn audit_file_serving_includes_etag_headers() {
use std::fs;
use tempfile::TempDir;
let dir = TempDir::new().unwrap();
let file_path = dir.path().join("test.txt");
let file_content = "Test content for ETag";
fs::write(&file_path, file_content).unwrap();
let static_files = crate::web::static_files::StaticFiles::new(dir.path());
let response = static_files
.handler()
.call_sync(crate::web::extract::Request::new("GET", "/test.txt"));
assert!(
response.headers.contains_key("etag"),
"Static file response must include ETag header"
);
let etag = response.headers.get("etag").unwrap();
assert!(
!etag.starts_with("W/"),
"Static file ETag must be strong (no W/ prefix)"
);
assert!(
etag.starts_with('"') && etag.ends_with('"'),
"Static file ETag must be quoted"
);
assert!(
response.headers.contains_key("cache-control"),
"Static file response must include Cache-Control header"
);
}
#[test]
fn audit_conditional_requests_with_strong_etags() {
use std::fs;
use tempfile::TempDir;
let dir = TempDir::new().unwrap();
let file_path = dir.path().join("test.txt");
let file_content = "Test content for conditional requests";
fs::write(&file_path, file_content).unwrap();
let static_files = crate::web::static_files::StaticFiles::new(dir.path());
let handler = static_files.handler();
let response1 = handler.call_sync(crate::web::extract::Request::new("GET", "/test.txt"));
assert_eq!(response1.status, crate::web::response::StatusCode::OK);
let etag = response1.headers.get("etag").unwrap().clone();
let response2 = handler.call_sync(
crate::web::extract::Request::new("GET", "/test.txt").with_header("If-None-Match", etag),
);
assert_eq!(
response2.status,
crate::web::response::StatusCode::NOT_MODIFIED,
"Matching ETag must return 304 Not Modified"
);
assert!(
response2.body.is_empty(),
"304 response must have empty body"
);
assert!(
response2.headers.contains_key("etag"),
"304 response must still include ETag header"
);
let different_etag = r#""different-etag-value""#;
let response3 = handler.call_sync(
crate::web::extract::Request::new("GET", "/test.txt")
.with_header("If-None-Match", different_etag),
);
assert_eq!(
response3.status,
crate::web::response::StatusCode::OK,
"Non-matching ETag must return 200 OK with full content"
);
assert!(!response3.body.is_empty(), "200 response must include body");
}