mini-static 0.21.0

A secure, async static file server with streaming, traversal protection, and connection limits.
Documentation
use super::*;

#[test]
fn reload_event_frame_formats_css_correctly() {
    let frame = reload_event_frame(&ChangeType::Css);
    let s = String::from_utf8(frame.to_vec()).unwrap();
    assert_eq!(s, "event: css\ndata: {\"type\":\"css\"}\n\n");
}

#[test]
fn reload_event_frame_formats_script_correctly() {
    let frame = reload_event_frame(&ChangeType::Script);
    let s = String::from_utf8(frame.to_vec()).unwrap();
    assert_eq!(s, "event: script\ndata: {\"type\":\"script\"}\n\n");
}

#[test]
fn reload_event_frame_formats_html_correctly() {
    let frame = reload_event_frame(&ChangeType::Html);
    let s = String::from_utf8(frame.to_vec()).unwrap();
    assert_eq!(s, "event: html\ndata: {\"type\":\"html\"}\n\n");
}

#[test]
fn reload_event_frame_formats_other_correctly() {
    let frame = reload_event_frame(&ChangeType::Other);
    let s = String::from_utf8(frame.to_vec()).unwrap();
    assert_eq!(s, "event: other\ndata: {\"type\":\"other\"}\n\n");
}

#[test]
fn change_type_from_path_css() {
    assert_eq!(
        ChangeType::from_path(Path::new("style.css")),
        ChangeType::Css
    );
    assert_eq!(
        ChangeType::from_path(Path::new("dir/main.css")),
        ChangeType::Css
    );
}

#[test]
fn change_type_from_path_script() {
    assert_eq!(
        ChangeType::from_path(Path::new("app.js")),
        ChangeType::Script
    );
    assert_eq!(
        ChangeType::from_path(Path::new("mod.mjs")),
        ChangeType::Script
    );
    assert_eq!(
        ChangeType::from_path(Path::new("dir/lib.js")),
        ChangeType::Script
    );
}

#[test]
fn change_type_from_path_html() {
    assert_eq!(
        ChangeType::from_path(Path::new("index.html")),
        ChangeType::Html
    );
    assert_eq!(
        ChangeType::from_path(Path::new("page.htm")),
        ChangeType::Html
    );
    assert_eq!(
        ChangeType::from_path(Path::new("dir/file.html")),
        ChangeType::Html
    );
}

#[test]
fn change_type_from_path_other() {
    assert_eq!(
        ChangeType::from_path(Path::new("image.png")),
        ChangeType::Other
    );
    assert_eq!(
        ChangeType::from_path(Path::new("data.json")),
        ChangeType::Other
    );
    assert_eq!(
        ChangeType::from_path(Path::new("README")),
        ChangeType::Other
    );
}

#[test]
fn reload_script_tag_embeds_the_live_reload_path() {
    // Disproves hardcoding a divergent path literal in the script: if someone edits
    // `LIVE_RELOAD_PATH` without updating `reload_script_tag()`, the injected client
    // would open an `EventSource` to a path the server never serves.
    assert!(reload_script_tag().contains(LIVE_RELOAD_PATH));
}

#[test]
fn inject_reload_script_inserts_before_closing_body_tag() {
    let mut html = b"<html><body><h1>hi</h1></body></html>".to_vec();
    inject_reload_script(&mut html);
    let s = String::from_utf8(html).unwrap();

    assert!(s.starts_with("<html><body><h1>hi</h1>"));
    assert!(s.ends_with("</body></html>"));
    assert!(s.contains(LIVE_RELOAD_PATH));
    // The script must land strictly before the closing tag, not after it.
    assert!(s.find("<script>").unwrap() < s.find("</body>").unwrap());
}

#[test]
fn inject_reload_script_handles_uppercase_closing_tag() {
    let mut html = b"<HTML><BODY>hi</BODY></HTML>".to_vec();
    inject_reload_script(&mut html);
    let s = String::from_utf8(html).unwrap();

    assert!(s.find("<script>").unwrap() < s.find("</BODY>").unwrap());
}

#[test]
fn inject_reload_script_appends_when_no_body_tag_present() {
    let mut html = b"<h1>fragment, no body tag</h1>".to_vec();
    inject_reload_script(&mut html);
    let s = String::from_utf8(html).unwrap();

    assert!(s.starts_with("<h1>fragment, no body tag</h1>"));
    assert!(s.ends_with("</script>"));
}

#[tokio::test]
async fn sse_body_yields_a_frame_per_broadcast_event() {
    use crate::watcher::Broadcaster;
    use http_body_util::BodyExt;
    use std::path::PathBuf;

    let broadcaster = Broadcaster::new();
    let rx = broadcaster.subscribe();
    let mut body = SseBody::new(rx);

    broadcaster.broadcast(ChangeEvent {
        path: PathBuf::from("style.css"),
        change_type: ChangeType::Css,
    });

    let frame = body
        .frame()
        .await
        .expect("stream ended early")
        .expect("frame error");
    let data = frame.into_data().unwrap();
    assert_eq!(&data[..], b"event: css\ndata: {\"type\":\"css\"}\n\n");
}