mini-static 0.5.2

A secure, async static file server with streaming, traversal protection, and connection limits.
Documentation
use mini_static::Server;
use hyper::Method;
use std::fs;
use tempfile::TempDir;

#[test]
fn delete_method_returns_405() {
    let root = TempDir::new().unwrap();
    fs::write(root.path().join("file.txt"), b"content").unwrap();

    let server = Server::new(root.path()).unwrap();
    let response = server.handle_request_with_method(&Method::DELETE, "/file.txt");

    assert_eq!(response.status().as_u16(), 405);
}

#[test]
fn delete_response_includes_allow_header() {
    let root = TempDir::new().unwrap();
    fs::write(root.path().join("file.txt"), b"content").unwrap();

    let server = Server::new(root.path()).unwrap();
    let response = server.handle_request_with_method(&Method::DELETE, "/file.txt");

    let allow_header = response.headers().get("Allow");
    assert_eq!(
        allow_header.map(|v| v.to_str().unwrap()),
        Some("GET, HEAD"),
        "DELETE should include Allow: GET, HEAD"
    );
}

#[test]
fn post_method_returns_405() {
    let root = TempDir::new().unwrap();
    let server = Server::new(root.path()).unwrap();
    let response = server.handle_request_with_method(&Method::POST, "/anything");

    assert_eq!(response.status().as_u16(), 405);
}

#[test]
fn put_method_returns_405() {
    let root = TempDir::new().unwrap();
    let server = Server::new(root.path()).unwrap();
    let response = server.handle_request_with_method(&Method::PUT, "/anything");

    assert_eq!(response.status().as_u16(), 405);
}

#[test]
fn patch_method_returns_405() {
    let root = TempDir::new().unwrap();
    let server = Server::new(root.path()).unwrap();
    let response = server.handle_request_with_method(&Method::PATCH, "/anything");

    assert_eq!(response.status().as_u16(), 405);
}

#[test]
fn options_method_returns_405() {
    let root = TempDir::new().unwrap();
    let server = Server::new(root.path()).unwrap();
    let response = server.handle_request_with_method(&Method::OPTIONS, "/anything");

    assert_eq!(response.status().as_u16(), 405);
}

#[test]
fn get_method_succeeds_for_existing_file() {
    let root = TempDir::new().unwrap();
    fs::write(root.path().join("file.txt"), b"content").unwrap();

    let server = Server::new(root.path()).unwrap();
    let response = server.handle_request_with_method(&Method::GET, "/file.txt");

    assert_eq!(response.status().as_u16(), 200);
}

#[test]
fn head_method_succeeds_for_existing_file() {
    let root = TempDir::new().unwrap();
    fs::write(root.path().join("file.txt"), b"content").unwrap();

    let server = Server::new(root.path()).unwrap();
    let response = server.handle_request_with_method(&Method::HEAD, "/file.txt");

    assert_eq!(response.status().as_u16(), 200);
}

#[test]
fn get_method_returns_404_for_missing() {
    let root = TempDir::new().unwrap();
    let server = Server::new(root.path()).unwrap();
    let response = server.handle_request_with_method(&Method::GET, "/missing.txt");

    assert_eq!(response.status().as_u16(), 404);
}

#[test]
fn head_method_returns_404_for_missing() {
    let root = TempDir::new().unwrap();
    let server = Server::new(root.path()).unwrap();
    let response = server.handle_request_with_method(&Method::HEAD, "/missing.txt");

    assert_eq!(response.status().as_u16(), 404);
}

#[test]
fn method_not_allowed_includes_nosniff() {
    let root = TempDir::new().unwrap();
    let server = Server::new(root.path()).unwrap();
    let response = server.handle_request_with_method(&Method::DELETE, "/anything");

    assert!(response.headers().get("X-Content-Type-Options").is_some());
    assert_eq!(
        response
            .headers()
            .get("X-Content-Type-Options")
            .map(|v| v.to_str().unwrap()),
        Some("nosniff")
    );
}

#[test]
fn delete_on_file_does_not_read_file_content() {
    // The key test from the DEV_PLAN: DELETE returns 405 before attempting to read the file.
    // We verify this by checking that DELETE returns 405 status immediately, without
    // trying to access file content.
    let root = TempDir::new().unwrap();
    fs::write(root.path().join("file.txt"), b"secret content").unwrap();

    let server = Server::new(root.path()).unwrap();
    let response = server.handle_request_with_method(&Method::DELETE, "/file.txt");

    // If we got here and got a 405, we know the method gate rejected it before any file I/O
    assert_eq!(response.status().as_u16(), 405, "method gate should reject DELETE");

    // Verify the response doesn't contain the file's content
    // (This would be checked by reading the response body, but for now we just verify the status)
}

#[test]
fn trace_method_returns_405() {
    let root = TempDir::new().unwrap();
    let server = Server::new(root.path()).unwrap();
    let response = server.handle_request_with_method(&Method::TRACE, "/anything");

    assert_eq!(response.status().as_u16(), 405);
}

#[test]
fn connect_method_returns_405() {
    let root = TempDir::new().unwrap();
    let server = Server::new(root.path()).unwrap();
    let response = server.handle_request_with_method(&Method::CONNECT, "/anything");

    assert_eq!(response.status().as_u16(), 405);
}