ic-host-process 0.62.1

Native process observations, admitted execution and Git provenance
Documentation
use super::*;
use std::io::Read as _;

fn limits() -> HttpFixtureLimits {
    HttpFixtureLimits {
        timeout: Duration::from_secs(10),
        max_header_bytes: 1024,
        max_body_bytes: 4,
    }
}

fn client(fixture: &HttpFixture) -> TcpStream {
    let stream = TcpStream::connect(fixture.endpoint().strip_prefix("http://").unwrap()).unwrap();
    stream
        .set_read_timeout(Some(Duration::from_secs(5)))
        .unwrap();
    stream
        .set_write_timeout(Some(Duration::from_secs(5)))
        .unwrap();
    stream
}

#[test]
fn responds_with_exact_bytes_and_returns_the_unmodified_request() {
    let fixture = HttpFixture::spawn(&[0, 255, 13, 10], "application/cbor", limits()).unwrap();
    let mut stream = client(&fixture);
    let request = b"POST /query HTTP/1.1\r\nContent-Length: 4\r\n\r\ntest";
    stream.write_all(request).unwrap();
    let mut response = Vec::new();
    stream.read_to_end(&mut response).unwrap();
    assert_eq!(response, b"HTTP/1.1 200 OK\r\nContent-Type: application/cbor\r\nContent-Length: 4\r\nConnection: close\r\n\r\n\x00\xff\r\n");
    assert_eq!(fixture.finish().unwrap(), request);
}

#[test]
fn refuses_invalid_setup_and_request_budgets() {
    for content_type in ["", "text/plain\r\nInjected: yes", "text/\0plain"] {
        assert_eq!(
            HttpFixture::spawn(&[], content_type, limits())
                .unwrap_err()
                .kind(),
            io::ErrorKind::InvalidInput
        );
    }
    for selected in [
        HttpFixtureLimits {
            timeout: Duration::ZERO,
            ..limits()
        },
        HttpFixtureLimits {
            max_header_bytes: 0,
            ..limits()
        },
        HttpFixtureLimits {
            max_header_bytes: 3,
            ..limits()
        },
        HttpFixtureLimits {
            max_body_bytes: usize::MAX,
            ..limits()
        },
    ] {
        assert_eq!(
            HttpFixture::spawn(&[], "text/plain", selected)
                .unwrap_err()
                .kind(),
            io::ErrorKind::InvalidInput
        );
    }
    for selected in [
        HttpFixtureLimits {
            max_header_bytes: 8,
            ..limits()
        },
        HttpFixtureLimits {
            max_body_bytes: 3,
            ..limits()
        },
    ] {
        let fixture = HttpFixture::spawn(&[], "text/plain", selected).unwrap();
        let mut stream = client(&fixture);
        stream
            .write_all(b"POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\ntest")
            .unwrap();
        assert_eq!(
            fixture.finish().unwrap_err().kind(),
            io::ErrorKind::InvalidData
        );
    }
}

#[test]
fn deadline_covers_accept_and_an_incomplete_request() {
    for connect in [false, true] {
        let fixture = HttpFixture::spawn(
            &[],
            "text/plain",
            HttpFixtureLimits {
                timeout: Duration::from_millis(100),
                ..limits()
            },
        )
        .unwrap();
        let mut stream = connect.then(|| client(&fixture));
        if let Some(stream) = stream.as_mut() {
            stream.write_all(b"GET / HTTP/1.1\r\n").unwrap();
        }
        let error = fixture.finish().unwrap_err();
        assert!(matches!(
            error.kind(),
            io::ErrorKind::TimedOut | io::ErrorKind::WouldBlock
        ));
    }
}

#[test]
fn dropping_cancels_an_unstarted_or_incomplete_exchange() {
    for connect in [false, true] {
        let fixture = HttpFixture::spawn(
            &[],
            "text/plain",
            HttpFixtureLimits {
                timeout: Duration::from_secs(30),
                ..limits()
            },
        )
        .unwrap();
        let mut stream = connect.then(|| client(&fixture));
        if let Some(stream) = stream.as_mut() {
            stream.write_all(b"GET / HTTP/1.1\r\n").unwrap();
        }
        let started = Instant::now();
        drop(fixture);
        assert!(started.elapsed() < Duration::from_secs(5));
    }
}

#[test]
fn dropping_interrupts_a_response_to_a_stalled_reader() {
    let fixture = HttpFixture::spawn(
        &vec![1; 8 * 1024 * 1024],
        "application/octet-stream",
        HttpFixtureLimits {
            timeout: Duration::from_secs(30),
            ..limits()
        },
    )
    .unwrap();
    let mut stream = client(&fixture);
    stream.write_all(b"GET / HTTP/1.1\r\n\r\n").unwrap();
    stream.read_exact(&mut [0]).unwrap();
    let started = Instant::now();
    drop(fixture);
    assert!(started.elapsed() < Duration::from_secs(5));
}