use super::{Door, Lys, NOW, Response, TestResult, answer, request, site, written};
use flate2::read::GzDecoder;
use std::io::Read;
fn written_bytes(response: Response) -> Result<(String, Vec<u8>), Box<dyn std::error::Error>> {
let mut out = Vec::new();
response.write(&mut out)?;
let at = out
.windows(4)
.position(|window| window == b"\r\n\r\n")
.ok_or("no head")?;
let body = out.split_off(at + 4);
Ok((String::from_utf8(out)?, body))
}
const ASSET: &str = "/assets/index-BdF3x9kZ.js";
const PNG: [u8; 4] = [137, 80, 78, 71];
fn gunzip(bytes: &[u8]) -> Result<String, std::io::Error> {
let mut text = String::new();
GzDecoder::new(bytes).read_to_string(&mut text)?;
Ok(text)
}
#[test]
fn a_hashed_asset_is_kept_for_good_and_the_index_is_never_stored() -> TestResult {
let files = tempfile::tempdir()?;
std::fs::create_dir(files.path().join("assets"))?;
std::fs::write(files.path().join("index.html"), "<html>studio</html>")?;
std::fs::write(files.path().join("app.js"), "let a = 1;")?;
std::fs::write(files.path().join("assets/index-BdF3x9kZ.js"), "let b = 2;")?;
let site = site(files.path());
let door = Door::new(Lys);
let hashed = written(answer(
&door,
&site,
&request("GET /assets/index-BdF3x9kZ.js HTTP/1.1\r\n\r\n")?,
NOW,
))?;
assert!(
hashed.contains("\r\nCache-Control: public, max-age=31536000, immutable\r\n"),
"{hashed}"
);
assert!(!hashed.contains("no-store"), "{hashed}");
for address in ["/", "/index.html", "/app.js", "/assets/absent-BdF3x9kZ.js"] {
let answered = written(answer(
&door,
&site,
&request(&format!("GET {address} HTTP/1.1\r\n\r\n"))?,
NOW,
))?;
assert!(
answered.contains("\r\nCache-Control: no-store\r\n"),
"{address}: {answered}"
);
assert!(!answered.contains("immutable"), "{address}: {answered}");
}
Ok(())
}
#[test]
fn gzip_is_sent_only_when_asked_and_decodes_to_the_file() -> TestResult {
let files = tempfile::tempdir()?;
std::fs::create_dir(files.path().join("assets"))?;
let script = "export const studio = 1;\n".repeat(100);
std::fs::write(files.path().join(ASSET.trim_start_matches('/')), &script)?;
std::fs::write(files.path().join("index.html"), "<html>first</html>")?;
std::fs::write(files.path().join("logo.png"), PNG)?;
let site = site(files.path());
let door = Door::new(Lys);
let get = |address: &str, accept: Option<&str>| {
let accept = accept
.map(|accept| format!("Accept-Encoding: {accept}\r\n"))
.unwrap_or_default();
request(&format!("GET {address} HTTP/1.1\r\n{accept}\r\n"))
.map(|asked| answer(&door, &site, &asked, NOW))
};
let (plain, body) = written_bytes(get(ASSET, None)?)?;
assert!(!plain.contains("Content-Encoding"), "{plain}");
assert!(plain.contains("\r\nVary: Accept-Encoding\r\n"), "{plain}");
assert_eq!(body, script.as_bytes());
let (refused, body) = written_bytes(get(ASSET, Some("br, gzip;q=0"))?)?;
assert!(!refused.contains("Content-Encoding"), "{refused}");
assert_eq!(body, script.as_bytes());
let (packed, body) = written_bytes(get(ASSET, Some("gzip, deflate, br"))?)?;
assert!(
packed.contains("\r\nContent-Encoding: gzip\r\n"),
"{packed}"
);
assert!(packed.contains("\r\nVary: Accept-Encoding\r\n"), "{packed}");
assert!(
packed.contains(&format!("\r\nContent-Length: {}\r\n", body.len())),
"{packed}"
);
assert!(body.len() < script.len(), "gzip made it no smaller");
assert_eq!(gunzip(&body)?, script);
let (first, body) = written_bytes(get("/", Some("gzip"))?)?;
assert!(first.contains("Content-Encoding: gzip"), "{first}");
assert_eq!(gunzip(&body)?, "<html>first</html>");
std::fs::write(files.path().join("index.html"), "<html>second</html>")?;
let (_, body) = written_bytes(get("/", Some("gzip"))?)?;
assert_eq!(gunzip(&body)?, "<html>second</html>");
let (image, body) = written_bytes(get("/logo.png", Some("gzip"))?)?;
assert!(!image.contains("Content-Encoding"), "{image}");
assert!(!image.contains("Vary"), "{image}");
assert_eq!(body, PNG);
Ok(())
}