scratch-server 1.1.0

Simple HTTP Server
Documentation
use reqwest::blocking::Client;
use reqwest::header::ACCEPT_ENCODING;
use std::fs;
use std::io::Read;
use std::time::Duration;

use crate::common::utils::{http_client, https_client, spawn_server, testdata_path};

#[test]
fn port_argument_starts_server_on_selected_port() {
    let server = spawn_server(&["--ip", "127.0.0.1"], false);
    let response = http_client()
        .get(server.base_url())
        .send()
        .expect("Request to server failed");

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

#[test]
fn cert_and_certpass_enable_https_requests() {
    let cert_pass = "testing";
    let cert_path = testdata_path(&["certs", "keyStore.p12"]);
    let cert_value = cert_path.to_string_lossy().to_string();
    let server = spawn_server(&["--cert", &cert_value, "--certpass", cert_pass], false);

    let response = https_client()
        .get(server.https_base_url())
        .send()
        .expect("HTTPS request failed");

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

#[test]
fn silent_argument_disables_startup_logging() {
    let mut noisy = spawn_server(&["--ip", "127.0.0.1"], true);
    let mut silent = spawn_server(&["--ip", "127.0.0.1", "--silent"], true);

    let _ = noisy.child.kill();
    let _ = silent.child.kill();

    let _ = noisy.child.wait();
    let _ = silent.child.wait();

    let mut noisy_stdout = String::new();
    let mut silent_stdout = String::new();

    if let Some(mut out) = noisy.child.stdout.take() {
        out.read_to_string(&mut noisy_stdout)
            .expect("Failed reading noisy stdout");
    }
    if let Some(mut out) = silent.child.stdout.take() {
        out.read_to_string(&mut silent_stdout)
            .expect("Failed reading silent stdout");
    }

    assert!(
        noisy_stdout.contains("Port:") || noisy_stdout.contains("Logs:"),
        "Expected startup output in non-silent mode, got: {noisy_stdout}"
    );
    assert!(
        !silent_stdout.contains("Port:") && !silent_stdout.contains("Logs:"),
        "Did not expect startup banner in silent mode, got: {silent_stdout}"
    );
}

#[test]
fn requests_are_logged_when_logging_enabled() {
    let mut server = spawn_server(&["--ip", "127.0.0.1"], true);
    let response = http_client()
        .get(server.base_url())
        .send()
        .expect("Request to server failed");
    assert!(response.status().is_success());

    server.child.kill().expect("Failed to kill server");
    server.child.wait().expect("Failed to wait for server");

    if let Some(mut stdout) = server.child.stdout.take() {
        let mut stdout_string = String::new();
        stdout
            .read_to_string(&mut stdout_string)
            .expect("Failed to read server stdout");
        println!("Server stdout:\n{}", stdout_string);
        assert!(stdout_string.contains("127.0.0.1"));
        assert!(stdout_string.contains("200"));
        assert!(stdout_string.contains("GET"));
        assert!(stdout_string.contains("/"));
    }
}

#[test]
fn cors_argument_adds_cors_headers_to_options_response() {
    let server = spawn_server(&["--ip", "127.0.0.1", "--cors"], false);

    let response = http_client()
        .get(server.base_url())
        .send()
        .expect("CORS request failed");

    assert_eq!(response.status().as_u16(), 200);
    assert_eq!(
        response
            .headers()
            .get("access-control-allow-origin")
            .and_then(|h| h.to_str().ok()),
        Some("*")
    );
    assert_eq!(
        response
            .headers()
            .get("access-control-allow-methods")
            .and_then(|h| h.to_str().ok()),
        Some("GET, POST, PUT, DELETE")
    );
}

#[test]
fn ip_argument_binds_server_to_localhost() {
    let server = spawn_server(&["--ip", "127.0.0.1"], false);

    let response = http_client()
        .get(server.base_url())
        .send()
        .expect("Localhost request failed");

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

#[test]
fn auth_argument_requires_credentials_and_allows_valid_basic_auth() {
    let server = spawn_server(&["--ip", "127.0.0.1", "--auth", "user:pass"], false);

    let unauthorized = http_client()
        .get(server.base_url())
        .send()
        .expect("Unauthorized request failed");

    assert_eq!(unauthorized.status().as_u16(), 401);
    assert_eq!(
        unauthorized
            .headers()
            .get("www-authenticate")
            .and_then(|h| h.to_str().ok()),
        Some("Basic")
    );

    let authorized = http_client()
        .get(server.base_url())
        .basic_auth("user", Some("pass"))
        .send()
        .expect("Authorized request failed");

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

#[test]
fn compression_argument_sets_gzip_content_encoding() {
    let server = spawn_server(&["--ip", "127.0.0.1", "--compression"], false);

    let response = Client::builder()
        .timeout(Duration::from_secs(5))
        .build()
        .expect("Failed to build client")
        .get(server.base_url())
        .header(ACCEPT_ENCODING, "gzip")
        .send()
        .expect("Compressed request failed");

    assert_eq!(response.status().as_u16(), 200);
    assert_eq!(
        response
            .headers()
            .get("content-encoding")
            .and_then(|h| h.to_str().ok()),
        Some("gzip")
    );
}

#[test]
fn compression_argument_only_compresses_when_accept_encoding_is_set() {
    let server = spawn_server(&["--ip", "127.0.0.1"], false);

    let response = Client::builder()
        .timeout(Duration::from_secs(5))
        .build()
        .expect("Failed to build no-gzip client")
        .get(server.base_url())
        .header(ACCEPT_ENCODING, "gzip")
        .send()
        .expect("Compressed request failed");

    assert_eq!(response.status().as_u16(), 200);
    assert_eq!(
        response
            .headers()
            .get("content-encoding")
            .and_then(|h| h.to_str().ok()),
        None
    );
}

#[test]
fn index_argument_serves_custom_index_file() {
    let custom_index = testdata_path(&["public", "custom_index.html"]);
    let custom_index_value = custom_index.to_string_lossy().to_string();
    let expected = fs::read_to_string(&custom_index).expect("Failed to read fixture file");

    let server = spawn_server(
        &["--ip", "127.0.0.1", "--index", &custom_index_value],
        false,
    );

    let response = http_client()
        .get(format!("{}/", server.base_url()))
        .send()
        .expect("Custom index request failed");

    assert_eq!(response.status().as_u16(), 200);
    let body = response.text().expect("Failed to read response body");
    assert_eq!(body, expected);
}