mini-static 0.20.0

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

#[test]
fn escape_js_string_escapes_backslash_quote_and_slash() {
    assert_eq!(escape_js_string("a\\b"), "a\\\\b");
    assert_eq!(escape_js_string("a\"b"), "a\\\"b");
    assert_eq!(escape_js_string("a/b"), "a\\/b");
}

#[test]
fn escape_js_string_escapes_control_characters() {
    assert_eq!(escape_js_string("a\u{0007}b"), "a\\u0007b");
}

#[test]
fn escape_js_string_breaks_up_a_closing_script_sequence() {
    // The invariant that matters: no unescaped `</script` (case-insensitive)
    // byte sequence survives -- that's the only sequence HTML's tokenizer
    // treats specially while already inside a <script> element's raw text.
    // An unescaped, slash-free `<script>` elsewhere is inert text there,
    // not a second tag -- the tokenizer isn't scanning for tag-opens in
    // that state, only for its own closing sequence.
    let escaped = escape_js_string("</script>");
    assert_eq!(escaped, "<\\/script>");
    assert!(!escaped.to_lowercase().contains("</script"));
}

#[test]
fn spa_script_tag_embeds_the_navigate_event_name() {
    assert!(spa_script_tag(None).contains(SPA_NAVIGATE_EVENT));
    assert!(spa_script_tag(Some("#app")).contains(SPA_NAVIGATE_EVENT));
}

#[test]
fn spa_script_tag_embeds_the_configured_root_selector() {
    let script = spa_script_tag(Some("#app"));
    assert!(script.contains("#app"));
}

#[test]
fn spa_script_tag_falls_back_to_document_body_when_no_root_configured() {
    let script = spa_script_tag(None);
    assert!(script.contains("doc.body"));
    assert!(script.contains("ROOT_SELECTOR=null;"));
}

#[test]
fn spa_script_tag_embeds_the_selector_with_its_quote_escaped() {
    let script = spa_script_tag(Some(r#"[data-x="y"]"#));
    // Pins the exact generated assignment: a bare `"` here would terminate
    // the string literal early and corrupt the rest of the script.
    assert!(script.contains(r#"ROOT_SELECTOR="[data-x=\"y\"]";"#));
}

#[test]
fn spa_script_tag_neutralizes_a_closing_script_sequence_in_the_selector() {
    let malicious = "</script><script>alert(1)</script>";
    let script = spa_script_tag(Some(malicious));

    // The only `</script` (case-insensitive) sequence allowed to survive
    // is the genuine wrapper's own closing tag -- exactly one.
    let lower = script.to_lowercase();
    assert_eq!(lower.matches("</script").count(), 1);
}

#[test]
fn spa_transition_style_tag_is_empty_for_fade() {
    assert_eq!(spa_transition_style_tag(&SpaTransition::Fade), "");
}

#[test]
fn spa_transition_style_tag_overrides_blend_mode_for_slide() {
    let style = spa_transition_style_tag(&SpaTransition::Slide(SlideOptions::default()));
    assert!(style.contains("mix-blend-mode:normal"));
    assert!(style.contains("::view-transition-old(root)"));
    assert!(style.contains("::view-transition-new(root)"));
}

#[test]
fn spa_transition_style_tag_defaults_to_300ms_forward_ease() {
    let style = spa_transition_style_tag(&SpaTransition::Slide(SlideOptions::default()));
    assert!(style.contains("animation-duration:300ms;"));
    assert!(style.contains("animation-timing-function:ease;"));
    assert!(style.contains("mini-static-slide-out{to{transform:translateX(-100%);}}"));
    assert!(style.contains("mini-static-slide-in{from{transform:translateX(100%);}}"));
}

#[test]
fn spa_transition_style_tag_honors_configured_duration_and_easing() {
    let options = SlideOptions::default()
        .duration_ms(750)
        .easing("ease-in-out");
    let style = spa_transition_style_tag(&SpaTransition::Slide(options));
    assert!(style.contains("animation-duration:750ms;"));
    assert!(style.contains("animation-timing-function:ease-in-out;"));
}

#[test]
fn spa_transition_style_tag_reverses_transforms_for_reverse_direction() {
    let options = SlideOptions::default().direction(SlideDirection::Reverse);
    let style = spa_transition_style_tag(&SpaTransition::Slide(options));
    assert!(style.contains("mini-static-slide-out{to{transform:translateX(100%);}}"));
    assert!(style.contains("mini-static-slide-in{from{transform:translateX(-100%);}}"));
}

#[test]
fn spa_transition_style_tag_strips_style_breakout_chars_from_easing() {
    let options = SlideOptions::default().easing("</style><script>alert(1)</script>");
    let style = spa_transition_style_tag(&SpaTransition::Slide(options));

    // The only `<style`/`</style` occurrences allowed to survive are the
    // genuine wrapper's own opening and closing tags -- exactly one each.
    let lower = style.to_lowercase();
    assert_eq!(lower.matches("<style").count(), 1);
    assert_eq!(lower.matches("</style").count(), 1);
    assert!(!lower.contains("<script"));
}

#[test]
fn inject_spa_script_inserts_before_closing_body_tag() {
    let mut html = b"<html><body><h1>hi</h1></body></html>".to_vec();
    inject_spa_script(&mut html, None, &SpaTransition::Fade);
    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(SPA_NAVIGATE_EVENT));
    assert!(s.find("<script>").unwrap() < s.find("</body>").unwrap());
}

#[test]
fn inject_spa_script_handles_uppercase_closing_tag() {
    let mut html = b"<HTML><BODY>hi</BODY></HTML>".to_vec();
    inject_spa_script(&mut html, None, &SpaTransition::Fade);
    let s = String::from_utf8(html).unwrap();

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

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

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

#[test]
fn inject_spa_script_is_safe_on_empty_input() {
    let mut html: Vec<u8> = Vec::new();
    inject_spa_script(&mut html, Some("#app"), &SpaTransition::Fade);
    let s = String::from_utf8(html).unwrap();

    assert!(s.starts_with("<script>"));
    assert!(s.ends_with("</script>"));
}

#[test]
fn inject_spa_script_prepends_the_style_tag_when_slide_is_configured() {
    let mut html = b"<html><body></body></html>".to_vec();
    inject_spa_script(
        &mut html,
        None,
        &SpaTransition::Slide(SlideOptions::default()),
    );
    let s = String::from_utf8(html).unwrap();

    assert!(s.contains("<style>"));
    let style_pos = s.find("<style>").unwrap();
    let script_pos = s.find("<script>").unwrap();
    assert!(style_pos < script_pos);
}

#[test]
fn inject_spa_script_injects_no_style_tag_for_fade() {
    let mut html = b"<html><body></body></html>".to_vec();
    inject_spa_script(&mut html, None, &SpaTransition::Fade);
    let s = String::from_utf8(html).unwrap();

    assert!(!s.contains("<style>"));
}