mini-static 0.20.0

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

use std::fs;
use std::time::Duration;
use tempfile::TempDir;
use tokio::time::sleep;

#[tokio::test]
async fn asset_folder_overlapping_output_dir_is_rejected() {
    let root = TempDir::new().unwrap();
    let result = Server::new(root.path())
        .unwrap()
        .with_asset_folder(root.path());
    assert!(
        result.is_err(),
        "an asset folder equal to the output dir must be rejected"
    );
}

#[tokio::test]
async fn asset_folder_overlapping_an_existing_asset_folder_is_rejected() {
    let root = TempDir::new().unwrap();
    let assets = TempDir::new().unwrap();

    let result = Server::new(root.path())
        .unwrap()
        .with_asset_folder(assets.path())
        .unwrap()
        .with_asset_folder(assets.path());
    assert!(
        result.is_err(),
        "registering the same asset folder twice must be rejected"
    );
}

#[tokio::test]
async fn asset_folder_overlapping_a_source_folder_is_rejected_both_ways() {
    let root = TempDir::new().unwrap();
    let shared = TempDir::new().unwrap();

    let via_asset_then_source = Server::new(root.path())
        .unwrap()
        .with_asset_folder(shared.path())
        .unwrap()
        .with_source_folder(shared.path());
    assert!(
        via_asset_then_source.is_err(),
        "a source folder overlapping an already-registered asset folder must be rejected"
    );

    let via_source_then_asset = Server::new(root.path())
        .unwrap()
        .with_source_folder(shared.path())
        .unwrap()
        .with_asset_folder(shared.path());
    assert!(
        via_source_then_asset.is_err(),
        "an asset folder overlapping an already-registered source folder must be rejected"
    );
}

#[tokio::test]
async fn asset_folder_files_are_served_after_startup_build() {
    let assets = TempDir::new().unwrap();
    let out = TempDir::new().unwrap();
    fs::write(assets.path().join("index.html"), "<html>hi</html>").unwrap();
    fs::create_dir(assets.path().join("images")).unwrap();
    fs::write(assets.path().join("images/logo.svg"), "<svg></svg>").unwrap();

    let server = Server::new(out.path())
        .unwrap()
        .with_asset_folder(assets.path())
        .unwrap();
    let (port, handle) = server.run_ephemeral().await.unwrap();

    sleep(Duration::from_millis(200)).await;

    let index = fs::read_to_string(out.path().join("index.html")).unwrap();
    assert_eq!(index, "<html>hi</html>");
    let logo = fs::read_to_string(out.path().join("images/logo.svg")).unwrap();
    assert_eq!(logo, "<svg></svg>");

    let mut conn = tokio::net::TcpStream::connect(("127.0.0.1", port))
        .await
        .unwrap();
    use tokio::io::{AsyncReadExt, AsyncWriteExt};
    conn.write_all(b"GET /index.html HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")
        .await
        .unwrap();
    let mut response = Vec::new();
    conn.read_to_end(&mut response).await.unwrap();
    let response = String::from_utf8_lossy(&response);
    assert!(response.contains("HTTP/1.1 200"), "got: {response}");
    assert!(response.contains("<html>hi</html>"), "got: {response}");

    handle.shutdown().await;
}

#[tokio::test]
async fn asset_folder_change_rebuilds_and_live_reloads() {
    let assets = TempDir::new().unwrap();
    let out = TempDir::new().unwrap();
    fs::write(assets.path().join("index.html"), "v1").unwrap();

    let server = Server::new(out.path())
        .unwrap()
        .with_live_reload()
        .with_asset_folder(assets.path())
        .unwrap();
    let (_port, handle) = server.run_ephemeral().await.unwrap();

    sleep(Duration::from_millis(800)).await;
    assert_eq!(
        fs::read_to_string(out.path().join("index.html")).unwrap(),
        "v1"
    );

    fs::write(assets.path().join("index.html"), "v2").unwrap();
    sleep(Duration::from_millis(1500)).await;

    assert_eq!(
        fs::read_to_string(out.path().join("index.html")).unwrap(),
        "v2",
        "editing the source asset must re-copy it into the output dir"
    );

    handle.shutdown().await;
}